{"version":3,"file":"planConfig-Cp6n3Ly_.mjs","names":[],"sources":["../src/types/planSettings.ts","../src/schemas/plan/planConfig.ts"],"sourcesContent":["/**\n * The planning settings, described once for both surfaces.\n *\n * The terminal panel and the cockpit settings page render the same five fields,\n * and the one thing they must agree on is which config key each one writes. So\n * the ids, labels and key paths live here rather than in either renderer.\n *\n * It lives under src/types because that is the one server root a browser bundle\n * may read, which also means it may import nothing: pulling the config package\n * in for the thinking levels would drag a node module into the page. They are\n * spelled out instead, and a test asserts they still match the parser's.\n */\n\nexport const PLAN_CONFIG_SECTION_ID = 'planning';\nconst PLANNING_PATH = ['modes', PLAN_CONFIG_SECTION_ID] as const;\nconst MAIN_KEY = 'main';\nconst SUBAGENTS_KEY = 'subagents';\n\n/** Both agents fall back to the session's own settings when left unset. */\nconst INHERIT_MODEL = 'inherit the session model';\nconst INHERIT_THINKING = 'inherit';\n\n/** Mirrors DOOM_PLANNING_THINKING_LEVELS; pinned by planConfig's test. */\nexport const PLAN_THINKING_LEVELS: readonly string[] = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];\n\nexport interface PlanSettingShape {\n  readonly id: string;\n  readonly label: string;\n  readonly keyPath: readonly string[];\n  readonly placeholder: string;\n  readonly detail: string;\n  /** Present for a closed set; a renderer offers these rather than free text. */\n  readonly options?: readonly string[];\n  /** Offers the machine's authenticated models rather than free text. */\n  readonly models?: boolean;\n}\n\nexport const PLAN_SETTING_SHAPES: readonly PlanSettingShape[] = [\n  {\n    id: 'main.model',\n    label: 'main model',\n    keyPath: [...PLANNING_PATH, MAIN_KEY, 'model'],\n    placeholder: INHERIT_MODEL,\n    detail: 'model the main agent switches to while plan mode is on.',\n    models: true,\n  },\n  {\n    id: 'main.thinking',\n    label: 'main thinking',\n    keyPath: [...PLANNING_PATH, MAIN_KEY, 'thinking'],\n    placeholder: INHERIT_THINKING,\n    detail: 'thinking level appended to the main model while planning.',\n    options: PLAN_THINKING_LEVELS,\n  },\n  {\n    id: 'subagents.model',\n    label: 'subagent model',\n    keyPath: [...PLANNING_PATH, SUBAGENTS_KEY, 'model'],\n    placeholder: INHERIT_MODEL,\n    detail: 'model forced onto delegated planning subagents.',\n    models: true,\n  },\n  {\n    id: 'subagents.thinking',\n    label: 'subagent thinking',\n    keyPath: [...PLANNING_PATH, SUBAGENTS_KEY, 'thinking'],\n    placeholder: INHERIT_THINKING,\n    detail: 'thinking level appended to the subagent model.',\n    options: PLAN_THINKING_LEVELS,\n  },\n  {\n    id: 'plansdirectory',\n    label: 'plans directory',\n    keyPath: [...PLANNING_PATH, 'plansDirectory'],\n    placeholder: '~/.pi/plans',\n    detail: 'absolute, repo-relative, or under ~. written plans land here.',\n  },\n];\n","/**\n * Plan mode's slice of the `SPC e c` config panel.\n *\n * Plan mode reads `modes.planning` from disk on every activation, so an edit\n * here lands on the next time the mode is turned on with no restart and nothing\n * to invalidate.\n *\n * The descriptor table is the whole design: the panel renders it and the write\n * handler looks the key path back up in it, so adding a setting means adding a\n * row rather than a case.\n */\n\nimport { CONFIG_ACTION, type ConfigChoice, type ConfigField, type ConfigSection } from '@agimon-ai/doompi-core/config';\n\nimport { PLAN_CONFIG_SECTION_ID, PLAN_SETTING_SHAPES, type PlanSettingShape } from '../../types/planSettings';\nimport type { PlanningModeConfig } from './config';\n\nexport { PLAN_CONFIG_SECTION_ID };\nconst SECTION_ORDER = 30;\n/** Selecting this clears the setting, which is how the file spells \"inherit\". */\nconst INHERIT_CHOICE_ID = 'inherit';\n\n/**\n * A planning setting as the terminal panel needs it: the shared shape, plus how\n * to read the value out of a loaded config. The reader stays here because it\n * touches the parsed config types, which the browser half never sees.\n */\nexport interface PlanSettingDescriptor extends PlanSettingShape {\n  readonly read: (config: PlanningModeConfig | undefined) => string | undefined;\n}\n\nconst READERS: Readonly<Record<string, (config: PlanningModeConfig | undefined) => string | undefined>> = {\n  'main.model': (config) => config?.main?.model,\n  'main.thinking': (config) => config?.main?.thinking,\n  'subagents.model': (config) => config?.subagents?.model,\n  'subagents.thinking': (config) => config?.subagents?.thinking,\n  plansdirectory: (config) => config?.plansDirectory,\n};\n\nexport const PLAN_SETTINGS: readonly PlanSettingDescriptor[] = PLAN_SETTING_SHAPES.map((shape) => ({\n  ...shape,\n  read: READERS[shape.id]!,\n}));\n\nexport function planSettingByFieldId(fieldId: string): PlanSettingDescriptor | undefined {\n  return PLAN_SETTINGS.find((setting) => setting.id === fieldId);\n}\n\n/** A model this session could actually switch to, as the panel offers it. */\nexport interface PlanModelChoice {\n  readonly provider: string;\n  readonly id: string;\n}\n\n/**\n * The models the session can reach, plus the entry that clears the setting.\n *\n * Offered as a list because a model spec is `provider/id` and typing one from\n * memory is how a setting ends up naming a model this session cannot use. The\n * inherit entry CLEARS rather than writing a value: unset is what \"use the\n * session's own model\" means in the file.\n */\nfunction modelChoices(models: readonly PlanModelChoice[]): ConfigChoice[] {\n  const inherit: ConfigChoice = {\n    id: INHERIT_CHOICE_ID,\n    label: PLAN_SETTING_SHAPES.find((shape) => shape.models === true)?.placeholder ?? INHERIT_CHOICE_ID,\n    detail: 'Leaves the setting unset.',\n    action: CONFIG_ACTION.clear,\n  };\n  return [\n    inherit,\n    ...models.map((model): ConfigChoice => ({\n      id: `${model.provider}/${model.id}`,\n      label: model.id,\n      group: model.provider,\n      detail: model.provider,\n      action: CONFIG_ACTION.set,\n    })),\n  ];\n}\n\nfunction choicesFor(\n  setting: PlanSettingDescriptor,\n  models: readonly PlanModelChoice[],\n): { choices: ConfigChoice[]; kind: 'enum' | 'choice' } | undefined {\n  // `set` rather than a bespoke action: choosing a level is the same write as\n  // typing one, so it shares the handler.\n  if (setting.options) {\n    return {\n      kind: 'enum',\n      choices: setting.options.map((option) => ({ id: option, label: option, action: CONFIG_ACTION.set })),\n    };\n  }\n  // A model setting with nothing to offer stays free text rather than opening\n  // an empty list: a session with no authenticated provider can still be edited.\n  if (!setting.models || models.length === 0) return undefined;\n  return { kind: 'choice', choices: modelChoices(models) };\n}\n\nexport function planConfigSections(\n  config: PlanningModeConfig | undefined,\n  notice?: string,\n  models: readonly PlanModelChoice[] = [],\n): readonly ConfigSection[] {\n  const fields: ConfigField[] = PLAN_SETTINGS.map((setting) => {\n    const value = setting.read(config);\n    const offered = choicesFor(setting, models);\n    return {\n      id: setting.id,\n      label: setting.label,\n      kind: offered?.kind ?? ('text' as const),\n      keyPath: setting.keyPath.join('.'),\n      placeholder: setting.placeholder,\n      detail: setting.detail,\n      ...(value ? { value } : {}),\n      ...(offered ? { choices: offered.choices } : {}),\n    };\n  });\n  return [\n    {\n      id: PLAN_CONFIG_SECTION_ID,\n      title: 'planning',\n      order: SECTION_ORDER,\n      detail: 'plan mode',\n      fields,\n      ...(notice ? { notice, noticeLevel: 'error' as const } : {}),\n    },\n  ];\n}\n"],"mappings":"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"}