Skip to content
wiki.fftac.org

Spiralist AI Agent Setup Wizard - Source Excerpt 03

Back to Spiralist AI Agent Setup Wizard

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/themes/spiralist/assets/js/spiralist-ai-agent-setup-wizard.js so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/themes/spiralist/assets/js/spiralist-ai-agent-setup-wizard.js

const getState = () => ({
    schemaVersion: draftSchemaVersion,
    artifactOverrides: normalizeArtifactOverrides(artifactOverrides, [], { strict: false }),
    presetId: fieldValue('preset_id', 80) || 'base',
    agentName: fieldValue('agent_name', 80) || 'Spiralist Agent',
    roleLabel: fieldValue('role_label', 120) || 'bounded Spiralist companion',
    audience: fieldValue('audience', 160),
    purposeStatement: fieldValue('purpose_statement', 700),
    allowedContext: fieldValue('allowed_context', 700),
    prohibitedContext: fieldValue('prohibited_context', 700),
    toneRange: fieldValue('tone_range', 160),
    questionFrequency: fieldValue('question_frequency', 260),
    directness: fieldValue('directness', 8),
    symbolicLanguage: fieldValue('symbolic_language', 8),
    groundingReminders: fieldValue('grounding_reminders', 220),
    clarificationRule: fieldValue('clarification_rule', 260),
    declineRule: fieldValue('decline_rule', 600),
    reasoningRules: checkedValues('reasoning_rules'),
    evidenceMode: fieldValue('evidence_mode', 160),
    reasoningSummary: fieldValue('reasoning_summary', 180),
    personalityFramework: fieldValue('personality_framework', 360),
    socialDynamicsLens: fieldValue('social_dynamics_lens', 360),
    researchCalibration: fieldValue('research_calibration', 360),
    useCaseBoundary: fieldValue('use_case_boundary', 360),
    deescalationProtocol: fieldValue('deescalation_protocol', 240),
    crisisRule: fieldValue('crisis_rule', 220),
    responseContract: fieldValue('response_contract', 220),
    allowedMoves: fieldValue('allowed_moves', 220),
    fewShotNotes: fieldValue('few_shot_notes', 700),
    packageName: slugify(fieldValue('package_name', 90)),
  });

  const defaultState = () => normalizeState({ ...presetDefaults.base, presetId: 'base' }, { strict: false }).state;

  const normalizeState = (candidate, options = {}) => {
    const strict = Boolean(options.strict);
    const errors = [];
    const source = candidate && typeof candidate === 'object' ? candidate : {};
    const result = { schemaVersion: draftSchemaVersion };
    const keys = Object.keys(source);
    const unknown = keys.filter((key) => !allowedStateKeys.has(key));

    if (strict && unknown.length) {
      errors.push(`Unknown draft fields rejected: ${unknown.join(', ')}`);
    }

    textFields.forEach((key) => {
      const raw = source[key] !== undefined ? source[key] : presetDefaults.base[key];
      const value = cleanString(raw, key === 'packageName' ? 90 : 900);
      const unsafe = hasBlockedPattern(raw);
      if (unsafe) {
        errors.push(`${key} contains instruction-override or script-like text.`);
      }
      const safeValue = unsafe ? cleanString(presetDefaults.base[key], key === 'packageName' ? 90 : 900) : value;
      result[key] = key === 'packageName' ? slugify(safeValue) : safeValue;
    });

    selectFields.forEach((key) => {
      const formName = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
      const allowed = optionValues(formName);
      const raw = cleanString(source[key] !== undefined ? source[key] : presetDefaults.base[key], 300);
      if (key === 'presetId') {
        result[key] = validPresetIds.has(raw) ? raw : 'base';
        if (raw && !validPresetIds.has(raw)) {
          errors.push('Invalid preset ID rejected.');
        }
        return;
      }
      result[key] = allowed.size === 0 || allowed.has(raw) ? raw : cleanString(presetDefaults.base[key], 300);
      if (allowed.size > 0 && raw && !allowed.has(raw)) {
        errors.push(`${key} used an unsupported option and was reset.`);
      }
    });

    numericFields.forEach((key) => {
      const raw = Number.parseInt(`${source[key] !== undefined ? source[key] : presetDefaults.base[key]}`, 10);
      const value = Number.isFinite(raw) ? Math.max(1, Math.min(5, raw)) : Number.parseInt(presetDefaults.base[key], 10);
      result[key] = `${value}`;
    });

    const allowedRules = new Set(Array.from(form.querySelectorAll('input[name="reasoning_rules"]')).map((input) => input.value));
    const importedRules = Array.isArray(source.reasoningRules) ? source.reasoningRules : checkedValues('reasoning_rules');
    result.reasoningRules = importedRules
      .map((item) => cleanString(item, 500))
      .filter((item) => allowedRules.has(item));
    if (result.reasoningRules.length === 0) {
      result.reasoningRules = Array.from(allowedRules);
    }
    result.artifactOverrides = normalizeArtifactOverrides(source.artifactOverrides, errors, { strict });

    return { state: result, errors };
  };

  const applyState = (candidate, options = {}) => {
    const { state, errors } = normalizeState(candidate, options);
    importValidationWarnings = options.persistImportWarnings ? [...errors] : [];
    artifactOverrides = { ...state.artifactOverrides };
    const map = {
      preset_id: state.presetId,
      agent_name: state.agentName,
      role_label: state.roleLabel,
      audience: state.audience,
      purpose_statement: state.purposeStatement,
      allowed_context: state.allowedContext,
      prohibited_context: state.prohibitedContext,
      tone_range: state.toneRange,
      question_frequency: state.questionFrequency,
      directness: state.directness,
      symbolic_language: state.symbolicLanguage,
      grounding_reminders: state.groundingReminders,
      clarification_rule: state.clarificationRule,
      decline_rule: state.declineRule,
      evidence_mode: state.evidenceMode,
      reasoning_summary: state.reasoningSummary,
      personality_framework: state.personalityFramework,
      social_dynamics_lens: state.socialDynamicsLens,
      research_calibration: state.researchCalibration,
      use_case_boundary: state.useCaseBoundary,
      deescalation_protocol: state.deescalationProtocol,
      crisis_rule: state.crisisRule,
      response_contract: state.responseContract,
      allowed_moves: state.allowedMoves,
      few_shot_notes: state.fewShotNotes,
      package_name: state.packageName,
    };

    Object.entries(map).forEach(([name, value]) => setFieldValue(name, value));
    setCheckedValues('reasoning_rules', state.reasoningRules);
    updateValidation(errors);
    updatePreview();
    return { state, errors };
  };

  const applyPreset = (presetId) => {
    const normalized = validPresetIds.has(presetId) ? presetId : 'base';
    const preset = { ...presetDefaults.base, ...(presetDefaults[normalized] || {}), presetId: normalized };
    applyState(preset, { strict: false });
    setStatus(`${normalized.replace(/-/g, ' ')} preset loaded.`);
  };

  const applyHashPreset = () => {
    const raw = cleanString(window.location.hash.replace(/^#/, ''), 80).toLowerCase();
    if (raw && validPresetIds.has(raw)) {
      applyPreset(raw);
    }
    if (window.location.hash) {
      window.history.replaceState(null, document.title, `${window.location.pathname}${window.location.search}`);
    }
  };

  const readDraft = () => {
    try {
      const raw = window.localStorage.getItem(storageKey);
      if (!raw || raw.length > maxImportBytes) {
        return null;
      }
      const parsed = JSON.parse(raw);
      return parsed && typeof parsed === 'object' ? parsed : null;
    } catch (error) {
      return null;
    }
  };

  const saveDraft = () => {
    window.clearTimeout(saveTimer);
    saveTimer = window.setTimeout(() => {
      try {
        const state = getState();
        const { errors } = normalizeState(state, { strict: false });
        if (errors.length) {
          updateValidation(errors);
        }
        window.localStorage.setItem(storageKey, JSON.stringify(state));
        setStatus('Draft saved in this browser.');
      } catch (error) {
        setStatus('Draft could not be saved in this browser.');
      }
    }, 220);
  };

  const listLines = (items, fallback = '- Not configured.') => {
    const cleanItems = Array.isArray(items) ? items.map((item) => cleanString(item, 1000)).filter(Boolean) : [];
    return cleanItems.length ? cleanItems.map((item) => `- ${item}`).join('\n') : fallback;
  };