@caupulican/pi-adaptative 0.81.3 → 0.81.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/core/agent-session.d.ts +1 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +23 -17
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/billing-failover-controller.d.ts +7 -0
  7. package/dist/core/billing-failover-controller.d.ts.map +1 -1
  8. package/dist/core/billing-failover-controller.js +6 -0
  9. package/dist/core/billing-failover-controller.js.map +1 -1
  10. package/dist/core/compaction-support.d.ts +10 -1
  11. package/dist/core/compaction-support.d.ts.map +1 -1
  12. package/dist/core/compaction-support.js +68 -9
  13. package/dist/core/compaction-support.js.map +1 -1
  14. package/dist/core/export-html/index.d.ts.map +1 -1
  15. package/dist/core/export-html/index.js +9 -9
  16. package/dist/core/export-html/index.js.map +1 -1
  17. package/dist/core/failure-corpus.d.ts +49 -0
  18. package/dist/core/failure-corpus.d.ts.map +1 -0
  19. package/dist/core/failure-corpus.js +61 -0
  20. package/dist/core/failure-corpus.js.map +1 -0
  21. package/dist/core/model-router/fitness-gate.d.ts +5 -3
  22. package/dist/core/model-router/fitness-gate.d.ts.map +1 -1
  23. package/dist/core/model-router/fitness-gate.js +1 -0
  24. package/dist/core/model-router/fitness-gate.js.map +1 -1
  25. package/dist/core/model-router/status.d.ts +4 -0
  26. package/dist/core/model-router/status.d.ts.map +1 -1
  27. package/dist/core/model-router/status.js +4 -0
  28. package/dist/core/model-router/status.js.map +1 -1
  29. package/dist/core/model-router-controller.d.ts.map +1 -1
  30. package/dist/core/model-router-controller.js +2 -0
  31. package/dist/core/model-router-controller.js.map +1 -1
  32. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  33. package/dist/core/models/default-model-suggestions.js +18 -0
  34. package/dist/core/models/default-model-suggestions.js.map +1 -1
  35. package/dist/core/settings-manager.d.ts +1 -0
  36. package/dist/core/settings-manager.d.ts.map +1 -1
  37. package/dist/core/settings-manager.js +12 -0
  38. package/dist/core/settings-manager.js.map +1 -1
  39. package/dist/core/slash-commands.d.ts.map +1 -1
  40. package/dist/core/slash-commands.js +4 -0
  41. package/dist/core/slash-commands.js.map +1 -1
  42. package/dist/core/tools/bash.d.ts.map +1 -1
  43. package/dist/core/tools/bash.js +6 -8
  44. package/dist/core/tools/bash.js.map +1 -1
  45. package/dist/modes/interactive/components/fitness-role-selector.d.ts +1 -1
  46. package/dist/modes/interactive/components/fitness-role-selector.d.ts.map +1 -1
  47. package/dist/modes/interactive/components/fitness-role-selector.js +5 -0
  48. package/dist/modes/interactive/components/fitness-role-selector.js.map +1 -1
  49. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  50. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  51. package/dist/modes/interactive/interactive-mode.js +13 -0
  52. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  53. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  54. package/dist/modes/interactive/local-model-commands.js +14 -0
  55. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  56. package/dist/modes/interactive/session-flow-commands.d.ts +16 -0
  57. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  58. package/dist/modes/interactive/session-flow-commands.js +32 -0
  59. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  60. package/docs/bug-ledger.md +113 -0
  61. package/docs/models.md +9 -0
  62. package/docs/providers.md +12 -0
  63. package/docs/scout.md +4 -2
  64. package/docs/settings.md +2 -0
  65. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  66. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  67. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  68. package/examples/extensions/sandbox/package-lock.json +2 -2
  69. package/examples/extensions/sandbox/package.json +1 -1
  70. package/examples/extensions/with-deps/package-lock.json +2 -2
  71. package/examples/extensions/with-deps/package.json +1 -1
  72. package/npm-shrinkwrap.json +12 -12
  73. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"local-model-commands.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/local-model-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/models` local-model lifecycle and `/fitness` probe/role-assignment flows\n * extracted from interactive-mode.\n *\n * `/models` is a USER-invoked local-model manager (list/add/remove/stop, never a\n * model-invokable tool); the fitness helpers probe a model on the current host\n * and land a shaped role. The server/selector flows share a `LocalModelHost`\n * seam, while `runFitnessAndAssign`/`assignFitnessRole` take narrow host shapes\n * matching their prototype-driven behaviour tests (fitness-probe-gate,\n * fitness-role-assignment), which keep exercising interactive-mode's thin\n * wrappers unchanged.\n */\n\nimport type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { getSupportedThinkingLevels } from \"@caupulican/pi-ai\";\nimport {\n\ttype Component,\n\tContainer,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type { ModelRegistry } from \"../../core/model-registry.ts\";\nimport { resolveCliModel } from \"../../core/model-resolver.ts\";\nimport { DEFAULT_MODEL_SUGGESTIONS } from \"../../core/models/default-model-suggestions.ts\";\nimport { FitnessStore } from \"../../core/models/fitness-store.ts\";\nimport { registerLocalModel, unregisterLocalModel } from \"../../core/models/local-registration.ts\";\nimport type { OllamaRuntime } from \"../../core/models/local-runtime.ts\";\nimport { matchesInstalledLocalModel, normalizeModelSource } from \"../../core/models/model-ref.ts\";\nimport { formatModelFitnessReport, isProbeAllFailed } from \"../../core/research/model-fitness.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { DynamicBorder } from \"./components/dynamic-border.ts\";\nimport { type FitnessRole, FitnessRoleSelectorComponent } from \"./components/fitness-role-selector.ts\";\nimport { ModelSelectorComponent } from \"./components/model-selector.ts\";\nimport { ModelSuggestionSelectorComponent } from \"./components/model-suggestion-selector.ts\";\nimport { getSelectListTheme } from \"./theme/theme.ts\";\n\ntype SelectorFactory = (done: () => void) => { component: Component; focus: Component };\n\nconst MODEL_ROUTER_THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst MODEL_ROUTER_THINKING_INHERIT_VALUE = \"__inherit_model_router_thinking__\";\nconst MODEL_ROUTER_THINKING_INHERIT_LABEL = \"(inherit)\";\n\nconst THINKING_LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\nconst MODEL_ROUTER_THINKING_FALLBACK_LEVELS: readonly ThinkingLevel[] = [\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n];\n\ntype RouterThinkingLevel = ThinkingLevel | typeof MODEL_ROUTER_THINKING_INHERIT_VALUE;\ntype ModelRouterThinkingField =\n\t| \"cheapThinking\"\n\t| \"mediumThinking\"\n\t| \"expensiveThinking\"\n\t| \"executorThinking\"\n\t| \"judgeThinking\";\n\n/** Narrow seam for persisting a probed model's role — matches the fitness-role test. */\nexport interface AssignRoleHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n}\n\n/** Seam for the fitness probe + role-selector flow — matches the fitness-probe-gate test. */\nexport interface RunFitnessHost {\n\treadonly session: Pick<AgentSession, \"runModelFitness\"> & { modelRegistry?: ModelRegistry };\n\treadonly settingsManager: SettingsManager;\n\treadonly chatContainer: Container;\n\treadonly ui: TUI;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowSelector(create: SelectorFactory): void;\n}\n\n/** Full seam for the `/models` server/selector flows. */\nexport interface LocalModelHost {\n\treadonly localRuntime: OllamaRuntime;\n\treadonly session: AgentSession;\n\treadonly settingsManager: SettingsManager;\n\treadonly ui: TUI;\n\treadonly chatContainer: Container;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowSelector(create: SelectorFactory): void;\n}\n\nclass ModelRouterThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: RouterThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: RouterThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingOptions: SelectItem[] = [\n\t\t\t{\n\t\t\t\tvalue: MODEL_ROUTER_THINKING_INHERIT_VALUE,\n\t\t\t\tlabel: MODEL_ROUTER_THINKING_INHERIT_LABEL,\n\t\t\t\tdescription: \"Use current session default for this tier.\",\n\t\t\t},\n\t\t\t...availableLevels.map((level) => ({\n\t\t\t\tvalue: level,\n\t\t\t\tlabel: level,\n\t\t\t\tdescription: THINKING_LEVEL_DESCRIPTIONS[level],\n\t\t\t})),\n\t\t];\n\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingOptions,\n\t\t\tthinkingOptions.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tMODEL_ROUTER_THINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\tconst index = thinkingOptions.findIndex((item) => item.value === currentLevel);\n\t\tif (index !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(index);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as RouterThinkingLevel);\n\t\t};\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n}\n/**\n * /models — USER-invoked local model lifecycle (never a model-invokable tool):\n * list/add/remove/stop per local-model-lifecycle-design.md. Removal is explicit-only with\n * full disclosure; a pasted install command is parsed for its ref, never executed.\n */\nexport async function handleModelsCommand(host: LocalModelHost, argsText: string): Promise<void> {\n\tconst [action = \"list\", ...rest] = argsText.split(/\\s+/).filter(Boolean);\n\ttry {\n\t\tif (action === \"suggest\" || action === \"suggestions\") {\n\t\t\tshowModelSuggestionSelector(host);\n\t\t\treturn;\n\t\t}\n\t\tif (action === \"stop\") {\n\t\t\tconst stopped = host.localRuntime.stop();\n\t\t\thost.showStatus(\n\t\t\t\tstopped.stopped\n\t\t\t\t\t? \"Pi-managed local model server stopped (models remain installed).\"\n\t\t\t\t\t: \"No pi-managed server running (a system server, if any, is not pi's to stop).\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (action === \"add\") {\n\t\t\tconst rawRef = rest.join(\" \");\n\t\t\tif (!rawRef) {\n\t\t\t\thost.showStatus(\n\t\t\t\t\t\"Usage: /models add <ollama-tag | hf.co/org/repo[:quant] | huggingface URL | pasted install command>\",\n\t\t\t\t);\n\t\t\t\thost.showStatus(\"Or start from a validated suggestion: /models suggest\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst source = normalizeModelSource(rawRef);\n\t\t\tif (source.type === \"rejected\") {\n\t\t\t\thost.showStatus(`Not added: ${source.reason}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (source.type === \"api\") {\n\t\t\t\thost.showStatus(\n\t\t\t\t\t`${source.ref} is an API model — nothing to install. Configure auth for the provider, then probe it with /fitness ${source.ref}.`,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait addLocalModel(host, source.pullRef);\n\t\t\treturn;\n\t\t}\n\n\t\tif (action === \"remove\") {\n\t\t\tconst ref = rest[0];\n\t\t\tconst confirmed = rest[1] === \"confirm\";\n\t\t\tif (!ref) {\n\t\t\t\thost.showStatus(\"Usage: /models remove <ref> confirm\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait removeLocalModel(host, ref, confirmed);\n\t\t\treturn;\n\t\t}\n\n\t\tawait listLocalModels(host);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function ensureLocalServer(host: LocalModelHost): Promise<boolean> {\n\tconst status = await host.localRuntime.detect();\n\tif (status.serverUp) return true;\n\tif (!status.binaryPath) {\n\t\tfor (const line of host.localRuntime.installGuide()) host.showStatus(line);\n\t\treturn false;\n\t}\n\thost.showStatus(\n\t\t`Starting local model server (${status.binarySource} binary, owned storage: ${status.ownedModelsDir})…`,\n\t);\n\tconst started = await host.localRuntime.start();\n\tif (!started.started) {\n\t\thost.showStatus(`Could not start the local server: ${started.reason}`);\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nexport async function listLocalModels(host: LocalModelHost): Promise<void> {\n\tconst status = await host.localRuntime.detect();\n\tif (!status.serverUp) {\n\t\tif (!status.binaryPath) {\n\t\t\tfor (const line of host.localRuntime.installGuide()) host.showStatus(line);\n\t\t\treturn;\n\t\t}\n\t\thost.showStatus(\n\t\t\t`Local server not running (binary: ${status.binarySource}). /models add starts it on demand; /fitness probes registered models.`,\n\t\t);\n\t\treturn;\n\t}\n\tconst models = await host.localRuntime.list();\n\tconst fitness = FitnessStore.forAgentDir(getAgentDir()).getForHost();\n\tconst lines = [\n\t\t`Local models (${status.managedByPi ? `pi-managed server, storage: ${status.ownedModelsDir}` : \"system server — storage owned by the system daemon\"}):`,\n\t\t...(models.length === 0\n\t\t\t? [\" (none installed — /models add <ref>, or /models suggest for a validated roster)\"]\n\t\t\t: []),\n\t\t...models.map((model) => {\n\t\t\tconst report = fitness.find((entry) => entry.model === `ollama/${model.name}`);\n\t\t\tconst gb = (model.sizeBytes / 1e9).toFixed(2);\n\t\t\tconst probe = report\n\t\t\t\t? `probed ${report.at.slice(0, 10)}: digest ${report.report.digest?.succeeded ?? \"?\"}/${report.report.digest?.total ?? \"?\"}, tool-calls ${report.report.toolCall.succeeded}/${report.report.toolCall.total}${report.report.tokensPerSecond ? `, ~${report.report.tokensPerSecond} tok/s` : \"\"}`\n\t\t\t\t: `unprobed — run /fitness ollama/${model.name}`;\n\t\t\treturn ` - ${model.name} (${gb} GB) · ${probe}`;\n\t\t}),\n\t\t\"Commands: /models add <ref> · /models remove <ref> confirm · /models stop\",\n\t];\n\tfor (const line of lines) host.showStatus(line);\n}\n\nexport async function addLocalModel(host: LocalModelHost, pullRef: string, preselectRole?: FitnessRole): Promise<void> {\n\tif (!(await ensureLocalServer(host))) return;\n\thost.showStatus(`Pulling ${pullRef}… (weights land in the server's model storage)`);\n\tlet lastShown = 0;\n\tconst pulled = await host.localRuntime.pull(pullRef, (progress) => {\n\t\tconst now = Date.now();\n\t\tif (now - lastShown > 2000) {\n\t\t\tlastShown = now;\n\t\t\thost.showStatus(` ${pullRef}: ${progress}`);\n\t\t}\n\t});\n\tif (!pulled.ok) {\n\t\thost.showStatus(`Pull failed: ${pulled.error}`);\n\t\treturn;\n\t}\n\tconst registration = registerLocalModel({\n\t\tagentDir: getAgentDir(),\n\t\tref: pullRef,\n\t\tbaseUrl: host.localRuntime.baseUrl,\n\t});\n\tif (!registration.ok) {\n\t\thost.showStatus(`Pulled, but not auto-registered: ${registration.reason}`);\n\t\tif (registration.manualSnippet) {\n\t\t\thost.showStatus(`Add this to ${registration.modelsJsonPath} yourself:\\n${registration.manualSnippet}`);\n\t\t}\n\t\treturn;\n\t}\n\thost.session.modelRegistry.refresh();\n\thost.showStatus(`${pullRef} installed and registered as ollama/${pullRef}. Probing fitness…`);\n\tawait runFitnessAndAssign(host, `ollama/${pullRef}`, preselectRole);\n}\n\nexport async function removeLocalModel(host: LocalModelHost, ref: string, confirmed: boolean): Promise<void> {\n\tconst status = await host.localRuntime.detect();\n\tif (!status.serverUp) {\n\t\thost.showStatus(\"Local server not running — start it (any /models action) before removing.\");\n\t\treturn;\n\t}\n\tconst models = await host.localRuntime.list();\n\tconst target = models.find((model) => matchesInstalledLocalModel(ref, model.name));\n\tif (!target) {\n\t\thost.showStatus(\n\t\t\t`${ref} is not installed. Installed: ${models.map((model) => model.name).join(\", \") || \"(none)\"}`,\n\t\t);\n\t\treturn;\n\t}\n\tif (!confirmed) {\n\t\t// EXPLICIT USER ACTION ONLY: full disclosure, then require the confirm token.\n\t\tconst gb = (target.sizeBytes / 1e9).toFixed(2);\n\t\thost.showStatus(\n\t\t\t[\n\t\t\t\t`Removing ${ref} will delete:`,\n\t\t\t\t` - model weights (${gb} GB) from ${status.managedByPi ? status.ownedModelsDir : \"the system server's storage\"}`,\n\t\t\t\t` - the ollama/${ref} entry in models.json`,\n\t\t\t\t` - its cached fitness report for this host`,\n\t\t\t\t`Run: /models remove ${ref} confirm`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\treturn;\n\t}\n\tconst removed = await host.localRuntime.remove(ref);\n\tif (!removed.ok) {\n\t\thost.showStatus(`Remove failed: ${removed.error}`);\n\t\treturn;\n\t}\n\tunregisterLocalModel({ agentDir: getAgentDir(), ref });\n\tFitnessStore.forAgentDir(getAgentDir()).remove(`ollama/${ref}`);\n\thost.session.modelRegistry.refresh();\n\thost.showStatus(`${ref} removed: weights deleted, registration and fitness report dropped.`);\n}\n\n/** /fitness with no args: pick a model from the configured registry, probe it, assign a role. */\n/** Pick a validated suggestion → install it → probe on this host → land its shaped role. */\nexport function showModelSuggestionSelector(host: LocalModelHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSuggestionSelectorComponent(\n\t\t\tDEFAULT_MODEL_SUGGESTIONS,\n\t\t\tasync (suggestion) => {\n\t\t\t\tdone();\n\t\t\t\t// The shaped role rides along so the post-probe selector lands on it pre-selected;\n\t\t\t\t// non-tool-callers carry curator/judge/none, never executor, so this can't footgun.\n\t\t\t\tawait addLocalModel(host, suggestion.pullRef, suggestion.assignRole);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showFitnessModelSelector(host: LocalModelHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSelectorComponent(\n\t\t\thost.ui,\n\t\t\thost.session.model,\n\t\t\thost.settingsManager,\n\t\t\thost.session.modelRegistry,\n\t\t\thost.session.scopedModels,\n\t\t\tasync (model) => {\n\t\t\t\tdone();\n\t\t\t\tawait runFitnessAndAssign(host, `${model.provider}/${model.id}`);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\n/** Resolve the routed model's supported thinking levels for a fitness-assignment flow, with a safe fallback. */\nfunction getModelThinkingLevels(modelRegistry: ModelRegistry | undefined, modelRef: string): ThinkingLevel[] {\n\tif (!modelRegistry) {\n\t\treturn [...MODEL_ROUTER_THINKING_FALLBACK_LEVELS];\n\t}\n\tconst resolved = resolveCliModel({ cliModel: modelRef, modelRegistry });\n\tif (!resolved.model) {\n\t\treturn [...MODEL_ROUTER_THINKING_FALLBACK_LEVELS];\n\t}\n\treturn getSupportedThinkingLevels(resolved.model);\n}\n\nfunction getModelRouterThinkingField(role: FitnessRole): ModelRouterThinkingField | undefined {\n\tif (role === \"router-cheap\") {\n\t\treturn \"cheapThinking\";\n\t}\n\tif (role === \"router-medium\") {\n\t\treturn \"mediumThinking\";\n\t}\n\tif (role === \"router-expensive\") {\n\t\treturn \"expensiveThinking\";\n\t}\n\tif (role === \"executor\") {\n\t\treturn \"executorThinking\";\n\t}\n\tif (role === \"judge\") {\n\t\treturn \"judgeThinking\";\n\t}\n\treturn undefined;\n}\n\nfunction promptForModelRouterThinking(host: RunFitnessHost, modelRef: string, role: FitnessRole): void {\n\tconst thinkingField = getModelRouterThinkingField(role);\n\tif (!thinkingField) {\n\t\treturn;\n\t}\n\n\tconst modelRegistry = host.session.modelRegistry;\n\tconst availableLevels = getModelThinkingLevels(modelRegistry, modelRef);\n\tconst settings = host.settingsManager.getModelRouterSettings();\n\tconst configuredThinking = settings[thinkingField];\n\tconst currentThinking =\n\t\tconfiguredThinking && availableLevels.includes(configuredThinking)\n\t\t\t? configuredThinking\n\t\t\t: MODEL_ROUTER_THINKING_INHERIT_VALUE;\n\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelRouterThinkingSelectorComponent(\n\t\t\tcurrentThinking,\n\t\t\tavailableLevels,\n\t\t\t(level) => {\n\t\t\t\tdone();\n\t\t\t\thost.settingsManager.setModelRouterSettings({\n\t\t\t\t\t...settings,\n\t\t\t\t\t[thinkingField]: level === MODEL_ROUTER_THINKING_INHERIT_VALUE ? undefined : level,\n\t\t\t\t});\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\n/** Probe a model's fitness, show the report, then offer one-step role assignment. When the model\n * came from a validated suggestion, `preselectRole` lands its shaped role already highlighted. */\nexport async function runFitnessAndAssign(\n\thost: RunFitnessHost,\n\tmodelRef: string,\n\tpreselectRole?: FitnessRole,\n): Promise<void> {\n\thost.showStatus(`Model fitness probe running on ${modelRef}… (6 surfaces; local models may take a few minutes)`);\n\ttry {\n\t\tconst outcome = await host.session.runModelFitness({ model: modelRef });\n\t\tif (!outcome.started) {\n\t\t\thost.showStatus(`Model fitness skipped: ${outcome.skipReason}`);\n\t\t\treturn;\n\t\t}\n\t\thost.chatContainer.addChild(new Spacer(1));\n\t\thost.chatContainer.addChild(new Text(formatModelFitnessReport(outcome.model, outcome.report), 1, 0));\n\t\thost.ui.requestRender();\n\t\t// Validate-before-load: zero successes on every probed surface means the model cannot\n\t\t// drive any of the harness's subagent contracts on this host — refuse adoption instead\n\t\t// of landing a role selector the user might reflexively confirm (this is the reported\n\t\t// bug: a 0/3-everywhere model still got set as judge model and saved to Model Router).\n\t\tif (isProbeAllFailed(outcome.report)) {\n\t\t\thost.showStatus(\n\t\t\t\t`${outcome.model} failed the fitness probe on all surfaces — not configured. Use /model to set it manually if you accept the risk.`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\thost.showSelector((done) => {\n\t\t\tconst selector = new FitnessRoleSelectorComponent(\n\t\t\t\toutcome.model,\n\t\t\t\t(role) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tassignFitnessRole(host, outcome.model, role);\n\t\t\t\t\tpromptForModelRouterThinking(host, outcome.model, role);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t},\n\t\t\t\tpreselectRole,\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\n/** Persist a role assignment from the post-probe selector into the matching settings. */\nexport function assignFitnessRole(host: AssignRoleHost, modelRef: string, role: FitnessRole): void {\n\tif (role === \"none\") {\n\t\thost.showStatus(`Fitness result for ${modelRef} saved. Assign a role later from /settings.`);\n\t\treturn;\n\t}\n\tif (role === \"curator\") {\n\t\tconst current = host.settingsManager.getContextCurationSettings();\n\t\thost.settingsManager.setContextCurationSettings({ ...current, enabled: true, model: modelRef });\n\t\thost.showStatus(`Context curation enabled with ${modelRef} as the curator.`);\n\t\treturn;\n\t}\n\tif (role === \"executor\") {\n\t\tconst router = host.settingsManager.getModelRouterSettings();\n\t\thost.settingsManager.setModelRouterSettings({ ...router, executorModel: modelRef });\n\t\tconst hint = router.enabled ? \"\" : \" Model router is currently disabled — enable it in /settings → Model Router.\";\n\t\thost.showStatus(`${modelRef} set as the toolkit executor (direct Level-0 hits route to it).${hint}`);\n\t\treturn;\n\t}\n\tconst router = host.settingsManager.getModelRouterSettings();\n\tconst field =\n\t\trole === \"router-cheap\"\n\t\t\t? \"cheapModel\"\n\t\t\t: role === \"router-medium\"\n\t\t\t\t? \"mediumModel\"\n\t\t\t\t: role === \"router-expensive\"\n\t\t\t\t\t? \"expensiveModel\"\n\t\t\t\t\t: role === \"judge\"\n\t\t\t\t\t\t? \"judgeModel\"\n\t\t\t\t\t\t: \"learningModel\";\n\thost.settingsManager.setModelRouterSettings({ ...router, [field]: modelRef });\n\tconst hint = router.enabled ? \"\" : \" Model router is currently disabled — enable it in /settings → Model Router.\";\n\thost.showStatus(`${modelRef} set as ${role.replace(\"router-\", \"router \")} model.${hint}`);\n}\n"]}
1
+ {"version":3,"file":"local-model-commands.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/local-model-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/models` local-model lifecycle and `/fitness` probe/role-assignment flows\n * extracted from interactive-mode.\n *\n * `/models` is a USER-invoked local-model manager (list/add/remove/stop, never a\n * model-invokable tool); the fitness helpers probe a model on the current host\n * and land a shaped role. The server/selector flows share a `LocalModelHost`\n * seam, while `runFitnessAndAssign`/`assignFitnessRole` take narrow host shapes\n * matching their prototype-driven behaviour tests (fitness-probe-gate,\n * fitness-role-assignment), which keep exercising interactive-mode's thin\n * wrappers unchanged.\n */\n\nimport type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { getSupportedThinkingLevels } from \"@caupulican/pi-ai\";\nimport {\n\ttype Component,\n\tContainer,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type { ModelRegistry } from \"../../core/model-registry.ts\";\nimport { resolveCliModel } from \"../../core/model-resolver.ts\";\nimport { evaluateSurfaceFitness } from \"../../core/model-router/fitness-gate.ts\";\nimport { DEFAULT_MODEL_SUGGESTIONS } from \"../../core/models/default-model-suggestions.ts\";\nimport { FitnessStore } from \"../../core/models/fitness-store.ts\";\nimport { registerLocalModel, unregisterLocalModel } from \"../../core/models/local-registration.ts\";\nimport type { OllamaRuntime } from \"../../core/models/local-runtime.ts\";\nimport { matchesInstalledLocalModel, normalizeModelSource } from \"../../core/models/model-ref.ts\";\nimport { formatModelFitnessReport, isProbeAllFailed } from \"../../core/research/model-fitness.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { DynamicBorder } from \"./components/dynamic-border.ts\";\nimport { type FitnessRole, FitnessRoleSelectorComponent } from \"./components/fitness-role-selector.ts\";\nimport { ModelSelectorComponent } from \"./components/model-selector.ts\";\nimport { ModelSuggestionSelectorComponent } from \"./components/model-suggestion-selector.ts\";\nimport { getSelectListTheme } from \"./theme/theme.ts\";\n\ntype SelectorFactory = (done: () => void) => { component: Component; focus: Component };\n\nconst MODEL_ROUTER_THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst MODEL_ROUTER_THINKING_INHERIT_VALUE = \"__inherit_model_router_thinking__\";\nconst MODEL_ROUTER_THINKING_INHERIT_LABEL = \"(inherit)\";\n\nconst THINKING_LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\nconst MODEL_ROUTER_THINKING_FALLBACK_LEVELS: readonly ThinkingLevel[] = [\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n];\n\ntype RouterThinkingLevel = ThinkingLevel | typeof MODEL_ROUTER_THINKING_INHERIT_VALUE;\ntype ModelRouterThinkingField =\n\t| \"cheapThinking\"\n\t| \"mediumThinking\"\n\t| \"expensiveThinking\"\n\t| \"executorThinking\"\n\t| \"judgeThinking\";\n\n/** Narrow seam for persisting a probed model's role — matches the fitness-role test. */\nexport interface AssignRoleHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n}\n\n/** Seam for the fitness probe + role-selector flow — matches the fitness-probe-gate test. */\nexport interface RunFitnessHost {\n\treadonly session: Pick<AgentSession, \"runModelFitness\"> & { modelRegistry?: ModelRegistry };\n\treadonly settingsManager: SettingsManager;\n\treadonly chatContainer: Container;\n\treadonly ui: TUI;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowSelector(create: SelectorFactory): void;\n}\n\n/** Full seam for the `/models` server/selector flows. */\nexport interface LocalModelHost {\n\treadonly localRuntime: OllamaRuntime;\n\treadonly session: AgentSession;\n\treadonly settingsManager: SettingsManager;\n\treadonly ui: TUI;\n\treadonly chatContainer: Container;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowSelector(create: SelectorFactory): void;\n}\n\nclass ModelRouterThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: RouterThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: RouterThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingOptions: SelectItem[] = [\n\t\t\t{\n\t\t\t\tvalue: MODEL_ROUTER_THINKING_INHERIT_VALUE,\n\t\t\t\tlabel: MODEL_ROUTER_THINKING_INHERIT_LABEL,\n\t\t\t\tdescription: \"Use current session default for this tier.\",\n\t\t\t},\n\t\t\t...availableLevels.map((level) => ({\n\t\t\t\tvalue: level,\n\t\t\t\tlabel: level,\n\t\t\t\tdescription: THINKING_LEVEL_DESCRIPTIONS[level],\n\t\t\t})),\n\t\t];\n\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingOptions,\n\t\t\tthinkingOptions.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tMODEL_ROUTER_THINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\tconst index = thinkingOptions.findIndex((item) => item.value === currentLevel);\n\t\tif (index !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(index);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as RouterThinkingLevel);\n\t\t};\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n}\n/**\n * /models — USER-invoked local model lifecycle (never a model-invokable tool):\n * list/add/remove/stop per local-model-lifecycle-design.md. Removal is explicit-only with\n * full disclosure; a pasted install command is parsed for its ref, never executed.\n */\nexport async function handleModelsCommand(host: LocalModelHost, argsText: string): Promise<void> {\n\tconst [action = \"list\", ...rest] = argsText.split(/\\s+/).filter(Boolean);\n\ttry {\n\t\tif (action === \"suggest\" || action === \"suggestions\") {\n\t\t\tshowModelSuggestionSelector(host);\n\t\t\treturn;\n\t\t}\n\t\tif (action === \"stop\") {\n\t\t\tconst stopped = host.localRuntime.stop();\n\t\t\thost.showStatus(\n\t\t\t\tstopped.stopped\n\t\t\t\t\t? \"Pi-managed local model server stopped (models remain installed).\"\n\t\t\t\t\t: \"No pi-managed server running (a system server, if any, is not pi's to stop).\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (action === \"add\") {\n\t\t\tconst rawRef = rest.join(\" \");\n\t\t\tif (!rawRef) {\n\t\t\t\thost.showStatus(\n\t\t\t\t\t\"Usage: /models add <ollama-tag | hf.co/org/repo[:quant] | huggingface URL | pasted install command>\",\n\t\t\t\t);\n\t\t\t\thost.showStatus(\"Or start from a validated suggestion: /models suggest\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst source = normalizeModelSource(rawRef);\n\t\t\tif (source.type === \"rejected\") {\n\t\t\t\thost.showStatus(`Not added: ${source.reason}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (source.type === \"api\") {\n\t\t\t\thost.showStatus(\n\t\t\t\t\t`${source.ref} is an API model — nothing to install. Configure auth for the provider, then probe it with /fitness ${source.ref}.`,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait addLocalModel(host, source.pullRef);\n\t\t\treturn;\n\t\t}\n\n\t\tif (action === \"remove\") {\n\t\t\tconst ref = rest[0];\n\t\t\tconst confirmed = rest[1] === \"confirm\";\n\t\t\tif (!ref) {\n\t\t\t\thost.showStatus(\"Usage: /models remove <ref> confirm\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait removeLocalModel(host, ref, confirmed);\n\t\t\treturn;\n\t\t}\n\n\t\tawait listLocalModels(host);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function ensureLocalServer(host: LocalModelHost): Promise<boolean> {\n\tconst status = await host.localRuntime.detect();\n\tif (status.serverUp) return true;\n\tif (!status.binaryPath) {\n\t\tfor (const line of host.localRuntime.installGuide()) host.showStatus(line);\n\t\treturn false;\n\t}\n\thost.showStatus(\n\t\t`Starting local model server (${status.binarySource} binary, owned storage: ${status.ownedModelsDir})…`,\n\t);\n\tconst started = await host.localRuntime.start();\n\tif (!started.started) {\n\t\thost.showStatus(`Could not start the local server: ${started.reason}`);\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nexport async function listLocalModels(host: LocalModelHost): Promise<void> {\n\tconst status = await host.localRuntime.detect();\n\tif (!status.serverUp) {\n\t\tif (!status.binaryPath) {\n\t\t\tfor (const line of host.localRuntime.installGuide()) host.showStatus(line);\n\t\t\treturn;\n\t\t}\n\t\thost.showStatus(\n\t\t\t`Local server not running (binary: ${status.binarySource}). /models add starts it on demand; /fitness probes registered models.`,\n\t\t);\n\t\treturn;\n\t}\n\tconst models = await host.localRuntime.list();\n\tconst fitness = FitnessStore.forAgentDir(getAgentDir()).getForHost();\n\tconst lines = [\n\t\t`Local models (${status.managedByPi ? `pi-managed server, storage: ${status.ownedModelsDir}` : \"system server — storage owned by the system daemon\"}):`,\n\t\t...(models.length === 0\n\t\t\t? [\" (none installed — /models add <ref>, or /models suggest for a validated roster)\"]\n\t\t\t: []),\n\t\t...models.map((model) => {\n\t\t\tconst report = fitness.find((entry) => entry.model === `ollama/${model.name}`);\n\t\t\tconst gb = (model.sizeBytes / 1e9).toFixed(2);\n\t\t\tconst probe = report\n\t\t\t\t? `probed ${report.at.slice(0, 10)}: digest ${report.report.digest?.succeeded ?? \"?\"}/${report.report.digest?.total ?? \"?\"}, tool-calls ${report.report.toolCall.succeeded}/${report.report.toolCall.total}${report.report.tokensPerSecond ? `, ~${report.report.tokensPerSecond} tok/s` : \"\"}`\n\t\t\t\t: `unprobed — run /fitness ollama/${model.name}`;\n\t\t\treturn ` - ${model.name} (${gb} GB) · ${probe}`;\n\t\t}),\n\t\t\"Commands: /models add <ref> · /models remove <ref> confirm · /models stop\",\n\t];\n\tfor (const line of lines) host.showStatus(line);\n}\n\nexport async function addLocalModel(host: LocalModelHost, pullRef: string, preselectRole?: FitnessRole): Promise<void> {\n\tif (!(await ensureLocalServer(host))) return;\n\thost.showStatus(`Pulling ${pullRef}… (weights land in the server's model storage)`);\n\tlet lastShown = 0;\n\tconst pulled = await host.localRuntime.pull(pullRef, (progress) => {\n\t\tconst now = Date.now();\n\t\tif (now - lastShown > 2000) {\n\t\t\tlastShown = now;\n\t\t\thost.showStatus(` ${pullRef}: ${progress}`);\n\t\t}\n\t});\n\tif (!pulled.ok) {\n\t\thost.showStatus(`Pull failed: ${pulled.error}`);\n\t\treturn;\n\t}\n\tconst registration = registerLocalModel({\n\t\tagentDir: getAgentDir(),\n\t\tref: pullRef,\n\t\tbaseUrl: host.localRuntime.baseUrl,\n\t});\n\tif (!registration.ok) {\n\t\thost.showStatus(`Pulled, but not auto-registered: ${registration.reason}`);\n\t\tif (registration.manualSnippet) {\n\t\t\thost.showStatus(`Add this to ${registration.modelsJsonPath} yourself:\\n${registration.manualSnippet}`);\n\t\t}\n\t\treturn;\n\t}\n\thost.session.modelRegistry.refresh();\n\thost.showStatus(`${pullRef} installed and registered as ollama/${pullRef}. Probing fitness…`);\n\tawait runFitnessAndAssign(host, `ollama/${pullRef}`, preselectRole);\n}\n\nexport async function removeLocalModel(host: LocalModelHost, ref: string, confirmed: boolean): Promise<void> {\n\tconst status = await host.localRuntime.detect();\n\tif (!status.serverUp) {\n\t\thost.showStatus(\"Local server not running — start it (any /models action) before removing.\");\n\t\treturn;\n\t}\n\tconst models = await host.localRuntime.list();\n\tconst target = models.find((model) => matchesInstalledLocalModel(ref, model.name));\n\tif (!target) {\n\t\thost.showStatus(\n\t\t\t`${ref} is not installed. Installed: ${models.map((model) => model.name).join(\", \") || \"(none)\"}`,\n\t\t);\n\t\treturn;\n\t}\n\tif (!confirmed) {\n\t\t// EXPLICIT USER ACTION ONLY: full disclosure, then require the confirm token.\n\t\tconst gb = (target.sizeBytes / 1e9).toFixed(2);\n\t\thost.showStatus(\n\t\t\t[\n\t\t\t\t`Removing ${ref} will delete:`,\n\t\t\t\t` - model weights (${gb} GB) from ${status.managedByPi ? status.ownedModelsDir : \"the system server's storage\"}`,\n\t\t\t\t` - the ollama/${ref} entry in models.json`,\n\t\t\t\t` - its cached fitness report for this host`,\n\t\t\t\t`Run: /models remove ${ref} confirm`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\treturn;\n\t}\n\tconst removed = await host.localRuntime.remove(ref);\n\tif (!removed.ok) {\n\t\thost.showStatus(`Remove failed: ${removed.error}`);\n\t\treturn;\n\t}\n\tunregisterLocalModel({ agentDir: getAgentDir(), ref });\n\tFitnessStore.forAgentDir(getAgentDir()).remove(`ollama/${ref}`);\n\thost.session.modelRegistry.refresh();\n\thost.showStatus(`${ref} removed: weights deleted, registration and fitness report dropped.`);\n}\n\n/** /fitness with no args: pick a model from the configured registry, probe it, assign a role. */\n/** Pick a validated suggestion → install it → probe on this host → land its shaped role. */\nexport function showModelSuggestionSelector(host: LocalModelHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSuggestionSelectorComponent(\n\t\t\tDEFAULT_MODEL_SUGGESTIONS,\n\t\t\tasync (suggestion) => {\n\t\t\t\tdone();\n\t\t\t\t// The shaped role rides along so the post-probe selector lands on it pre-selected;\n\t\t\t\t// non-tool-callers carry curator/judge/none, never executor, so this can't footgun.\n\t\t\t\tawait addLocalModel(host, suggestion.pullRef, suggestion.assignRole);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showFitnessModelSelector(host: LocalModelHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSelectorComponent(\n\t\t\thost.ui,\n\t\t\thost.session.model,\n\t\t\thost.settingsManager,\n\t\t\thost.session.modelRegistry,\n\t\t\thost.session.scopedModels,\n\t\t\tasync (model) => {\n\t\t\t\tdone();\n\t\t\t\tawait runFitnessAndAssign(host, `${model.provider}/${model.id}`);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\n/** Resolve the routed model's supported thinking levels for a fitness-assignment flow, with a safe fallback. */\nfunction getModelThinkingLevels(modelRegistry: ModelRegistry | undefined, modelRef: string): ThinkingLevel[] {\n\tif (!modelRegistry) {\n\t\treturn [...MODEL_ROUTER_THINKING_FALLBACK_LEVELS];\n\t}\n\tconst resolved = resolveCliModel({ cliModel: modelRef, modelRegistry });\n\tif (!resolved.model) {\n\t\treturn [...MODEL_ROUTER_THINKING_FALLBACK_LEVELS];\n\t}\n\treturn getSupportedThinkingLevels(resolved.model);\n}\n\nfunction getModelRouterThinkingField(role: FitnessRole): ModelRouterThinkingField | undefined {\n\tif (role === \"router-cheap\") {\n\t\treturn \"cheapThinking\";\n\t}\n\tif (role === \"router-medium\") {\n\t\treturn \"mediumThinking\";\n\t}\n\tif (role === \"router-expensive\") {\n\t\treturn \"expensiveThinking\";\n\t}\n\tif (role === \"executor\") {\n\t\treturn \"executorThinking\";\n\t}\n\tif (role === \"judge\") {\n\t\treturn \"judgeThinking\";\n\t}\n\treturn undefined;\n}\n\nfunction promptForModelRouterThinking(host: RunFitnessHost, modelRef: string, role: FitnessRole): void {\n\tconst thinkingField = getModelRouterThinkingField(role);\n\tif (!thinkingField) {\n\t\treturn;\n\t}\n\n\tconst modelRegistry = host.session.modelRegistry;\n\tconst availableLevels = getModelThinkingLevels(modelRegistry, modelRef);\n\tconst settings = host.settingsManager.getModelRouterSettings();\n\tconst configuredThinking = settings[thinkingField];\n\tconst currentThinking =\n\t\tconfiguredThinking && availableLevels.includes(configuredThinking)\n\t\t\t? configuredThinking\n\t\t\t: MODEL_ROUTER_THINKING_INHERIT_VALUE;\n\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelRouterThinkingSelectorComponent(\n\t\t\tcurrentThinking,\n\t\t\tavailableLevels,\n\t\t\t(level) => {\n\t\t\t\tdone();\n\t\t\t\thost.settingsManager.setModelRouterSettings({\n\t\t\t\t\t...settings,\n\t\t\t\t\t[thinkingField]: level === MODEL_ROUTER_THINKING_INHERIT_VALUE ? undefined : level,\n\t\t\t\t});\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\n/** Probe a model's fitness, show the report, then offer one-step role assignment. When the model\n * came from a validated suggestion, `preselectRole` lands its shaped role already highlighted. */\nexport async function runFitnessAndAssign(\n\thost: RunFitnessHost,\n\tmodelRef: string,\n\tpreselectRole?: FitnessRole,\n): Promise<void> {\n\thost.showStatus(`Model fitness probe running on ${modelRef}… (6 surfaces; local models may take a few minutes)`);\n\ttry {\n\t\tconst outcome = await host.session.runModelFitness({ model: modelRef });\n\t\tif (!outcome.started) {\n\t\t\thost.showStatus(`Model fitness skipped: ${outcome.skipReason}`);\n\t\t\treturn;\n\t\t}\n\t\thost.chatContainer.addChild(new Spacer(1));\n\t\thost.chatContainer.addChild(new Text(formatModelFitnessReport(outcome.model, outcome.report), 1, 0));\n\t\thost.ui.requestRender();\n\t\t// Validate-before-load: zero successes on every probed surface means the model cannot\n\t\t// drive any of the harness's subagent contracts on this host — refuse adoption instead\n\t\t// of landing a role selector the user might reflexively confirm (this is the reported\n\t\t// bug: a 0/3-everywhere model still got set as judge model and saved to Model Router).\n\t\tif (isProbeAllFailed(outcome.report)) {\n\t\t\thost.showStatus(\n\t\t\t\t`${outcome.model} failed the fitness probe on all surfaces — not configured. Use /model to set it manually if you accept the risk.`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\thost.showSelector((done) => {\n\t\t\tconst selector = new FitnessRoleSelectorComponent(\n\t\t\t\toutcome.model,\n\t\t\t\t(role) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tif (role === \"scout\") {\n\t\t\t\t\t\tconst verdict = evaluateSurfaceFitness(\"scout_auto\", outcome.report);\n\t\t\t\t\t\tif (!verdict.fit) {\n\t\t\t\t\t\t\tconst reason = verdict.reason === \"lane_failed\" ? `failed ${verdict.lane}` : \"was not probed\";\n\t\t\t\t\t\t\thost.showStatus(\n\t\t\t\t\t\t\t\t`${outcome.model} not assigned as scout: ${reason} on the scout_auto fitness exam. Use the docs/scout.md Modelfile recipe, then rerun /fitness.`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tassignFitnessRole(host, outcome.model, role);\n\t\t\t\t\tpromptForModelRouterThinking(host, outcome.model, role);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t},\n\t\t\t\tpreselectRole,\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\n/** Persist a role assignment from the post-probe selector into the matching settings. */\nexport function assignFitnessRole(host: AssignRoleHost, modelRef: string, role: FitnessRole): void {\n\tif (role === \"none\") {\n\t\thost.showStatus(`Fitness result for ${modelRef} saved. Assign a role later from /settings.`);\n\t\treturn;\n\t}\n\tif (role === \"curator\") {\n\t\tconst current = host.settingsManager.getContextCurationSettings();\n\t\thost.settingsManager.setContextCurationSettings({ ...current, enabled: true, model: modelRef });\n\t\thost.showStatus(`Context curation enabled with ${modelRef} as the curator.`);\n\t\treturn;\n\t}\n\tif (role === \"executor\") {\n\t\tconst router = host.settingsManager.getModelRouterSettings();\n\t\thost.settingsManager.setModelRouterSettings({ ...router, executorModel: modelRef });\n\t\tconst hint = router.enabled ? \"\" : \" Model router is currently disabled — enable it in /settings → Model Router.\";\n\t\thost.showStatus(`${modelRef} set as the toolkit executor (direct Level-0 hits route to it).${hint}`);\n\t\treturn;\n\t}\n\tif (role === \"scout\") {\n\t\thost.settingsManager.setScoutSettings({ enabled: true, model: modelRef });\n\t\thost.showStatus(`${modelRef} set as the repository scout (context_scout enabled).`);\n\t\treturn;\n\t}\n\tconst router = host.settingsManager.getModelRouterSettings();\n\tconst field =\n\t\trole === \"router-cheap\"\n\t\t\t? \"cheapModel\"\n\t\t\t: role === \"router-medium\"\n\t\t\t\t? \"mediumModel\"\n\t\t\t\t: role === \"router-expensive\"\n\t\t\t\t\t? \"expensiveModel\"\n\t\t\t\t\t: role === \"judge\"\n\t\t\t\t\t\t? \"judgeModel\"\n\t\t\t\t\t\t: \"learningModel\";\n\thost.settingsManager.setModelRouterSettings({ ...router, [field]: modelRef });\n\tconst hint = router.enabled ? \"\" : \" Model router is currently disabled — enable it in /settings → Model Router.\";\n\thost.showStatus(`${modelRef} set as ${role.replace(\"router-\", \"router \")} model.${hint}`);\n}\n"]}
@@ -14,6 +14,7 @@ import { getSupportedThinkingLevels } from "@caupulican/pi-ai";
14
14
  import { Container, SelectList, Spacer, Text, } from "@caupulican/pi-tui";
15
15
  import { getAgentDir } from "../../config.js";
16
16
  import { resolveCliModel } from "../../core/model-resolver.js";
17
+ import { evaluateSurfaceFitness } from "../../core/model-router/fitness-gate.js";
17
18
  import { DEFAULT_MODEL_SUGGESTIONS } from "../../core/models/default-model-suggestions.js";
18
19
  import { FitnessStore } from "../../core/models/fitness-store.js";
19
20
  import { registerLocalModel, unregisterLocalModel } from "../../core/models/local-registration.js";
@@ -353,6 +354,14 @@ export async function runFitnessAndAssign(host, modelRef, preselectRole) {
353
354
  host.showSelector((done) => {
354
355
  const selector = new FitnessRoleSelectorComponent(outcome.model, (role) => {
355
356
  done();
357
+ if (role === "scout") {
358
+ const verdict = evaluateSurfaceFitness("scout_auto", outcome.report);
359
+ if (!verdict.fit) {
360
+ const reason = verdict.reason === "lane_failed" ? `failed ${verdict.lane}` : "was not probed";
361
+ host.showStatus(`${outcome.model} not assigned as scout: ${reason} on the scout_auto fitness exam. Use the docs/scout.md Modelfile recipe, then rerun /fitness.`);
362
+ return;
363
+ }
364
+ }
356
365
  assignFitnessRole(host, outcome.model, role);
357
366
  promptForModelRouterThinking(host, outcome.model, role);
358
367
  }, () => {
@@ -385,6 +394,11 @@ export function assignFitnessRole(host, modelRef, role) {
385
394
  host.showStatus(`${modelRef} set as the toolkit executor (direct Level-0 hits route to it).${hint}`);
386
395
  return;
387
396
  }
397
+ if (role === "scout") {
398
+ host.settingsManager.setScoutSettings({ enabled: true, model: modelRef });
399
+ host.showStatus(`${modelRef} set as the repository scout (context_scout enabled).`);
400
+ return;
401
+ }
388
402
  const router = host.settingsManager.getModelRouterSettings();
389
403
  const field = role === "router-cheap"
390
404
  ? "cheapModel"
@@ -1 +1 @@
1
- {"version":3,"file":"local-model-commands.js","sourceRoot":"","sources":["../../../src/modes/interactive/local-model-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/models` local-model lifecycle and `/fitness` probe/role-assignment flows\n * extracted from interactive-mode.\n *\n * `/models` is a USER-invoked local-model manager (list/add/remove/stop, never a\n * model-invokable tool); the fitness helpers probe a model on the current host\n * and land a shaped role. The server/selector flows share a `LocalModelHost`\n * seam, while `runFitnessAndAssign`/`assignFitnessRole` take narrow host shapes\n * matching their prototype-driven behaviour tests (fitness-probe-gate,\n * fitness-role-assignment), which keep exercising interactive-mode's thin\n * wrappers unchanged.\n */\n\nimport type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { getSupportedThinkingLevels } from \"@caupulican/pi-ai\";\nimport {\n\ttype Component,\n\tContainer,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type { ModelRegistry } from \"../../core/model-registry.ts\";\nimport { resolveCliModel } from \"../../core/model-resolver.ts\";\nimport { DEFAULT_MODEL_SUGGESTIONS } from \"../../core/models/default-model-suggestions.ts\";\nimport { FitnessStore } from \"../../core/models/fitness-store.ts\";\nimport { registerLocalModel, unregisterLocalModel } from \"../../core/models/local-registration.ts\";\nimport type { OllamaRuntime } from \"../../core/models/local-runtime.ts\";\nimport { matchesInstalledLocalModel, normalizeModelSource } from \"../../core/models/model-ref.ts\";\nimport { formatModelFitnessReport, isProbeAllFailed } from \"../../core/research/model-fitness.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { DynamicBorder } from \"./components/dynamic-border.ts\";\nimport { type FitnessRole, FitnessRoleSelectorComponent } from \"./components/fitness-role-selector.ts\";\nimport { ModelSelectorComponent } from \"./components/model-selector.ts\";\nimport { ModelSuggestionSelectorComponent } from \"./components/model-suggestion-selector.ts\";\nimport { getSelectListTheme } from \"./theme/theme.ts\";\n\ntype SelectorFactory = (done: () => void) => { component: Component; focus: Component };\n\nconst MODEL_ROUTER_THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst MODEL_ROUTER_THINKING_INHERIT_VALUE = \"__inherit_model_router_thinking__\";\nconst MODEL_ROUTER_THINKING_INHERIT_LABEL = \"(inherit)\";\n\nconst THINKING_LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\nconst MODEL_ROUTER_THINKING_FALLBACK_LEVELS: readonly ThinkingLevel[] = [\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n];\n\ntype RouterThinkingLevel = ThinkingLevel | typeof MODEL_ROUTER_THINKING_INHERIT_VALUE;\ntype ModelRouterThinkingField =\n\t| \"cheapThinking\"\n\t| \"mediumThinking\"\n\t| \"expensiveThinking\"\n\t| \"executorThinking\"\n\t| \"judgeThinking\";\n\n/** Narrow seam for persisting a probed model's role — matches the fitness-role test. */\nexport interface AssignRoleHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n}\n\n/** Seam for the fitness probe + role-selector flow — matches the fitness-probe-gate test. */\nexport interface RunFitnessHost {\n\treadonly session: Pick<AgentSession, \"runModelFitness\"> & { modelRegistry?: ModelRegistry };\n\treadonly settingsManager: SettingsManager;\n\treadonly chatContainer: Container;\n\treadonly ui: TUI;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowSelector(create: SelectorFactory): void;\n}\n\n/** Full seam for the `/models` server/selector flows. */\nexport interface LocalModelHost {\n\treadonly localRuntime: OllamaRuntime;\n\treadonly session: AgentSession;\n\treadonly settingsManager: SettingsManager;\n\treadonly ui: TUI;\n\treadonly chatContainer: Container;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowSelector(create: SelectorFactory): void;\n}\n\nclass ModelRouterThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: RouterThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: RouterThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingOptions: SelectItem[] = [\n\t\t\t{\n\t\t\t\tvalue: MODEL_ROUTER_THINKING_INHERIT_VALUE,\n\t\t\t\tlabel: MODEL_ROUTER_THINKING_INHERIT_LABEL,\n\t\t\t\tdescription: \"Use current session default for this tier.\",\n\t\t\t},\n\t\t\t...availableLevels.map((level) => ({\n\t\t\t\tvalue: level,\n\t\t\t\tlabel: level,\n\t\t\t\tdescription: THINKING_LEVEL_DESCRIPTIONS[level],\n\t\t\t})),\n\t\t];\n\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingOptions,\n\t\t\tthinkingOptions.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tMODEL_ROUTER_THINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\tconst index = thinkingOptions.findIndex((item) => item.value === currentLevel);\n\t\tif (index !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(index);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as RouterThinkingLevel);\n\t\t};\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n}\n/**\n * /models — USER-invoked local model lifecycle (never a model-invokable tool):\n * list/add/remove/stop per local-model-lifecycle-design.md. Removal is explicit-only with\n * full disclosure; a pasted install command is parsed for its ref, never executed.\n */\nexport async function handleModelsCommand(host: LocalModelHost, argsText: string): Promise<void> {\n\tconst [action = \"list\", ...rest] = argsText.split(/\\s+/).filter(Boolean);\n\ttry {\n\t\tif (action === \"suggest\" || action === \"suggestions\") {\n\t\t\tshowModelSuggestionSelector(host);\n\t\t\treturn;\n\t\t}\n\t\tif (action === \"stop\") {\n\t\t\tconst stopped = host.localRuntime.stop();\n\t\t\thost.showStatus(\n\t\t\t\tstopped.stopped\n\t\t\t\t\t? \"Pi-managed local model server stopped (models remain installed).\"\n\t\t\t\t\t: \"No pi-managed server running (a system server, if any, is not pi's to stop).\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (action === \"add\") {\n\t\t\tconst rawRef = rest.join(\" \");\n\t\t\tif (!rawRef) {\n\t\t\t\thost.showStatus(\n\t\t\t\t\t\"Usage: /models add <ollama-tag | hf.co/org/repo[:quant] | huggingface URL | pasted install command>\",\n\t\t\t\t);\n\t\t\t\thost.showStatus(\"Or start from a validated suggestion: /models suggest\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst source = normalizeModelSource(rawRef);\n\t\t\tif (source.type === \"rejected\") {\n\t\t\t\thost.showStatus(`Not added: ${source.reason}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (source.type === \"api\") {\n\t\t\t\thost.showStatus(\n\t\t\t\t\t`${source.ref} is an API model — nothing to install. Configure auth for the provider, then probe it with /fitness ${source.ref}.`,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait addLocalModel(host, source.pullRef);\n\t\t\treturn;\n\t\t}\n\n\t\tif (action === \"remove\") {\n\t\t\tconst ref = rest[0];\n\t\t\tconst confirmed = rest[1] === \"confirm\";\n\t\t\tif (!ref) {\n\t\t\t\thost.showStatus(\"Usage: /models remove <ref> confirm\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait removeLocalModel(host, ref, confirmed);\n\t\t\treturn;\n\t\t}\n\n\t\tawait listLocalModels(host);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function ensureLocalServer(host: LocalModelHost): Promise<boolean> {\n\tconst status = await host.localRuntime.detect();\n\tif (status.serverUp) return true;\n\tif (!status.binaryPath) {\n\t\tfor (const line of host.localRuntime.installGuide()) host.showStatus(line);\n\t\treturn false;\n\t}\n\thost.showStatus(\n\t\t`Starting local model server (${status.binarySource} binary, owned storage: ${status.ownedModelsDir})…`,\n\t);\n\tconst started = await host.localRuntime.start();\n\tif (!started.started) {\n\t\thost.showStatus(`Could not start the local server: ${started.reason}`);\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nexport async function listLocalModels(host: LocalModelHost): Promise<void> {\n\tconst status = await host.localRuntime.detect();\n\tif (!status.serverUp) {\n\t\tif (!status.binaryPath) {\n\t\t\tfor (const line of host.localRuntime.installGuide()) host.showStatus(line);\n\t\t\treturn;\n\t\t}\n\t\thost.showStatus(\n\t\t\t`Local server not running (binary: ${status.binarySource}). /models add starts it on demand; /fitness probes registered models.`,\n\t\t);\n\t\treturn;\n\t}\n\tconst models = await host.localRuntime.list();\n\tconst fitness = FitnessStore.forAgentDir(getAgentDir()).getForHost();\n\tconst lines = [\n\t\t`Local models (${status.managedByPi ? `pi-managed server, storage: ${status.ownedModelsDir}` : \"system server — storage owned by the system daemon\"}):`,\n\t\t...(models.length === 0\n\t\t\t? [\" (none installed — /models add <ref>, or /models suggest for a validated roster)\"]\n\t\t\t: []),\n\t\t...models.map((model) => {\n\t\t\tconst report = fitness.find((entry) => entry.model === `ollama/${model.name}`);\n\t\t\tconst gb = (model.sizeBytes / 1e9).toFixed(2);\n\t\t\tconst probe = report\n\t\t\t\t? `probed ${report.at.slice(0, 10)}: digest ${report.report.digest?.succeeded ?? \"?\"}/${report.report.digest?.total ?? \"?\"}, tool-calls ${report.report.toolCall.succeeded}/${report.report.toolCall.total}${report.report.tokensPerSecond ? `, ~${report.report.tokensPerSecond} tok/s` : \"\"}`\n\t\t\t\t: `unprobed — run /fitness ollama/${model.name}`;\n\t\t\treturn ` - ${model.name} (${gb} GB) · ${probe}`;\n\t\t}),\n\t\t\"Commands: /models add <ref> · /models remove <ref> confirm · /models stop\",\n\t];\n\tfor (const line of lines) host.showStatus(line);\n}\n\nexport async function addLocalModel(host: LocalModelHost, pullRef: string, preselectRole?: FitnessRole): Promise<void> {\n\tif (!(await ensureLocalServer(host))) return;\n\thost.showStatus(`Pulling ${pullRef}… (weights land in the server's model storage)`);\n\tlet lastShown = 0;\n\tconst pulled = await host.localRuntime.pull(pullRef, (progress) => {\n\t\tconst now = Date.now();\n\t\tif (now - lastShown > 2000) {\n\t\t\tlastShown = now;\n\t\t\thost.showStatus(` ${pullRef}: ${progress}`);\n\t\t}\n\t});\n\tif (!pulled.ok) {\n\t\thost.showStatus(`Pull failed: ${pulled.error}`);\n\t\treturn;\n\t}\n\tconst registration = registerLocalModel({\n\t\tagentDir: getAgentDir(),\n\t\tref: pullRef,\n\t\tbaseUrl: host.localRuntime.baseUrl,\n\t});\n\tif (!registration.ok) {\n\t\thost.showStatus(`Pulled, but not auto-registered: ${registration.reason}`);\n\t\tif (registration.manualSnippet) {\n\t\t\thost.showStatus(`Add this to ${registration.modelsJsonPath} yourself:\\n${registration.manualSnippet}`);\n\t\t}\n\t\treturn;\n\t}\n\thost.session.modelRegistry.refresh();\n\thost.showStatus(`${pullRef} installed and registered as ollama/${pullRef}. Probing fitness…`);\n\tawait runFitnessAndAssign(host, `ollama/${pullRef}`, preselectRole);\n}\n\nexport async function removeLocalModel(host: LocalModelHost, ref: string, confirmed: boolean): Promise<void> {\n\tconst status = await host.localRuntime.detect();\n\tif (!status.serverUp) {\n\t\thost.showStatus(\"Local server not running — start it (any /models action) before removing.\");\n\t\treturn;\n\t}\n\tconst models = await host.localRuntime.list();\n\tconst target = models.find((model) => matchesInstalledLocalModel(ref, model.name));\n\tif (!target) {\n\t\thost.showStatus(\n\t\t\t`${ref} is not installed. Installed: ${models.map((model) => model.name).join(\", \") || \"(none)\"}`,\n\t\t);\n\t\treturn;\n\t}\n\tif (!confirmed) {\n\t\t// EXPLICIT USER ACTION ONLY: full disclosure, then require the confirm token.\n\t\tconst gb = (target.sizeBytes / 1e9).toFixed(2);\n\t\thost.showStatus(\n\t\t\t[\n\t\t\t\t`Removing ${ref} will delete:`,\n\t\t\t\t` - model weights (${gb} GB) from ${status.managedByPi ? status.ownedModelsDir : \"the system server's storage\"}`,\n\t\t\t\t` - the ollama/${ref} entry in models.json`,\n\t\t\t\t` - its cached fitness report for this host`,\n\t\t\t\t`Run: /models remove ${ref} confirm`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\treturn;\n\t}\n\tconst removed = await host.localRuntime.remove(ref);\n\tif (!removed.ok) {\n\t\thost.showStatus(`Remove failed: ${removed.error}`);\n\t\treturn;\n\t}\n\tunregisterLocalModel({ agentDir: getAgentDir(), ref });\n\tFitnessStore.forAgentDir(getAgentDir()).remove(`ollama/${ref}`);\n\thost.session.modelRegistry.refresh();\n\thost.showStatus(`${ref} removed: weights deleted, registration and fitness report dropped.`);\n}\n\n/** /fitness with no args: pick a model from the configured registry, probe it, assign a role. */\n/** Pick a validated suggestion → install it → probe on this host → land its shaped role. */\nexport function showModelSuggestionSelector(host: LocalModelHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSuggestionSelectorComponent(\n\t\t\tDEFAULT_MODEL_SUGGESTIONS,\n\t\t\tasync (suggestion) => {\n\t\t\t\tdone();\n\t\t\t\t// The shaped role rides along so the post-probe selector lands on it pre-selected;\n\t\t\t\t// non-tool-callers carry curator/judge/none, never executor, so this can't footgun.\n\t\t\t\tawait addLocalModel(host, suggestion.pullRef, suggestion.assignRole);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showFitnessModelSelector(host: LocalModelHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSelectorComponent(\n\t\t\thost.ui,\n\t\t\thost.session.model,\n\t\t\thost.settingsManager,\n\t\t\thost.session.modelRegistry,\n\t\t\thost.session.scopedModels,\n\t\t\tasync (model) => {\n\t\t\t\tdone();\n\t\t\t\tawait runFitnessAndAssign(host, `${model.provider}/${model.id}`);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\n/** Resolve the routed model's supported thinking levels for a fitness-assignment flow, with a safe fallback. */\nfunction getModelThinkingLevels(modelRegistry: ModelRegistry | undefined, modelRef: string): ThinkingLevel[] {\n\tif (!modelRegistry) {\n\t\treturn [...MODEL_ROUTER_THINKING_FALLBACK_LEVELS];\n\t}\n\tconst resolved = resolveCliModel({ cliModel: modelRef, modelRegistry });\n\tif (!resolved.model) {\n\t\treturn [...MODEL_ROUTER_THINKING_FALLBACK_LEVELS];\n\t}\n\treturn getSupportedThinkingLevels(resolved.model);\n}\n\nfunction getModelRouterThinkingField(role: FitnessRole): ModelRouterThinkingField | undefined {\n\tif (role === \"router-cheap\") {\n\t\treturn \"cheapThinking\";\n\t}\n\tif (role === \"router-medium\") {\n\t\treturn \"mediumThinking\";\n\t}\n\tif (role === \"router-expensive\") {\n\t\treturn \"expensiveThinking\";\n\t}\n\tif (role === \"executor\") {\n\t\treturn \"executorThinking\";\n\t}\n\tif (role === \"judge\") {\n\t\treturn \"judgeThinking\";\n\t}\n\treturn undefined;\n}\n\nfunction promptForModelRouterThinking(host: RunFitnessHost, modelRef: string, role: FitnessRole): void {\n\tconst thinkingField = getModelRouterThinkingField(role);\n\tif (!thinkingField) {\n\t\treturn;\n\t}\n\n\tconst modelRegistry = host.session.modelRegistry;\n\tconst availableLevels = getModelThinkingLevels(modelRegistry, modelRef);\n\tconst settings = host.settingsManager.getModelRouterSettings();\n\tconst configuredThinking = settings[thinkingField];\n\tconst currentThinking =\n\t\tconfiguredThinking && availableLevels.includes(configuredThinking)\n\t\t\t? configuredThinking\n\t\t\t: MODEL_ROUTER_THINKING_INHERIT_VALUE;\n\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelRouterThinkingSelectorComponent(\n\t\t\tcurrentThinking,\n\t\t\tavailableLevels,\n\t\t\t(level) => {\n\t\t\t\tdone();\n\t\t\t\thost.settingsManager.setModelRouterSettings({\n\t\t\t\t\t...settings,\n\t\t\t\t\t[thinkingField]: level === MODEL_ROUTER_THINKING_INHERIT_VALUE ? undefined : level,\n\t\t\t\t});\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\n/** Probe a model's fitness, show the report, then offer one-step role assignment. When the model\n * came from a validated suggestion, `preselectRole` lands its shaped role already highlighted. */\nexport async function runFitnessAndAssign(\n\thost: RunFitnessHost,\n\tmodelRef: string,\n\tpreselectRole?: FitnessRole,\n): Promise<void> {\n\thost.showStatus(`Model fitness probe running on ${modelRef}… (6 surfaces; local models may take a few minutes)`);\n\ttry {\n\t\tconst outcome = await host.session.runModelFitness({ model: modelRef });\n\t\tif (!outcome.started) {\n\t\t\thost.showStatus(`Model fitness skipped: ${outcome.skipReason}`);\n\t\t\treturn;\n\t\t}\n\t\thost.chatContainer.addChild(new Spacer(1));\n\t\thost.chatContainer.addChild(new Text(formatModelFitnessReport(outcome.model, outcome.report), 1, 0));\n\t\thost.ui.requestRender();\n\t\t// Validate-before-load: zero successes on every probed surface means the model cannot\n\t\t// drive any of the harness's subagent contracts on this host — refuse adoption instead\n\t\t// of landing a role selector the user might reflexively confirm (this is the reported\n\t\t// bug: a 0/3-everywhere model still got set as judge model and saved to Model Router).\n\t\tif (isProbeAllFailed(outcome.report)) {\n\t\t\thost.showStatus(\n\t\t\t\t`${outcome.model} failed the fitness probe on all surfaces — not configured. Use /model to set it manually if you accept the risk.`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\thost.showSelector((done) => {\n\t\t\tconst selector = new FitnessRoleSelectorComponent(\n\t\t\t\toutcome.model,\n\t\t\t\t(role) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tassignFitnessRole(host, outcome.model, role);\n\t\t\t\t\tpromptForModelRouterThinking(host, outcome.model, role);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t},\n\t\t\t\tpreselectRole,\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\n/** Persist a role assignment from the post-probe selector into the matching settings. */\nexport function assignFitnessRole(host: AssignRoleHost, modelRef: string, role: FitnessRole): void {\n\tif (role === \"none\") {\n\t\thost.showStatus(`Fitness result for ${modelRef} saved. Assign a role later from /settings.`);\n\t\treturn;\n\t}\n\tif (role === \"curator\") {\n\t\tconst current = host.settingsManager.getContextCurationSettings();\n\t\thost.settingsManager.setContextCurationSettings({ ...current, enabled: true, model: modelRef });\n\t\thost.showStatus(`Context curation enabled with ${modelRef} as the curator.`);\n\t\treturn;\n\t}\n\tif (role === \"executor\") {\n\t\tconst router = host.settingsManager.getModelRouterSettings();\n\t\thost.settingsManager.setModelRouterSettings({ ...router, executorModel: modelRef });\n\t\tconst hint = router.enabled ? \"\" : \" Model router is currently disabled — enable it in /settings → Model Router.\";\n\t\thost.showStatus(`${modelRef} set as the toolkit executor (direct Level-0 hits route to it).${hint}`);\n\t\treturn;\n\t}\n\tconst router = host.settingsManager.getModelRouterSettings();\n\tconst field =\n\t\trole === \"router-cheap\"\n\t\t\t? \"cheapModel\"\n\t\t\t: role === \"router-medium\"\n\t\t\t\t? \"mediumModel\"\n\t\t\t\t: role === \"router-expensive\"\n\t\t\t\t\t? \"expensiveModel\"\n\t\t\t\t\t: role === \"judge\"\n\t\t\t\t\t\t? \"judgeModel\"\n\t\t\t\t\t\t: \"learningModel\";\n\thost.settingsManager.setModelRouterSettings({ ...router, [field]: modelRef });\n\tconst hint = router.enabled ? \"\" : \" Model router is currently disabled — enable it in /settings → Model Router.\";\n\thost.showStatus(`${modelRef} set as ${role.replace(\"router-\", \"router \")} model.${hint}`);\n}\n"]}
1
+ {"version":3,"file":"local-model-commands.js","sourceRoot":"","sources":["../../../src/modes/interactive/local-model-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAEN,SAAS,EAET,UAAU,EAEV,MAAM,EACN,IAAI,GAEJ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gDAAgD,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AAEnG,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAClG,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAElG,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAoB,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AACvG,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,gCAAgC,EAAE,MAAM,2CAA2C,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAItD,MAAM,wCAAwC,GAA4B;IACzE,qBAAqB,EAAE,EAAE;IACzB,qBAAqB,EAAE,EAAE;CACzB,CAAC;AAEF,MAAM,mCAAmC,GAAG,mCAAmC,CAAC;AAChF,MAAM,mCAAmC,GAAG,WAAW,CAAC;AAExD,MAAM,2BAA2B,GAAkC;IAClE,GAAG,EAAE,cAAc;IACnB,OAAO,EAAE,mCAAmC;IAC5C,GAAG,EAAE,8BAA8B;IACnC,MAAM,EAAE,iCAAiC;IACzC,IAAI,EAAE,8BAA8B;IACpC,KAAK,EAAE,iCAAiC;CACxC,CAAC;AAEF,MAAM,qCAAqC,GAA6B;IACvE,KAAK;IACL,SAAS;IACT,KAAK;IACL,QAAQ;IACR,MAAM;IACN,OAAO;CACP,CAAC;AAuCF,MAAM,oCAAqC,SAAQ,SAAS;IACnD,UAAU,CAAa;IAE/B,YACC,YAAiC,EACjC,eAAgC,EAChC,QAA8C,EAC9C,QAAoB,EACnB;QACD,KAAK,EAAE,CAAC;QAER,MAAM,eAAe,GAAiB;YACrC;gBACC,KAAK,EAAE,mCAAmC;gBAC1C,KAAK,EAAE,mCAAmC;gBAC1C,WAAW,EAAE,4CAA4C;aACzD;YACD,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAClC,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,KAAK;gBACZ,WAAW,EAAE,2BAA2B,CAAC,KAAK,CAAC;aAC/C,CAAC,CAAC;SACH,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;QAEnC,IAAI,CAAC,UAAU,GAAG,IAAI,UAAU,CAC/B,eAAe,EACf,eAAe,CAAC,MAAM,EACtB,kBAAkB,EAAE,EACpB,wCAAwC,CACxC,CAAC;QAEF,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC;QAC/E,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACzC,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,QAAQ,CAAC,IAAI,CAAC,KAA4B,CAAC,CAAC;QAAA,CAC5C,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;YAChC,QAAQ,EAAE,CAAC;QAAA,CACX,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;IAAA,CACnC;CACD;AACD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAoB,EAAE,QAAgB,EAAiB;IAChG,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzE,IAAI,CAAC;QACJ,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;YACtD,2BAA2B,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO;QACR,CAAC;QACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;YACzC,IAAI,CAAC,UAAU,CACd,OAAO,CAAC,OAAO;gBACd,CAAC,CAAC,kEAAkE;gBACpE,CAAC,CAAC,8EAA8E,CACjF,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YACtB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACb,IAAI,CAAC,UAAU,CACd,qGAAqG,CACrG,CAAC;gBACF,IAAI,CAAC,UAAU,CAAC,uDAAuD,CAAC,CAAC;gBACzE,OAAO;YACR,CAAC;YACD,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAChC,IAAI,CAAC,UAAU,CAAC,cAAc,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC/C,OAAO;YACR,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;gBAC3B,IAAI,CAAC,UAAU,CACd,GAAG,MAAM,CAAC,GAAG,yGAAuG,MAAM,CAAC,GAAG,GAAG,CACjI,CAAC;gBACF,OAAO;YACR,CAAC;YACD,MAAM,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAC1C,OAAO;QACR,CAAC;QAED,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;YACxC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACV,IAAI,CAAC,UAAU,CAAC,qCAAqC,CAAC,CAAC;gBACvD,OAAO;YACR,CAAC;YACD,MAAM,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;YAC7C,OAAO;QACR,CAAC;QAED,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,SAAS,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACxE,CAAC;AAAA,CACD;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAoB,EAAoB;IAC/E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;IAChD,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACxB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC3E,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,UAAU,CACd,gCAAgC,MAAM,CAAC,YAAY,2BAA2B,MAAM,CAAC,cAAc,MAAI,CACvG,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAChD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC,UAAU,CAAC,qCAAqC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAoB,EAAiB;IAC1E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACxB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE;gBAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAC3E,OAAO;QACR,CAAC;QACD,IAAI,CAAC,UAAU,CACd,qCAAqC,MAAM,CAAC,YAAY,wEAAwE,CAChI,CAAC;QACF,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IAC9C,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC;IACrE,MAAM,KAAK,GAAG;QACb,iBAAiB,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,+BAA+B,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,sDAAoD,IAAI;QACvJ,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YACtB,CAAC,CAAC,CAAC,qFAAmF,CAAC;YACvF,CAAC,CAAC,EAAE,CAAC;QACN,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,UAAU,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAC/E,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC9C,MAAM,KAAK,GAAG,MAAM;gBACnB,CAAC,CAAC,UAAU,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,GAAG,gBAAgB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,eAAe,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC/R,CAAC,CAAC,oCAAkC,KAAK,CAAC,IAAI,EAAE,CAAC;YAClD,OAAO,OAAO,KAAK,CAAC,IAAI,KAAK,EAAE,WAAU,KAAK,EAAE,CAAC;QAAA,CACjD,CAAC;QACF,6EAA2E;KAC3E,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAAA,CAChD;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAoB,EAAE,OAAe,EAAE,aAA2B,EAAiB;IACtH,IAAI,CAAC,CAAC,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO;IAC7C,IAAI,CAAC,UAAU,CAAC,WAAW,OAAO,kDAAgD,CAAC,CAAC;IACpF,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;QAClE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,SAAS,GAAG,IAAI,EAAE,CAAC;YAC5B,SAAS,GAAG,GAAG,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,KAAK,OAAO,KAAK,QAAQ,EAAE,CAAC,CAAC;QAC9C,CAAC;IAAA,CACD,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAChB,IAAI,CAAC,UAAU,CAAC,gBAAgB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAChD,OAAO;IACR,CAAC;IACD,MAAM,YAAY,GAAG,kBAAkB,CAAC;QACvC,QAAQ,EAAE,WAAW,EAAE;QACvB,GAAG,EAAE,OAAO;QACZ,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,OAAO;KAClC,CAAC,CAAC;IACH,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;QACtB,IAAI,CAAC,UAAU,CAAC,oCAAoC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;QAC3E,IAAI,YAAY,CAAC,aAAa,EAAE,CAAC;YAChC,IAAI,CAAC,UAAU,CAAC,eAAe,YAAY,CAAC,cAAc,eAAe,YAAY,CAAC,aAAa,EAAE,CAAC,CAAC;QACxG,CAAC;QACD,OAAO;IACR,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,GAAG,OAAO,uCAAuC,OAAO,sBAAoB,CAAC,CAAC;IAC9F,MAAM,mBAAmB,CAAC,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,aAAa,CAAC,CAAC;AAAA,CACpE;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAoB,EAAE,GAAW,EAAE,SAAkB,EAAiB;IAC5G,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,UAAU,CAAC,6EAA2E,CAAC,CAAC;QAC7F,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,0BAA0B,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACnF,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,IAAI,CAAC,UAAU,CACd,GAAG,GAAG,iCAAiC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CACjG,CAAC;QACF,OAAO;IACR,CAAC;IACD,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,8EAA8E;QAC9E,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU,CACd;YACC,YAAY,GAAG,eAAe;YAC9B,sBAAsB,EAAE,aAAa,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,6BAA6B,EAAE;YACjH,kBAAkB,GAAG,uBAAuB;YAC5C,6CAA6C;YAC7C,uBAAuB,GAAG,UAAU;SACpC,CAAC,IAAI,CAAC,IAAI,CAAC,CACZ,CAAC;QACF,OAAO;IACR,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,CAAC,UAAU,CAAC,kBAAkB,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACnD,OAAO;IACR,CAAC;IACD,oBAAoB,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACvD,YAAY,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,qEAAqE,CAAC,CAAC;AAAA,CAC7F;AAED,iGAAiG;AACjG,kGAA4F;AAC5F,MAAM,UAAU,2BAA2B,CAAC,IAAoB,EAAQ;IACvE,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,gCAAgC,CACpD,yBAAyB,EACzB,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;YACrB,IAAI,EAAE,CAAC;YACP,mFAAmF;YACnF,oFAAoF;YACpF,MAAM,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;QAAA,CACrE,EACD,GAAG,EAAE,CAAC;YACL,IAAI,EAAE,CAAC;YACP,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAAA,CACxB,CACD,CAAC;QACF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAA,CAChD,CAAC,CAAC;AAAA,CACH;AAED,MAAM,UAAU,wBAAwB,CAAC,IAAoB,EAAQ;IACpE,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,sBAAsB,CAC1C,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,OAAO,CAAC,KAAK,EAClB,IAAI,CAAC,eAAe,EACpB,IAAI,CAAC,OAAO,CAAC,aAAa,EAC1B,IAAI,CAAC,OAAO,CAAC,YAAY,EACzB,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;YAChB,IAAI,EAAE,CAAC;YACP,MAAM,mBAAmB,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QAAA,CACjE,EACD,GAAG,EAAE,CAAC;YACL,IAAI,EAAE,CAAC;YACP,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAAA,CACxB,CACD,CAAC;QACF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAA,CAChD,CAAC,CAAC;AAAA,CACH;AAED,gHAAgH;AAChH,SAAS,sBAAsB,CAAC,aAAwC,EAAE,QAAgB,EAAmB;IAC5G,IAAI,CAAC,aAAa,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,qCAAqC,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAC;IACxE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,qCAAqC,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,0BAA0B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAAA,CAClD;AAED,SAAS,2BAA2B,CAAC,IAAiB,EAAwC;IAC7F,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;QAC7B,OAAO,eAAe,CAAC;IACxB,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;QAC9B,OAAO,gBAAgB,CAAC;IACzB,CAAC;IACD,IAAI,IAAI,KAAK,kBAAkB,EAAE,CAAC;QACjC,OAAO,mBAAmB,CAAC;IAC5B,CAAC;IACD,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACzB,OAAO,kBAAkB,CAAC;IAC3B,CAAC;IACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACtB,OAAO,eAAe,CAAC;IACxB,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,SAAS,4BAA4B,CAAC,IAAoB,EAAE,QAAgB,EAAE,IAAiB,EAAQ;IACtG,MAAM,aAAa,GAAG,2BAA2B,CAAC,IAAI,CAAC,CAAC;IACxD,IAAI,CAAC,aAAa,EAAE,CAAC;QACpB,OAAO;IACR,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;IACjD,MAAM,eAAe,GAAG,sBAAsB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,sBAAsB,EAAE,CAAC;IAC/D,MAAM,kBAAkB,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IACnD,MAAM,eAAe,GACpB,kBAAkB,IAAI,eAAe,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QACjE,CAAC,CAAC,kBAAkB;QACpB,CAAC,CAAC,mCAAmC,CAAC;IAExC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,oCAAoC,CACxD,eAAe,EACf,eAAe,EACf,CAAC,KAAK,EAAE,EAAE,CAAC;YACV,IAAI,EAAE,CAAC;YACP,IAAI,CAAC,eAAe,CAAC,sBAAsB,CAAC;gBAC3C,GAAG,QAAQ;gBACX,CAAC,aAAa,CAAC,EAAE,KAAK,KAAK,mCAAmC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK;aAClF,CAAC,CAAC;QAAA,CACH,EACD,GAAG,EAAE,CAAC;YACL,IAAI,EAAE,CAAC;YACP,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAAA,CACxB,CACD,CAAC;QACF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAA,CAChD,CAAC,CAAC;AAAA,CACH;AAED;kGACkG;AAClG,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,IAAoB,EACpB,QAAgB,EAChB,aAA2B,EACX;IAChB,IAAI,CAAC,UAAU,CAAC,kCAAkC,QAAQ,uDAAqD,CAAC,CAAC;IACjH,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,UAAU,CAAC,0BAA0B,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;YAChE,OAAO;QACR,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrG,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QACxB,sFAAsF;QACtF,yFAAuF;QACvF,sFAAsF;QACtF,uFAAuF;QACvF,IAAI,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,UAAU,CACd,GAAG,OAAO,CAAC,KAAK,qHAAmH,CACnI,CAAC;YACF,OAAO;QACR,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC3B,MAAM,QAAQ,GAAG,IAAI,4BAA4B,CAChD,OAAO,CAAC,KAAK,EACb,CAAC,IAAI,EAAE,EAAE,CAAC;gBACT,IAAI,EAAE,CAAC;gBACP,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;oBACtB,MAAM,OAAO,GAAG,sBAAsB,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;oBACrE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;wBAClB,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC;wBAC9F,IAAI,CAAC,UAAU,CACd,GAAG,OAAO,CAAC,KAAK,2BAA2B,MAAM,+FAA+F,CAChJ,CAAC;wBACF,OAAO;oBACR,CAAC;gBACF,CAAC;gBACD,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBAC7C,4BAA4B,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAAA,CACxD,EACD,GAAG,EAAE,CAAC;gBACL,IAAI,EAAE,CAAC;gBACP,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;YAAA,CACxB,EACD,aAAa,CACb,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QAAA,CAChD,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,SAAS,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACxE,CAAC;AAAA,CACD;AAED,yFAAyF;AACzF,MAAM,UAAU,iBAAiB,CAAC,IAAoB,EAAE,QAAgB,EAAE,IAAiB,EAAQ;IAClG,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,sBAAsB,QAAQ,6CAA6C,CAAC,CAAC;QAC7F,OAAO;IACR,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,0BAA0B,EAAE,CAAC;QAClE,IAAI,CAAC,eAAe,CAAC,0BAA0B,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChG,IAAI,CAAC,UAAU,CAAC,iCAAiC,QAAQ,kBAAkB,CAAC,CAAC;QAC7E,OAAO;IACR,CAAC;IACD,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,sBAAsB,EAAE,CAAC;QAC7D,IAAI,CAAC,eAAe,CAAC,sBAAsB,CAAC,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpF,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kFAA8E,CAAC;QAClH,IAAI,CAAC,UAAU,CAAC,GAAG,QAAQ,kEAAkE,IAAI,EAAE,CAAC,CAAC;QACrG,OAAO;IACR,CAAC;IACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1E,IAAI,CAAC,UAAU,CAAC,GAAG,QAAQ,uDAAuD,CAAC,CAAC;QACpF,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,sBAAsB,EAAE,CAAC;IAC7D,MAAM,KAAK,GACV,IAAI,KAAK,cAAc;QACtB,CAAC,CAAC,YAAY;QACd,CAAC,CAAC,IAAI,KAAK,eAAe;YACzB,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,IAAI,KAAK,kBAAkB;gBAC5B,CAAC,CAAC,gBAAgB;gBAClB,CAAC,CAAC,IAAI,KAAK,OAAO;oBACjB,CAAC,CAAC,YAAY;oBACd,CAAC,CAAC,eAAe,CAAC;IACvB,IAAI,CAAC,eAAe,CAAC,sBAAsB,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kFAA8E,CAAC;IAClH,IAAI,CAAC,UAAU,CAAC,GAAG,QAAQ,WAAW,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;AAAA,CAC1F","sourcesContent":["/**\n * `/models` local-model lifecycle and `/fitness` probe/role-assignment flows\n * extracted from interactive-mode.\n *\n * `/models` is a USER-invoked local-model manager (list/add/remove/stop, never a\n * model-invokable tool); the fitness helpers probe a model on the current host\n * and land a shaped role. The server/selector flows share a `LocalModelHost`\n * seam, while `runFitnessAndAssign`/`assignFitnessRole` take narrow host shapes\n * matching their prototype-driven behaviour tests (fitness-probe-gate,\n * fitness-role-assignment), which keep exercising interactive-mode's thin\n * wrappers unchanged.\n */\n\nimport type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { getSupportedThinkingLevels } from \"@caupulican/pi-ai\";\nimport {\n\ttype Component,\n\tContainer,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type { ModelRegistry } from \"../../core/model-registry.ts\";\nimport { resolveCliModel } from \"../../core/model-resolver.ts\";\nimport { evaluateSurfaceFitness } from \"../../core/model-router/fitness-gate.ts\";\nimport { DEFAULT_MODEL_SUGGESTIONS } from \"../../core/models/default-model-suggestions.ts\";\nimport { FitnessStore } from \"../../core/models/fitness-store.ts\";\nimport { registerLocalModel, unregisterLocalModel } from \"../../core/models/local-registration.ts\";\nimport type { OllamaRuntime } from \"../../core/models/local-runtime.ts\";\nimport { matchesInstalledLocalModel, normalizeModelSource } from \"../../core/models/model-ref.ts\";\nimport { formatModelFitnessReport, isProbeAllFailed } from \"../../core/research/model-fitness.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { DynamicBorder } from \"./components/dynamic-border.ts\";\nimport { type FitnessRole, FitnessRoleSelectorComponent } from \"./components/fitness-role-selector.ts\";\nimport { ModelSelectorComponent } from \"./components/model-selector.ts\";\nimport { ModelSuggestionSelectorComponent } from \"./components/model-suggestion-selector.ts\";\nimport { getSelectListTheme } from \"./theme/theme.ts\";\n\ntype SelectorFactory = (done: () => void) => { component: Component; focus: Component };\n\nconst MODEL_ROUTER_THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst MODEL_ROUTER_THINKING_INHERIT_VALUE = \"__inherit_model_router_thinking__\";\nconst MODEL_ROUTER_THINKING_INHERIT_LABEL = \"(inherit)\";\n\nconst THINKING_LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\nconst MODEL_ROUTER_THINKING_FALLBACK_LEVELS: readonly ThinkingLevel[] = [\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n];\n\ntype RouterThinkingLevel = ThinkingLevel | typeof MODEL_ROUTER_THINKING_INHERIT_VALUE;\ntype ModelRouterThinkingField =\n\t| \"cheapThinking\"\n\t| \"mediumThinking\"\n\t| \"expensiveThinking\"\n\t| \"executorThinking\"\n\t| \"judgeThinking\";\n\n/** Narrow seam for persisting a probed model's role — matches the fitness-role test. */\nexport interface AssignRoleHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n}\n\n/** Seam for the fitness probe + role-selector flow — matches the fitness-probe-gate test. */\nexport interface RunFitnessHost {\n\treadonly session: Pick<AgentSession, \"runModelFitness\"> & { modelRegistry?: ModelRegistry };\n\treadonly settingsManager: SettingsManager;\n\treadonly chatContainer: Container;\n\treadonly ui: TUI;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowSelector(create: SelectorFactory): void;\n}\n\n/** Full seam for the `/models` server/selector flows. */\nexport interface LocalModelHost {\n\treadonly localRuntime: OllamaRuntime;\n\treadonly session: AgentSession;\n\treadonly settingsManager: SettingsManager;\n\treadonly ui: TUI;\n\treadonly chatContainer: Container;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\tshowSelector(create: SelectorFactory): void;\n}\n\nclass ModelRouterThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: RouterThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: RouterThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingOptions: SelectItem[] = [\n\t\t\t{\n\t\t\t\tvalue: MODEL_ROUTER_THINKING_INHERIT_VALUE,\n\t\t\t\tlabel: MODEL_ROUTER_THINKING_INHERIT_LABEL,\n\t\t\t\tdescription: \"Use current session default for this tier.\",\n\t\t\t},\n\t\t\t...availableLevels.map((level) => ({\n\t\t\t\tvalue: level,\n\t\t\t\tlabel: level,\n\t\t\t\tdescription: THINKING_LEVEL_DESCRIPTIONS[level],\n\t\t\t})),\n\t\t];\n\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingOptions,\n\t\t\tthinkingOptions.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tMODEL_ROUTER_THINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\tconst index = thinkingOptions.findIndex((item) => item.value === currentLevel);\n\t\tif (index !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(index);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as RouterThinkingLevel);\n\t\t};\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n}\n/**\n * /models — USER-invoked local model lifecycle (never a model-invokable tool):\n * list/add/remove/stop per local-model-lifecycle-design.md. Removal is explicit-only with\n * full disclosure; a pasted install command is parsed for its ref, never executed.\n */\nexport async function handleModelsCommand(host: LocalModelHost, argsText: string): Promise<void> {\n\tconst [action = \"list\", ...rest] = argsText.split(/\\s+/).filter(Boolean);\n\ttry {\n\t\tif (action === \"suggest\" || action === \"suggestions\") {\n\t\t\tshowModelSuggestionSelector(host);\n\t\t\treturn;\n\t\t}\n\t\tif (action === \"stop\") {\n\t\t\tconst stopped = host.localRuntime.stop();\n\t\t\thost.showStatus(\n\t\t\t\tstopped.stopped\n\t\t\t\t\t? \"Pi-managed local model server stopped (models remain installed).\"\n\t\t\t\t\t: \"No pi-managed server running (a system server, if any, is not pi's to stop).\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (action === \"add\") {\n\t\t\tconst rawRef = rest.join(\" \");\n\t\t\tif (!rawRef) {\n\t\t\t\thost.showStatus(\n\t\t\t\t\t\"Usage: /models add <ollama-tag | hf.co/org/repo[:quant] | huggingface URL | pasted install command>\",\n\t\t\t\t);\n\t\t\t\thost.showStatus(\"Or start from a validated suggestion: /models suggest\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst source = normalizeModelSource(rawRef);\n\t\t\tif (source.type === \"rejected\") {\n\t\t\t\thost.showStatus(`Not added: ${source.reason}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (source.type === \"api\") {\n\t\t\t\thost.showStatus(\n\t\t\t\t\t`${source.ref} is an API model — nothing to install. Configure auth for the provider, then probe it with /fitness ${source.ref}.`,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait addLocalModel(host, source.pullRef);\n\t\t\treturn;\n\t\t}\n\n\t\tif (action === \"remove\") {\n\t\t\tconst ref = rest[0];\n\t\t\tconst confirmed = rest[1] === \"confirm\";\n\t\t\tif (!ref) {\n\t\t\t\thost.showStatus(\"Usage: /models remove <ref> confirm\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait removeLocalModel(host, ref, confirmed);\n\t\t\treturn;\n\t\t}\n\n\t\tawait listLocalModels(host);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function ensureLocalServer(host: LocalModelHost): Promise<boolean> {\n\tconst status = await host.localRuntime.detect();\n\tif (status.serverUp) return true;\n\tif (!status.binaryPath) {\n\t\tfor (const line of host.localRuntime.installGuide()) host.showStatus(line);\n\t\treturn false;\n\t}\n\thost.showStatus(\n\t\t`Starting local model server (${status.binarySource} binary, owned storage: ${status.ownedModelsDir})…`,\n\t);\n\tconst started = await host.localRuntime.start();\n\tif (!started.started) {\n\t\thost.showStatus(`Could not start the local server: ${started.reason}`);\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nexport async function listLocalModels(host: LocalModelHost): Promise<void> {\n\tconst status = await host.localRuntime.detect();\n\tif (!status.serverUp) {\n\t\tif (!status.binaryPath) {\n\t\t\tfor (const line of host.localRuntime.installGuide()) host.showStatus(line);\n\t\t\treturn;\n\t\t}\n\t\thost.showStatus(\n\t\t\t`Local server not running (binary: ${status.binarySource}). /models add starts it on demand; /fitness probes registered models.`,\n\t\t);\n\t\treturn;\n\t}\n\tconst models = await host.localRuntime.list();\n\tconst fitness = FitnessStore.forAgentDir(getAgentDir()).getForHost();\n\tconst lines = [\n\t\t`Local models (${status.managedByPi ? `pi-managed server, storage: ${status.ownedModelsDir}` : \"system server — storage owned by the system daemon\"}):`,\n\t\t...(models.length === 0\n\t\t\t? [\" (none installed — /models add <ref>, or /models suggest for a validated roster)\"]\n\t\t\t: []),\n\t\t...models.map((model) => {\n\t\t\tconst report = fitness.find((entry) => entry.model === `ollama/${model.name}`);\n\t\t\tconst gb = (model.sizeBytes / 1e9).toFixed(2);\n\t\t\tconst probe = report\n\t\t\t\t? `probed ${report.at.slice(0, 10)}: digest ${report.report.digest?.succeeded ?? \"?\"}/${report.report.digest?.total ?? \"?\"}, tool-calls ${report.report.toolCall.succeeded}/${report.report.toolCall.total}${report.report.tokensPerSecond ? `, ~${report.report.tokensPerSecond} tok/s` : \"\"}`\n\t\t\t\t: `unprobed — run /fitness ollama/${model.name}`;\n\t\t\treturn ` - ${model.name} (${gb} GB) · ${probe}`;\n\t\t}),\n\t\t\"Commands: /models add <ref> · /models remove <ref> confirm · /models stop\",\n\t];\n\tfor (const line of lines) host.showStatus(line);\n}\n\nexport async function addLocalModel(host: LocalModelHost, pullRef: string, preselectRole?: FitnessRole): Promise<void> {\n\tif (!(await ensureLocalServer(host))) return;\n\thost.showStatus(`Pulling ${pullRef}… (weights land in the server's model storage)`);\n\tlet lastShown = 0;\n\tconst pulled = await host.localRuntime.pull(pullRef, (progress) => {\n\t\tconst now = Date.now();\n\t\tif (now - lastShown > 2000) {\n\t\t\tlastShown = now;\n\t\t\thost.showStatus(` ${pullRef}: ${progress}`);\n\t\t}\n\t});\n\tif (!pulled.ok) {\n\t\thost.showStatus(`Pull failed: ${pulled.error}`);\n\t\treturn;\n\t}\n\tconst registration = registerLocalModel({\n\t\tagentDir: getAgentDir(),\n\t\tref: pullRef,\n\t\tbaseUrl: host.localRuntime.baseUrl,\n\t});\n\tif (!registration.ok) {\n\t\thost.showStatus(`Pulled, but not auto-registered: ${registration.reason}`);\n\t\tif (registration.manualSnippet) {\n\t\t\thost.showStatus(`Add this to ${registration.modelsJsonPath} yourself:\\n${registration.manualSnippet}`);\n\t\t}\n\t\treturn;\n\t}\n\thost.session.modelRegistry.refresh();\n\thost.showStatus(`${pullRef} installed and registered as ollama/${pullRef}. Probing fitness…`);\n\tawait runFitnessAndAssign(host, `ollama/${pullRef}`, preselectRole);\n}\n\nexport async function removeLocalModel(host: LocalModelHost, ref: string, confirmed: boolean): Promise<void> {\n\tconst status = await host.localRuntime.detect();\n\tif (!status.serverUp) {\n\t\thost.showStatus(\"Local server not running — start it (any /models action) before removing.\");\n\t\treturn;\n\t}\n\tconst models = await host.localRuntime.list();\n\tconst target = models.find((model) => matchesInstalledLocalModel(ref, model.name));\n\tif (!target) {\n\t\thost.showStatus(\n\t\t\t`${ref} is not installed. Installed: ${models.map((model) => model.name).join(\", \") || \"(none)\"}`,\n\t\t);\n\t\treturn;\n\t}\n\tif (!confirmed) {\n\t\t// EXPLICIT USER ACTION ONLY: full disclosure, then require the confirm token.\n\t\tconst gb = (target.sizeBytes / 1e9).toFixed(2);\n\t\thost.showStatus(\n\t\t\t[\n\t\t\t\t`Removing ${ref} will delete:`,\n\t\t\t\t` - model weights (${gb} GB) from ${status.managedByPi ? status.ownedModelsDir : \"the system server's storage\"}`,\n\t\t\t\t` - the ollama/${ref} entry in models.json`,\n\t\t\t\t` - its cached fitness report for this host`,\n\t\t\t\t`Run: /models remove ${ref} confirm`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\treturn;\n\t}\n\tconst removed = await host.localRuntime.remove(ref);\n\tif (!removed.ok) {\n\t\thost.showStatus(`Remove failed: ${removed.error}`);\n\t\treturn;\n\t}\n\tunregisterLocalModel({ agentDir: getAgentDir(), ref });\n\tFitnessStore.forAgentDir(getAgentDir()).remove(`ollama/${ref}`);\n\thost.session.modelRegistry.refresh();\n\thost.showStatus(`${ref} removed: weights deleted, registration and fitness report dropped.`);\n}\n\n/** /fitness with no args: pick a model from the configured registry, probe it, assign a role. */\n/** Pick a validated suggestion → install it → probe on this host → land its shaped role. */\nexport function showModelSuggestionSelector(host: LocalModelHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSuggestionSelectorComponent(\n\t\t\tDEFAULT_MODEL_SUGGESTIONS,\n\t\t\tasync (suggestion) => {\n\t\t\t\tdone();\n\t\t\t\t// The shaped role rides along so the post-probe selector lands on it pre-selected;\n\t\t\t\t// non-tool-callers carry curator/judge/none, never executor, so this can't footgun.\n\t\t\t\tawait addLocalModel(host, suggestion.pullRef, suggestion.assignRole);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showFitnessModelSelector(host: LocalModelHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSelectorComponent(\n\t\t\thost.ui,\n\t\t\thost.session.model,\n\t\t\thost.settingsManager,\n\t\t\thost.session.modelRegistry,\n\t\t\thost.session.scopedModels,\n\t\t\tasync (model) => {\n\t\t\t\tdone();\n\t\t\t\tawait runFitnessAndAssign(host, `${model.provider}/${model.id}`);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\n/** Resolve the routed model's supported thinking levels for a fitness-assignment flow, with a safe fallback. */\nfunction getModelThinkingLevels(modelRegistry: ModelRegistry | undefined, modelRef: string): ThinkingLevel[] {\n\tif (!modelRegistry) {\n\t\treturn [...MODEL_ROUTER_THINKING_FALLBACK_LEVELS];\n\t}\n\tconst resolved = resolveCliModel({ cliModel: modelRef, modelRegistry });\n\tif (!resolved.model) {\n\t\treturn [...MODEL_ROUTER_THINKING_FALLBACK_LEVELS];\n\t}\n\treturn getSupportedThinkingLevels(resolved.model);\n}\n\nfunction getModelRouterThinkingField(role: FitnessRole): ModelRouterThinkingField | undefined {\n\tif (role === \"router-cheap\") {\n\t\treturn \"cheapThinking\";\n\t}\n\tif (role === \"router-medium\") {\n\t\treturn \"mediumThinking\";\n\t}\n\tif (role === \"router-expensive\") {\n\t\treturn \"expensiveThinking\";\n\t}\n\tif (role === \"executor\") {\n\t\treturn \"executorThinking\";\n\t}\n\tif (role === \"judge\") {\n\t\treturn \"judgeThinking\";\n\t}\n\treturn undefined;\n}\n\nfunction promptForModelRouterThinking(host: RunFitnessHost, modelRef: string, role: FitnessRole): void {\n\tconst thinkingField = getModelRouterThinkingField(role);\n\tif (!thinkingField) {\n\t\treturn;\n\t}\n\n\tconst modelRegistry = host.session.modelRegistry;\n\tconst availableLevels = getModelThinkingLevels(modelRegistry, modelRef);\n\tconst settings = host.settingsManager.getModelRouterSettings();\n\tconst configuredThinking = settings[thinkingField];\n\tconst currentThinking =\n\t\tconfiguredThinking && availableLevels.includes(configuredThinking)\n\t\t\t? configuredThinking\n\t\t\t: MODEL_ROUTER_THINKING_INHERIT_VALUE;\n\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelRouterThinkingSelectorComponent(\n\t\t\tcurrentThinking,\n\t\t\tavailableLevels,\n\t\t\t(level) => {\n\t\t\t\tdone();\n\t\t\t\thost.settingsManager.setModelRouterSettings({\n\t\t\t\t\t...settings,\n\t\t\t\t\t[thinkingField]: level === MODEL_ROUTER_THINKING_INHERIT_VALUE ? undefined : level,\n\t\t\t\t});\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\n/** Probe a model's fitness, show the report, then offer one-step role assignment. When the model\n * came from a validated suggestion, `preselectRole` lands its shaped role already highlighted. */\nexport async function runFitnessAndAssign(\n\thost: RunFitnessHost,\n\tmodelRef: string,\n\tpreselectRole?: FitnessRole,\n): Promise<void> {\n\thost.showStatus(`Model fitness probe running on ${modelRef}… (6 surfaces; local models may take a few minutes)`);\n\ttry {\n\t\tconst outcome = await host.session.runModelFitness({ model: modelRef });\n\t\tif (!outcome.started) {\n\t\t\thost.showStatus(`Model fitness skipped: ${outcome.skipReason}`);\n\t\t\treturn;\n\t\t}\n\t\thost.chatContainer.addChild(new Spacer(1));\n\t\thost.chatContainer.addChild(new Text(formatModelFitnessReport(outcome.model, outcome.report), 1, 0));\n\t\thost.ui.requestRender();\n\t\t// Validate-before-load: zero successes on every probed surface means the model cannot\n\t\t// drive any of the harness's subagent contracts on this host — refuse adoption instead\n\t\t// of landing a role selector the user might reflexively confirm (this is the reported\n\t\t// bug: a 0/3-everywhere model still got set as judge model and saved to Model Router).\n\t\tif (isProbeAllFailed(outcome.report)) {\n\t\t\thost.showStatus(\n\t\t\t\t`${outcome.model} failed the fitness probe on all surfaces — not configured. Use /model to set it manually if you accept the risk.`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\thost.showSelector((done) => {\n\t\t\tconst selector = new FitnessRoleSelectorComponent(\n\t\t\t\toutcome.model,\n\t\t\t\t(role) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tif (role === \"scout\") {\n\t\t\t\t\t\tconst verdict = evaluateSurfaceFitness(\"scout_auto\", outcome.report);\n\t\t\t\t\t\tif (!verdict.fit) {\n\t\t\t\t\t\t\tconst reason = verdict.reason === \"lane_failed\" ? `failed ${verdict.lane}` : \"was not probed\";\n\t\t\t\t\t\t\thost.showStatus(\n\t\t\t\t\t\t\t\t`${outcome.model} not assigned as scout: ${reason} on the scout_auto fitness exam. Use the docs/scout.md Modelfile recipe, then rerun /fitness.`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tassignFitnessRole(host, outcome.model, role);\n\t\t\t\t\tpromptForModelRouterThinking(host, outcome.model, role);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t},\n\t\t\t\tpreselectRole,\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\n/** Persist a role assignment from the post-probe selector into the matching settings. */\nexport function assignFitnessRole(host: AssignRoleHost, modelRef: string, role: FitnessRole): void {\n\tif (role === \"none\") {\n\t\thost.showStatus(`Fitness result for ${modelRef} saved. Assign a role later from /settings.`);\n\t\treturn;\n\t}\n\tif (role === \"curator\") {\n\t\tconst current = host.settingsManager.getContextCurationSettings();\n\t\thost.settingsManager.setContextCurationSettings({ ...current, enabled: true, model: modelRef });\n\t\thost.showStatus(`Context curation enabled with ${modelRef} as the curator.`);\n\t\treturn;\n\t}\n\tif (role === \"executor\") {\n\t\tconst router = host.settingsManager.getModelRouterSettings();\n\t\thost.settingsManager.setModelRouterSettings({ ...router, executorModel: modelRef });\n\t\tconst hint = router.enabled ? \"\" : \" Model router is currently disabled — enable it in /settings → Model Router.\";\n\t\thost.showStatus(`${modelRef} set as the toolkit executor (direct Level-0 hits route to it).${hint}`);\n\t\treturn;\n\t}\n\tif (role === \"scout\") {\n\t\thost.settingsManager.setScoutSettings({ enabled: true, model: modelRef });\n\t\thost.showStatus(`${modelRef} set as the repository scout (context_scout enabled).`);\n\t\treturn;\n\t}\n\tconst router = host.settingsManager.getModelRouterSettings();\n\tconst field =\n\t\trole === \"router-cheap\"\n\t\t\t? \"cheapModel\"\n\t\t\t: role === \"router-medium\"\n\t\t\t\t? \"mediumModel\"\n\t\t\t\t: role === \"router-expensive\"\n\t\t\t\t\t? \"expensiveModel\"\n\t\t\t\t\t: role === \"judge\"\n\t\t\t\t\t\t? \"judgeModel\"\n\t\t\t\t\t\t: \"learningModel\";\n\thost.settingsManager.setModelRouterSettings({ ...router, [field]: modelRef });\n\tconst hint = router.enabled ? \"\" : \" Model router is currently disabled — enable it in /settings → Model Router.\";\n\thost.showStatus(`${modelRef} set as ${role.replace(\"router-\", \"router \")} model.${hint}`);\n}\n"]}
@@ -17,6 +17,7 @@ import { type Component, type Container, Loader, type TUI } from "@caupulican/pi
17
17
  import type { AgentSession, GoalContinuationLoopOptions, GoalContinuationLoopResult } from "../../core/agent-session.ts";
18
18
  import type { AgentSessionRuntime } from "../../core/agent-session-runtime.ts";
19
19
  import type { ExtensionCommandContext } from "../../core/extensions/index.ts";
20
+ import type { GoalState } from "../../core/goals/goal-state.ts";
20
21
  import type { KeybindingsManager } from "../../core/keybindings.ts";
21
22
  import { MissingSessionCwdError } from "../../core/session-cwd.ts";
22
23
  import type { SettingsManager } from "../../core/settings-manager.ts";
@@ -110,6 +111,21 @@ export type ParsedGoalContinueCommand = {
110
111
  ok: false;
111
112
  error: string;
112
113
  };
114
+ export interface GoalCommandHost {
115
+ readonly session: {
116
+ getGoalStateSnapshot: () => GoalState | undefined;
117
+ saveGoalStateSnapshot: (state: GoalState) => string;
118
+ sendUserMessage: (content: string) => Promise<void>;
119
+ continueGoalLoop: (options: GoalContinuationLoopOptions) => Promise<GoalContinuationLoopResult>;
120
+ getGoalRuntimeSnapshot: (settings: {
121
+ maxStallTurns: number;
122
+ }) => ReturnType<AgentSession["getGoalRuntimeSnapshot"]>;
123
+ };
124
+ showStatus(message: string): void;
125
+ showError(message: string): void;
126
+ refreshAutonomyFooterStatus(): void;
127
+ }
128
+ export declare function handleGoalCommand(host: GoalCommandHost, text: string): Promise<void>;
113
129
  export interface GoalContinueCommandHost {
114
130
  readonly session: {
115
131
  continueGoalLoop: (options: GoalContinuationLoopOptions) => Promise<GoalContinuationLoopResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"session-flow-commands.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/session-flow-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session-picker, tree/fork navigation, and model-selector flows extracted from\n * interactive-mode.\n *\n * Each entry opens a selector overlay (via `host.showSelector`) or renders a\n * session-info block, driving `AgentSession`/`AgentSessionRuntime` navigation and\n * the fork/resume/clone/goal-continuation paths. The interdependent selector\n * flows share one narrow `SessionFlowHost` seam; the three prototype-tested leaf\n * commands (`handleCloneCommand`, `handleGoalContinueCommand`,\n * `handleSessionCommand`) take their own minimal host shapes so their behaviour\n * tests keep exercising them through interactive-mode's thin wrappers unchanged.\n */\n\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Model } from \"@caupulican/pi-ai\";\nimport type { EditorComponent } from \"@caupulican/pi-tui\";\nimport { type Component, type Container, Loader, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport type {\n\tAgentSession,\n\tGoalContinuationLoopOptions,\n\tGoalContinuationLoopResult,\n} from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\nimport type { ExtensionCommandContext } from \"../../core/extensions/index.ts\";\nimport type { KeybindingsManager } from \"../../core/keybindings.ts\";\nimport { findExactModelReferenceMatch, resolveModelScope } from \"../../core/model-resolver.ts\";\nimport { MissingSessionCwdError } from \"../../core/session-cwd.ts\";\nimport { listAllSessions, listSessions, openSession } from \"../../core/session-manager-factory.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { ProjectTrustStore } from \"../../core/trust-manager.ts\";\nimport type { CustomEditor } from \"./components/custom-editor.ts\";\nimport type { FooterComponent } from \"./components/footer.ts\";\nimport { keyText } from \"./components/keybinding-hints.ts\";\nimport { ModelSelectorComponent } from \"./components/model-selector.ts\";\nimport { ScopedModelsSelectorComponent } from \"./components/scoped-models-selector.ts\";\nimport { SessionSelectorComponent } from \"./components/session-selector.ts\";\nimport { TreeSelectorComponent } from \"./components/tree-selector.ts\";\nimport { TrustSelectorComponent } from \"./components/trust-selector.ts\";\nimport { UserMessageSelectorComponent } from \"./components/user-message-selector.ts\";\nimport type { ExtensionUiHost } from \"./extension-ui-host.ts\";\nimport { theme } from \"./theme/theme.ts\";\n\n/** Shared seam for the interdependent selector/navigation flows. */\nexport interface SessionFlowHost {\n\treadonly session: AgentSession;\n\treadonly sessionManager: SessionManager;\n\treadonly settingsManager: SettingsManager;\n\treadonly runtimeHost: AgentSessionRuntime;\n\treadonly ui: TUI;\n\treadonly chatContainer: Container;\n\treadonly statusContainer: Container;\n\treadonly editor: EditorComponent;\n\treadonly defaultEditor: CustomEditor;\n\treadonly footer: FooterComponent;\n\treadonly extensionUiHost: ExtensionUiHost;\n\treadonly keybindings: KeybindingsManager;\n\tloadingAnimation: Loader | undefined;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\trenderCurrentSessionState(): void;\n\trenderInitialMessages(options?: { forceHistoryLoad?: boolean }): Promise<void>;\n\tflushCompactionQueue(options?: { willRetry?: boolean }): Promise<void>;\n\thandleFatalRuntimeError(prefix: string, error: unknown): Promise<never>;\n\tpromptForMissingSessionCwd(error: MissingSessionCwdError): Promise<string | undefined>;\n\tupdateEditorBorderColor(): void;\n\tupdateAvailableProviderCount(): Promise<void>;\n\tmaybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;\n\tcheckDaxnutsEasterEgg(model: { provider: string; id: string }): void;\n\tgetModelCandidates(): Promise<Model<any>[]>;\n\tshutdown(options?: { fromSignal?: boolean }): Promise<void>;\n}\n\nexport async function showModelSelector(host: SessionFlowHost, initialSearchInput?: string): Promise<void> {\n\ttry {\n\t\tawait host.session.extensionRunner.emit({\n\t\t\ttype: \"model_selector_open\",\n\t\t\tcurrentModel: host.session.model,\n\t\t\tscopedModels: host.session.scopedModels,\n\t\t\tinitialSearchInput,\n\t\t});\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\treturn;\n\t}\n\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSelectorComponent(\n\t\t\thost.ui,\n\t\t\thost.session.model,\n\t\t\thost.settingsManager,\n\t\t\thost.session.modelRegistry,\n\t\t\thost.session.scopedModels,\n\t\t\tasync (model) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait host.session.setModel(model);\n\t\t\t\t\thost.footer.invalidate();\n\t\t\t\t\thost.updateEditorBorderColor();\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showStatus(`Model: ${model.id}`);\n\t\t\t\t\tvoid host.maybeWarnAboutAnthropicSubscriptionAuth(model);\n\t\t\t\t\thost.checkDaxnutsEasterEgg(model);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\tinitialSearchInput,\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport async function showModelsSelector(host: SessionFlowHost): Promise<void> {\n\t// Get all available models\n\thost.session.modelRegistry.refresh();\n\tconst allModels = host.session.modelRegistry.getAvailable();\n\n\tif (allModels.length === 0) {\n\t\thost.showStatus(\"No models available\");\n\t\treturn;\n\t}\n\n\t// Check if session has scoped models (from previous session-only changes or CLI --models)\n\tconst sessionScopedModels = host.session.scopedModels;\n\tconst hasSessionScope = sessionScopedModels.length > 0;\n\n\t// Build enabled model IDs from session state or settings\n\tlet currentEnabledIds: string[] | null = null;\n\n\tif (hasSessionScope) {\n\t\t// Use current session's scoped models\n\t\tcurrentEnabledIds = sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);\n\t} else {\n\t\t// Fall back to settings\n\t\tconst patterns = host.settingsManager.getEnabledModels();\n\t\tif (patterns !== undefined && patterns.length > 0) {\n\t\t\tconst scopedModels = await resolveModelScope(patterns, host.session.modelRegistry);\n\t\t\tcurrentEnabledIds = scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);\n\t\t}\n\t}\n\n\t// Helper to update session's scoped models (session-only, no persist)\n\tconst updateSessionModels = async (enabledIds: string[] | null) => {\n\t\tcurrentEnabledIds = enabledIds === null ? null : [...enabledIds];\n\t\tif (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {\n\t\t\tconst newScopedModels = await resolveModelScope(enabledIds, host.session.modelRegistry);\n\t\t\thost.session.setScopedModels(\n\t\t\t\tnewScopedModels.map((sm) => ({\n\t\t\t\t\tmodel: sm.model,\n\t\t\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t\t\t})),\n\t\t\t);\n\t\t} else {\n\t\t\t// All enabled or none enabled = no filter\n\t\t\thost.session.setScopedModels([]);\n\t\t}\n\t\tawait host.updateAvailableProviderCount();\n\t\thost.ui.requestRender();\n\t};\n\n\thost.showSelector((done) => {\n\t\tconst selector = new ScopedModelsSelectorComponent(\n\t\t\t{\n\t\t\t\tallModels,\n\t\t\t\tenabledModelIds: currentEnabledIds,\n\t\t\t},\n\t\t\t{\n\t\t\t\tonChange: async (enabledIds) => {\n\t\t\t\t\tawait updateSessionModels(enabledIds);\n\t\t\t\t},\n\t\t\t\tonPersist: (enabledIds) => {\n\t\t\t\t\t// Persist to settings\n\t\t\t\t\tconst newPatterns =\n\t\t\t\t\t\tenabledIds === null || enabledIds.length === allModels.length\n\t\t\t\t\t\t\t? undefined // All enabled = clear filter\n\t\t\t\t\t\t\t: enabledIds;\n\t\t\t\t\thost.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);\n\t\t\t\t\thost.showStatus(\"Model selection saved to settings\");\n\t\t\t\t},\n\t\t\t\tonCancel: () => {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showUserMessageSelector(host: SessionFlowHost, newSessionName?: string): void {\n\tconst userMessages = host.session.getUserMessagesForForking();\n\n\tif (userMessages.length === 0) {\n\t\thost.showStatus(\"No messages to fork from\");\n\t\treturn;\n\t}\n\n\tconst initialSelectedId = userMessages[userMessages.length - 1]?.entryId;\n\n\thost.showSelector((done) => {\n\t\tconst selector = new UserMessageSelectorComponent(\n\t\t\tuserMessages.map((m) => ({ id: m.entryId, text: m.text })),\n\t\t\tasync (entryId) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await host.runtimeHost.fork(entryId);\n\t\t\t\t\tif (result.cancelled) {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\thost.renderCurrentSessionState();\n\t\t\t\t\tif (newSessionName) {\n\t\t\t\t\t\thost.session.setSessionName(newSessionName);\n\t\t\t\t\t}\n\t\t\t\t\thost.editor.setText(result.selectedText ?? \"\");\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showStatus(newSessionName ? `Forked to new session: ${newSessionName}` : \"Forked to new session\");\n\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\tinitialSelectedId,\n\t\t);\n\t\treturn { component: selector, focus: selector.getMessageList() };\n\t});\n}\n\nexport function showTreeSelector(host: SessionFlowHost, initialSelectedId?: string): void {\n\tconst tree = host.sessionManager.getTree();\n\tconst realLeafId = host.sessionManager.getLeafId();\n\tconst initialFilterMode = host.settingsManager.getTreeFilterMode();\n\n\tif (tree.length === 0) {\n\t\thost.showStatus(\"No entries in session\");\n\t\treturn;\n\t}\n\n\thost.showSelector((done) => {\n\t\tconst selector = new TreeSelectorComponent(\n\t\t\ttree,\n\t\t\trealLeafId,\n\t\t\thost.ui.terminal.rows,\n\t\t\tasync (entryId) => {\n\t\t\t\t// Selecting the current leaf is a no-op (already there)\n\t\t\t\tif (entryId === realLeafId) {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showStatus(\"Already at this point\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Ask about summarization\n\t\t\t\tdone(); // Close selector first\n\n\t\t\t\t// Loop until user makes a complete choice or cancels to tree\n\t\t\t\tlet wantsSummary = false;\n\t\t\t\tlet customInstructions: string | undefined;\n\n\t\t\t\t// Check if we should skip the prompt (user preference to always default to no summary)\n\t\t\t\tif (!host.settingsManager.getBranchSummarySkipPrompt()) {\n\t\t\t\t\twhile (true) {\n\t\t\t\t\t\tconst summaryChoice = await host.extensionUiHost.showExtensionSelector(\"Summarize branch?\", [\n\t\t\t\t\t\t\t\"No summary\",\n\t\t\t\t\t\t\t\"Summarize\",\n\t\t\t\t\t\t\t\"Summarize with custom prompt\",\n\t\t\t\t\t\t]);\n\n\t\t\t\t\t\tif (summaryChoice === undefined) {\n\t\t\t\t\t\t\t// User pressed escape - re-show tree selector with same selection\n\t\t\t\t\t\t\tshowTreeSelector(host, entryId);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\twantsSummary = summaryChoice !== \"No summary\";\n\n\t\t\t\t\t\tif (summaryChoice === \"Summarize with custom prompt\") {\n\t\t\t\t\t\t\tcustomInstructions = await host.extensionUiHost.showExtensionEditor(\n\t\t\t\t\t\t\t\t\"Custom summarization instructions\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (customInstructions === undefined) {\n\t\t\t\t\t\t\t\t// User cancelled - loop back to summary selector\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// User made a complete choice\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Set up escape handler and loader if summarizing\n\t\t\t\tlet summaryLoader: Loader | undefined;\n\t\t\t\tconst originalOnEscape = host.defaultEditor.onEscape;\n\n\t\t\t\tif (wantsSummary) {\n\t\t\t\t\thost.defaultEditor.onEscape = () => {\n\t\t\t\t\t\thost.session.abortBranchSummary();\n\t\t\t\t\t};\n\t\t\t\t\thost.chatContainer.addChild(new Spacer(1));\n\t\t\t\t\tsummaryLoader = new Loader(\n\t\t\t\t\t\thost.ui,\n\t\t\t\t\t\t(spinner) => theme.fg(\"accent\", spinner),\n\t\t\t\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t\t\t\t`Summarizing branch... (${keyText(\"app.interrupt\")} to cancel)`,\n\t\t\t\t\t);\n\t\t\t\t\thost.statusContainer.addChild(summaryLoader);\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await host.session.navigateTree(entryId, {\n\t\t\t\t\t\tsummarize: wantsSummary,\n\t\t\t\t\t\tcustomInstructions,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (result.aborted) {\n\t\t\t\t\t\t// Summarization aborted - re-show tree selector with same selection\n\t\t\t\t\t\thost.showStatus(\"Branch summarization cancelled\");\n\t\t\t\t\t\tshowTreeSelector(host, entryId);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (result.cancelled) {\n\t\t\t\t\t\thost.showStatus(\"Navigation cancelled\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update UI\n\t\t\t\t\tawait host.renderInitialMessages();\n\t\t\t\t\tif (result.editorText && !host.editor.getText().trim()) {\n\t\t\t\t\t\thost.editor.setText(result.editorText);\n\t\t\t\t\t}\n\t\t\t\t\thost.showStatus(\"Navigated to selected point\");\n\t\t\t\t\tvoid host.flushCompactionQueue({ willRetry: false });\n\t\t\t\t} catch (error) {\n\t\t\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\t\t} finally {\n\t\t\t\t\tif (summaryLoader) {\n\t\t\t\t\t\tsummaryLoader.stop();\n\t\t\t\t\t\thost.statusContainer.clear();\n\t\t\t\t\t}\n\t\t\t\t\thost.defaultEditor.onEscape = originalOnEscape;\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\t(entryId, label) => {\n\t\t\t\thost.sessionManager.appendLabelChange(entryId, label);\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\tinitialSelectedId,\n\t\t\tinitialFilterMode,\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showTrustSelector(host: SessionFlowHost): void {\n\tconst cwd = host.sessionManager.getCwd();\n\tconst trustStore = new ProjectTrustStore(host.runtimeHost.services.agentDir);\n\tconst savedDecision = trustStore.get(cwd);\n\thost.showSelector((done) => {\n\t\tconst selector = new TrustSelectorComponent({\n\t\t\tcwd,\n\t\t\tsavedDecision,\n\t\t\tprojectTrusted: host.settingsManager.isProjectTrusted(),\n\t\t\tonSelect: (trusted) => {\n\t\t\t\ttrustStore.set(cwd, trusted);\n\t\t\t\tdone();\n\t\t\t\thost.showStatus(\n\t\t\t\t\t`Saved trust decision: ${trusted ? \"trusted\" : \"untrusted\"}. Restart pi for this to take effect.`,\n\t\t\t\t);\n\t\t\t},\n\t\t\tonCancel: () => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t});\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showSessionSelector(host: SessionFlowHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new SessionSelectorComponent(\n\t\t\t(onProgress) => listSessions(host.sessionManager.getCwd(), host.sessionManager.getSessionDir(), onProgress),\n\t\t\t(onProgress) =>\n\t\t\t\thost.sessionManager.usesDefaultSessionDir()\n\t\t\t\t\t? listAllSessions(onProgress)\n\t\t\t\t\t: listAllSessions(host.sessionManager.getSessionDir(), onProgress),\n\t\t\tasync (sessionPath) => {\n\t\t\t\tdone();\n\t\t\t\tawait handleResumeSession(host, sessionPath);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tvoid host.shutdown();\n\t\t\t},\n\t\t\t() => host.ui.requestRender(),\n\t\t\t{\n\t\t\t\trenameSession: async (sessionFilePath: string, nextName: string | undefined) => {\n\t\t\t\t\tconst next = (nextName ?? \"\").trim();\n\t\t\t\t\tif (!next) return;\n\t\t\t\t\tconst mgr = openSession(sessionFilePath);\n\t\t\t\t\tmgr.appendSessionInfo(next);\n\t\t\t\t},\n\t\t\t\tshowRenameHint: true,\n\t\t\t\tkeybindings: host.keybindings,\n\t\t\t},\n\n\t\t\thost.sessionManager.getSessionFile(),\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport async function handleResumeSession(\n\thost: SessionFlowHost,\n\tsessionPath: string,\n\toptions?: Parameters<ExtensionCommandContext[\"switchSession\"]>[1],\n): Promise<{ cancelled: boolean }> {\n\tif (host.loadingAnimation) {\n\t\thost.loadingAnimation.stop();\n\t\thost.loadingAnimation = undefined;\n\t}\n\thost.statusContainer.clear();\n\ttry {\n\t\tconst result = await host.runtimeHost.switchSession(sessionPath, {\n\t\t\twithSession: options?.withSession,\n\t\t});\n\t\tif (result.cancelled) {\n\t\t\treturn result;\n\t\t}\n\t\thost.renderCurrentSessionState();\n\t\thost.showStatus(\"Resumed session\");\n\t\treturn result;\n\t} catch (error: unknown) {\n\t\tif (error instanceof MissingSessionCwdError) {\n\t\t\tconst selectedCwd = await host.promptForMissingSessionCwd(error);\n\t\t\tif (!selectedCwd) {\n\t\t\t\thost.showStatus(\"Resume cancelled\");\n\t\t\t\treturn { cancelled: true };\n\t\t\t}\n\t\t\tconst result = await host.runtimeHost.switchSession(sessionPath, {\n\t\t\t\tcwdOverride: selectedCwd,\n\t\t\t\twithSession: options?.withSession,\n\t\t\t});\n\t\t\tif (result.cancelled) {\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\thost.renderCurrentSessionState();\n\t\t\thost.showStatus(\"Resumed session in current cwd\");\n\t\t\treturn result;\n\t\t}\n\t\treturn host.handleFatalRuntimeError(\"Failed to resume session\", error);\n\t}\n}\n\nexport async function handleModelCommand(host: SessionFlowHost, searchTerm?: string): Promise<void> {\n\tif (!searchTerm) {\n\t\tawait showModelSelector(host);\n\t\treturn;\n\t}\n\n\tconst model = await findExactModelMatch(host, searchTerm);\n\tif (model) {\n\t\ttry {\n\t\t\tawait host.session.setModel(model);\n\t\t\thost.footer.invalidate();\n\t\t\thost.updateEditorBorderColor();\n\t\t\thost.showStatus(`Model: ${model.id}`);\n\t\t\tvoid host.maybeWarnAboutAnthropicSubscriptionAuth(model);\n\t\t\thost.checkDaxnutsEasterEgg(model);\n\t\t} catch (error) {\n\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t\treturn;\n\t}\n\n\tawait showModelSelector(host, searchTerm);\n}\n\nexport async function findExactModelMatch(host: SessionFlowHost, searchTerm: string): Promise<Model<any> | undefined> {\n\tconst models = await host.getModelCandidates();\n\treturn findExactModelReferenceMatch(searchTerm, models);\n}\n\n// ===========================================================================\n// Prototype-tested leaf commands — narrow host shapes matching their fakes.\n// ===========================================================================\n\nexport interface CloneCommandHost {\n\treadonly sessionManager: { getLeafId: () => string | null };\n\treadonly runtimeHost: {\n\t\tfork: (entryId: string, options?: { position?: \"before\" | \"at\" }) => Promise<{ cancelled: boolean }>;\n\t};\n\trenderCurrentSessionState(): void;\n\treadonly editor: { setText: (text: string) => void };\n\treadonly session: { setSessionName: (name: string) => void };\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\treadonly ui: { requestRender: () => void };\n}\n\nexport async function handleCloneCommand(host: CloneCommandHost, newSessionName?: string): Promise<void> {\n\tconst leafId = host.sessionManager.getLeafId();\n\tif (!leafId) {\n\t\thost.showStatus(\"Nothing to clone yet\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst result = await host.runtimeHost.fork(leafId, { position: \"at\" });\n\t\tif (result.cancelled) {\n\t\t\thost.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\thost.renderCurrentSessionState();\n\t\tif (newSessionName) {\n\t\t\thost.session.setSessionName(newSessionName);\n\t\t}\n\t\thost.editor.setText(\"\");\n\t\thost.showStatus(newSessionName ? `Cloned to new session: ${newSessionName}` : \"Cloned to new session\");\n\t} catch (error: unknown) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport type ParsedGoalContinueCommand =\n\t| { ok: true; maxTurns: number; maxStallTurns: number; maxWallClockMinutes: number }\n\t| { ok: false; error: string };\n\nexport interface GoalContinueCommandHost {\n\treadonly session: {\n\t\tcontinueGoalLoop: (options: GoalContinuationLoopOptions) => Promise<GoalContinuationLoopResult>;\n\t};\n\tparseGoalContinueCommand(text: string): ParsedGoalContinueCommand;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\trefreshAutonomyFooterStatus(): void;\n}\n\nexport async function handleGoalContinueCommand(host: GoalContinueCommandHost, text: string): Promise<void> {\n\tconst parsed = host.parseGoalContinueCommand(text);\n\tif (!parsed.ok) {\n\t\thost.showError(parsed.error);\n\t\treturn;\n\t}\n\n\thost.showStatus(\n\t\t`Goal continuation started: up to ${parsed.maxTurns} turn(s), stall limit ${parsed.maxStallTurns}, wall-clock limit ${parsed.maxWallClockMinutes || \"disabled\"} minute(s).`,\n\t);\n\ttry {\n\t\tconst result = await host.session.continueGoalLoop({\n\t\t\tmaxTurns: parsed.maxTurns,\n\t\t\tmaxStallTurns: parsed.maxStallTurns,\n\t\t\tmaxWallClockMinutes: parsed.maxWallClockMinutes,\n\t\t});\n\t\tconst continuation = result.finalSnapshot.continuation;\n\t\thost.showStatus(\n\t\t\t`Goal continuation stopped: ${result.stopReason}; submitted ${result.turnsSubmitted} turn(s); latest decision ${continuation.action}/${continuation.reasonCode}.`,\n\t\t);\n\t} catch (error) {\n\t\thost.showError(`Goal continuation failed: ${error instanceof Error ? error.message : String(error)}`);\n\t} finally {\n\t\thost.refreshAutonomyFooterStatus();\n\t}\n}\n\nexport interface SessionInfoCommandHost {\n\treadonly session: {\n\t\tgetSessionStats: () => ReturnType<AgentSession[\"getSessionStats\"]>;\n\t\tgetDailyUsageTotals: () => ReturnType<AgentSession[\"getDailyUsageTotals\"]>;\n\t\tgetDailyUsageBreakdown: (formatLabel: (label: string) => string) => string;\n\t\tgetModelRouterStatus: (formatLabel: (label: string) => string) => string;\n\t};\n\treadonly sessionManager: { getSessionName: () => string | undefined };\n\treadonly chatContainer: Container;\n\treadonly ui: { requestRender: () => void };\n}\n\nexport function handleSessionCommand(host: SessionInfoCommandHost): void {\n\tconst stats = host.session.getSessionStats();\n\tconst sessionName = host.sessionManager.getSessionName();\n\n\tlet info = `${theme.bold(\"Session Info\")}\\n\\n`;\n\tif (sessionName) {\n\t\tinfo += `${theme.fg(\"dim\", \"Name:\")} ${sessionName}\\n`;\n\t}\n\tinfo += `${theme.fg(\"dim\", \"File:\")} ${stats.sessionFile ?? \"In-memory\"}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"ID:\")} ${stats.sessionId}\\n\\n`;\n\tinfo += `${theme.bold(\"Messages\")}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"User:\")} ${stats.userMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Assistant:\")} ${stats.assistantMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Tool Calls:\")} ${stats.toolCalls}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Tool Results:\")} ${stats.toolResults}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.totalMessages}\\n\\n`;\n\tinfo += `${theme.bold(\"Tokens\")}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Input:\")} ${stats.tokens.input.toLocaleString()}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Output:\")} ${stats.tokens.output.toLocaleString()}\\n`;\n\tif (stats.tokens.cacheRead > 0) {\n\t\tinfo += `${theme.fg(\"dim\", \"Cache Read:\")} ${stats.tokens.cacheRead.toLocaleString()}\\n`;\n\t}\n\tif (stats.tokens.cacheWrite > 0) {\n\t\tinfo += `${theme.fg(\"dim\", \"Cache Write:\")} ${stats.tokens.cacheWrite.toLocaleString()}\\n`;\n\t}\n\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.tokens.total.toLocaleString()}\\n`;\n\n\tconst dailyUsage = host.session.getDailyUsageTotals();\n\tif (stats.cost > 0 || dailyUsage.totalCost > 0) {\n\t\tinfo += `\\n${theme.bold(\"Cost\")}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Total:\")} $${stats.cost.toFixed(4)}\\n`;\n\t\tinfo += `${host.session.getDailyUsageBreakdown((label) => theme.fg(\"dim\", label))}`;\n\t}\n\n\tinfo += `\\n\\n${theme.bold(\"Model Router\")}\\n`;\n\tinfo += host.session.getModelRouterStatus((label) => theme.fg(\"dim\", label));\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Text(info, 1, 0));\n\thost.ui.requestRender();\n}\n"]}
1
+ {"version":3,"file":"session-flow-commands.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/session-flow-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session-picker, tree/fork navigation, and model-selector flows extracted from\n * interactive-mode.\n *\n * Each entry opens a selector overlay (via `host.showSelector`) or renders a\n * session-info block, driving `AgentSession`/`AgentSessionRuntime` navigation and\n * the fork/resume/clone/goal-continuation paths. The interdependent selector\n * flows share one narrow `SessionFlowHost` seam; the three prototype-tested leaf\n * commands (`handleCloneCommand`, `handleGoalContinueCommand`,\n * `handleSessionCommand`) take their own minimal host shapes so their behaviour\n * tests keep exercising them through interactive-mode's thin wrappers unchanged.\n */\n\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Model } from \"@caupulican/pi-ai\";\nimport type { EditorComponent } from \"@caupulican/pi-tui\";\nimport { type Component, type Container, Loader, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport type {\n\tAgentSession,\n\tGoalContinuationLoopOptions,\n\tGoalContinuationLoopResult,\n} from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\nimport type { ExtensionCommandContext } from \"../../core/extensions/index.ts\";\nimport type { GoalState } from \"../../core/goals/goal-state.ts\";\nimport { applyGoalAction } from \"../../core/goals/goal-tool-core.ts\";\nimport type { KeybindingsManager } from \"../../core/keybindings.ts\";\nimport { findExactModelReferenceMatch, resolveModelScope } from \"../../core/model-resolver.ts\";\nimport { MissingSessionCwdError } from \"../../core/session-cwd.ts\";\nimport { listAllSessions, listSessions, openSession } from \"../../core/session-manager-factory.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { ProjectTrustStore } from \"../../core/trust-manager.ts\";\nimport type { CustomEditor } from \"./components/custom-editor.ts\";\nimport type { FooterComponent } from \"./components/footer.ts\";\nimport { keyText } from \"./components/keybinding-hints.ts\";\nimport { ModelSelectorComponent } from \"./components/model-selector.ts\";\nimport { ScopedModelsSelectorComponent } from \"./components/scoped-models-selector.ts\";\nimport { SessionSelectorComponent } from \"./components/session-selector.ts\";\nimport { TreeSelectorComponent } from \"./components/tree-selector.ts\";\nimport { TrustSelectorComponent } from \"./components/trust-selector.ts\";\nimport { UserMessageSelectorComponent } from \"./components/user-message-selector.ts\";\nimport type { ExtensionUiHost } from \"./extension-ui-host.ts\";\nimport { theme } from \"./theme/theme.ts\";\n\n/** Shared seam for the interdependent selector/navigation flows. */\nexport interface SessionFlowHost {\n\treadonly session: AgentSession;\n\treadonly sessionManager: SessionManager;\n\treadonly settingsManager: SettingsManager;\n\treadonly runtimeHost: AgentSessionRuntime;\n\treadonly ui: TUI;\n\treadonly chatContainer: Container;\n\treadonly statusContainer: Container;\n\treadonly editor: EditorComponent;\n\treadonly defaultEditor: CustomEditor;\n\treadonly footer: FooterComponent;\n\treadonly extensionUiHost: ExtensionUiHost;\n\treadonly keybindings: KeybindingsManager;\n\tloadingAnimation: Loader | undefined;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\trenderCurrentSessionState(): void;\n\trenderInitialMessages(options?: { forceHistoryLoad?: boolean }): Promise<void>;\n\tflushCompactionQueue(options?: { willRetry?: boolean }): Promise<void>;\n\thandleFatalRuntimeError(prefix: string, error: unknown): Promise<never>;\n\tpromptForMissingSessionCwd(error: MissingSessionCwdError): Promise<string | undefined>;\n\tupdateEditorBorderColor(): void;\n\tupdateAvailableProviderCount(): Promise<void>;\n\tmaybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;\n\tcheckDaxnutsEasterEgg(model: { provider: string; id: string }): void;\n\tgetModelCandidates(): Promise<Model<any>[]>;\n\tshutdown(options?: { fromSignal?: boolean }): Promise<void>;\n}\n\nexport async function showModelSelector(host: SessionFlowHost, initialSearchInput?: string): Promise<void> {\n\ttry {\n\t\tawait host.session.extensionRunner.emit({\n\t\t\ttype: \"model_selector_open\",\n\t\t\tcurrentModel: host.session.model,\n\t\t\tscopedModels: host.session.scopedModels,\n\t\t\tinitialSearchInput,\n\t\t});\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\treturn;\n\t}\n\n\thost.showSelector((done) => {\n\t\tconst selector = new ModelSelectorComponent(\n\t\t\thost.ui,\n\t\t\thost.session.model,\n\t\t\thost.settingsManager,\n\t\t\thost.session.modelRegistry,\n\t\t\thost.session.scopedModels,\n\t\t\tasync (model) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait host.session.setModel(model);\n\t\t\t\t\thost.footer.invalidate();\n\t\t\t\t\thost.updateEditorBorderColor();\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showStatus(`Model: ${model.id}`);\n\t\t\t\t\tvoid host.maybeWarnAboutAnthropicSubscriptionAuth(model);\n\t\t\t\t\thost.checkDaxnutsEasterEgg(model);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\tinitialSearchInput,\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport async function showModelsSelector(host: SessionFlowHost): Promise<void> {\n\t// Get all available models\n\thost.session.modelRegistry.refresh();\n\tconst allModels = host.session.modelRegistry.getAvailable();\n\n\tif (allModels.length === 0) {\n\t\thost.showStatus(\"No models available\");\n\t\treturn;\n\t}\n\n\t// Check if session has scoped models (from previous session-only changes or CLI --models)\n\tconst sessionScopedModels = host.session.scopedModels;\n\tconst hasSessionScope = sessionScopedModels.length > 0;\n\n\t// Build enabled model IDs from session state or settings\n\tlet currentEnabledIds: string[] | null = null;\n\n\tif (hasSessionScope) {\n\t\t// Use current session's scoped models\n\t\tcurrentEnabledIds = sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);\n\t} else {\n\t\t// Fall back to settings\n\t\tconst patterns = host.settingsManager.getEnabledModels();\n\t\tif (patterns !== undefined && patterns.length > 0) {\n\t\t\tconst scopedModels = await resolveModelScope(patterns, host.session.modelRegistry);\n\t\t\tcurrentEnabledIds = scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);\n\t\t}\n\t}\n\n\t// Helper to update session's scoped models (session-only, no persist)\n\tconst updateSessionModels = async (enabledIds: string[] | null) => {\n\t\tcurrentEnabledIds = enabledIds === null ? null : [...enabledIds];\n\t\tif (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {\n\t\t\tconst newScopedModels = await resolveModelScope(enabledIds, host.session.modelRegistry);\n\t\t\thost.session.setScopedModels(\n\t\t\t\tnewScopedModels.map((sm) => ({\n\t\t\t\t\tmodel: sm.model,\n\t\t\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t\t\t})),\n\t\t\t);\n\t\t} else {\n\t\t\t// All enabled or none enabled = no filter\n\t\t\thost.session.setScopedModels([]);\n\t\t}\n\t\tawait host.updateAvailableProviderCount();\n\t\thost.ui.requestRender();\n\t};\n\n\thost.showSelector((done) => {\n\t\tconst selector = new ScopedModelsSelectorComponent(\n\t\t\t{\n\t\t\t\tallModels,\n\t\t\t\tenabledModelIds: currentEnabledIds,\n\t\t\t},\n\t\t\t{\n\t\t\t\tonChange: async (enabledIds) => {\n\t\t\t\t\tawait updateSessionModels(enabledIds);\n\t\t\t\t},\n\t\t\t\tonPersist: (enabledIds) => {\n\t\t\t\t\t// Persist to settings\n\t\t\t\t\tconst newPatterns =\n\t\t\t\t\t\tenabledIds === null || enabledIds.length === allModels.length\n\t\t\t\t\t\t\t? undefined // All enabled = clear filter\n\t\t\t\t\t\t\t: enabledIds;\n\t\t\t\t\thost.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);\n\t\t\t\t\thost.showStatus(\"Model selection saved to settings\");\n\t\t\t\t},\n\t\t\t\tonCancel: () => {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showUserMessageSelector(host: SessionFlowHost, newSessionName?: string): void {\n\tconst userMessages = host.session.getUserMessagesForForking();\n\n\tif (userMessages.length === 0) {\n\t\thost.showStatus(\"No messages to fork from\");\n\t\treturn;\n\t}\n\n\tconst initialSelectedId = userMessages[userMessages.length - 1]?.entryId;\n\n\thost.showSelector((done) => {\n\t\tconst selector = new UserMessageSelectorComponent(\n\t\t\tuserMessages.map((m) => ({ id: m.entryId, text: m.text })),\n\t\t\tasync (entryId) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await host.runtimeHost.fork(entryId);\n\t\t\t\t\tif (result.cancelled) {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\thost.renderCurrentSessionState();\n\t\t\t\t\tif (newSessionName) {\n\t\t\t\t\t\thost.session.setSessionName(newSessionName);\n\t\t\t\t\t}\n\t\t\t\t\thost.editor.setText(result.selectedText ?? \"\");\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showStatus(newSessionName ? `Forked to new session: ${newSessionName}` : \"Forked to new session\");\n\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\tinitialSelectedId,\n\t\t);\n\t\treturn { component: selector, focus: selector.getMessageList() };\n\t});\n}\n\nexport function showTreeSelector(host: SessionFlowHost, initialSelectedId?: string): void {\n\tconst tree = host.sessionManager.getTree();\n\tconst realLeafId = host.sessionManager.getLeafId();\n\tconst initialFilterMode = host.settingsManager.getTreeFilterMode();\n\n\tif (tree.length === 0) {\n\t\thost.showStatus(\"No entries in session\");\n\t\treturn;\n\t}\n\n\thost.showSelector((done) => {\n\t\tconst selector = new TreeSelectorComponent(\n\t\t\ttree,\n\t\t\trealLeafId,\n\t\t\thost.ui.terminal.rows,\n\t\t\tasync (entryId) => {\n\t\t\t\t// Selecting the current leaf is a no-op (already there)\n\t\t\t\tif (entryId === realLeafId) {\n\t\t\t\t\tdone();\n\t\t\t\t\thost.showStatus(\"Already at this point\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Ask about summarization\n\t\t\t\tdone(); // Close selector first\n\n\t\t\t\t// Loop until user makes a complete choice or cancels to tree\n\t\t\t\tlet wantsSummary = false;\n\t\t\t\tlet customInstructions: string | undefined;\n\n\t\t\t\t// Check if we should skip the prompt (user preference to always default to no summary)\n\t\t\t\tif (!host.settingsManager.getBranchSummarySkipPrompt()) {\n\t\t\t\t\twhile (true) {\n\t\t\t\t\t\tconst summaryChoice = await host.extensionUiHost.showExtensionSelector(\"Summarize branch?\", [\n\t\t\t\t\t\t\t\"No summary\",\n\t\t\t\t\t\t\t\"Summarize\",\n\t\t\t\t\t\t\t\"Summarize with custom prompt\",\n\t\t\t\t\t\t]);\n\n\t\t\t\t\t\tif (summaryChoice === undefined) {\n\t\t\t\t\t\t\t// User pressed escape - re-show tree selector with same selection\n\t\t\t\t\t\t\tshowTreeSelector(host, entryId);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\twantsSummary = summaryChoice !== \"No summary\";\n\n\t\t\t\t\t\tif (summaryChoice === \"Summarize with custom prompt\") {\n\t\t\t\t\t\t\tcustomInstructions = await host.extensionUiHost.showExtensionEditor(\n\t\t\t\t\t\t\t\t\"Custom summarization instructions\",\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (customInstructions === undefined) {\n\t\t\t\t\t\t\t\t// User cancelled - loop back to summary selector\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// User made a complete choice\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Set up escape handler and loader if summarizing\n\t\t\t\tlet summaryLoader: Loader | undefined;\n\t\t\t\tconst originalOnEscape = host.defaultEditor.onEscape;\n\n\t\t\t\tif (wantsSummary) {\n\t\t\t\t\thost.defaultEditor.onEscape = () => {\n\t\t\t\t\t\thost.session.abortBranchSummary();\n\t\t\t\t\t};\n\t\t\t\t\thost.chatContainer.addChild(new Spacer(1));\n\t\t\t\t\tsummaryLoader = new Loader(\n\t\t\t\t\t\thost.ui,\n\t\t\t\t\t\t(spinner) => theme.fg(\"accent\", spinner),\n\t\t\t\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t\t\t\t`Summarizing branch... (${keyText(\"app.interrupt\")} to cancel)`,\n\t\t\t\t\t);\n\t\t\t\t\thost.statusContainer.addChild(summaryLoader);\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await host.session.navigateTree(entryId, {\n\t\t\t\t\t\tsummarize: wantsSummary,\n\t\t\t\t\t\tcustomInstructions,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (result.aborted) {\n\t\t\t\t\t\t// Summarization aborted - re-show tree selector with same selection\n\t\t\t\t\t\thost.showStatus(\"Branch summarization cancelled\");\n\t\t\t\t\t\tshowTreeSelector(host, entryId);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (result.cancelled) {\n\t\t\t\t\t\thost.showStatus(\"Navigation cancelled\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update UI\n\t\t\t\t\tawait host.renderInitialMessages();\n\t\t\t\t\tif (result.editorText && !host.editor.getText().trim()) {\n\t\t\t\t\t\thost.editor.setText(result.editorText);\n\t\t\t\t\t}\n\t\t\t\t\thost.showStatus(\"Navigated to selected point\");\n\t\t\t\t\tvoid host.flushCompactionQueue({ willRetry: false });\n\t\t\t\t} catch (error) {\n\t\t\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\t\t} finally {\n\t\t\t\t\tif (summaryLoader) {\n\t\t\t\t\t\tsummaryLoader.stop();\n\t\t\t\t\t\thost.statusContainer.clear();\n\t\t\t\t\t}\n\t\t\t\t\thost.defaultEditor.onEscape = originalOnEscape;\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\t(entryId, label) => {\n\t\t\t\thost.sessionManager.appendLabelChange(entryId, label);\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\tinitialSelectedId,\n\t\t\tinitialFilterMode,\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showTrustSelector(host: SessionFlowHost): void {\n\tconst cwd = host.sessionManager.getCwd();\n\tconst trustStore = new ProjectTrustStore(host.runtimeHost.services.agentDir);\n\tconst savedDecision = trustStore.get(cwd);\n\thost.showSelector((done) => {\n\t\tconst selector = new TrustSelectorComponent({\n\t\t\tcwd,\n\t\t\tsavedDecision,\n\t\t\tprojectTrusted: host.settingsManager.isProjectTrusted(),\n\t\t\tonSelect: (trusted) => {\n\t\t\t\ttrustStore.set(cwd, trusted);\n\t\t\t\tdone();\n\t\t\t\thost.showStatus(\n\t\t\t\t\t`Saved trust decision: ${trusted ? \"trusted\" : \"untrusted\"}. Restart pi for this to take effect.`,\n\t\t\t\t);\n\t\t\t},\n\t\t\tonCancel: () => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t});\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport function showSessionSelector(host: SessionFlowHost): void {\n\thost.showSelector((done) => {\n\t\tconst selector = new SessionSelectorComponent(\n\t\t\t(onProgress) => listSessions(host.sessionManager.getCwd(), host.sessionManager.getSessionDir(), onProgress),\n\t\t\t(onProgress) =>\n\t\t\t\thost.sessionManager.usesDefaultSessionDir()\n\t\t\t\t\t? listAllSessions(onProgress)\n\t\t\t\t\t: listAllSessions(host.sessionManager.getSessionDir(), onProgress),\n\t\t\tasync (sessionPath) => {\n\t\t\t\tdone();\n\t\t\t\tawait handleResumeSession(host, sessionPath);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tdone();\n\t\t\t\thost.ui.requestRender();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tvoid host.shutdown();\n\t\t\t},\n\t\t\t() => host.ui.requestRender(),\n\t\t\t{\n\t\t\t\trenameSession: async (sessionFilePath: string, nextName: string | undefined) => {\n\t\t\t\t\tconst next = (nextName ?? \"\").trim();\n\t\t\t\t\tif (!next) return;\n\t\t\t\t\tconst mgr = openSession(sessionFilePath);\n\t\t\t\t\tmgr.appendSessionInfo(next);\n\t\t\t\t},\n\t\t\t\tshowRenameHint: true,\n\t\t\t\tkeybindings: host.keybindings,\n\t\t\t},\n\n\t\t\thost.sessionManager.getSessionFile(),\n\t\t);\n\t\treturn { component: selector, focus: selector };\n\t});\n}\n\nexport async function handleResumeSession(\n\thost: SessionFlowHost,\n\tsessionPath: string,\n\toptions?: Parameters<ExtensionCommandContext[\"switchSession\"]>[1],\n): Promise<{ cancelled: boolean }> {\n\tif (host.loadingAnimation) {\n\t\thost.loadingAnimation.stop();\n\t\thost.loadingAnimation = undefined;\n\t}\n\thost.statusContainer.clear();\n\ttry {\n\t\tconst result = await host.runtimeHost.switchSession(sessionPath, {\n\t\t\twithSession: options?.withSession,\n\t\t});\n\t\tif (result.cancelled) {\n\t\t\treturn result;\n\t\t}\n\t\thost.renderCurrentSessionState();\n\t\thost.showStatus(\"Resumed session\");\n\t\treturn result;\n\t} catch (error: unknown) {\n\t\tif (error instanceof MissingSessionCwdError) {\n\t\t\tconst selectedCwd = await host.promptForMissingSessionCwd(error);\n\t\t\tif (!selectedCwd) {\n\t\t\t\thost.showStatus(\"Resume cancelled\");\n\t\t\t\treturn { cancelled: true };\n\t\t\t}\n\t\t\tconst result = await host.runtimeHost.switchSession(sessionPath, {\n\t\t\t\tcwdOverride: selectedCwd,\n\t\t\t\twithSession: options?.withSession,\n\t\t\t});\n\t\t\tif (result.cancelled) {\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\thost.renderCurrentSessionState();\n\t\t\thost.showStatus(\"Resumed session in current cwd\");\n\t\t\treturn result;\n\t\t}\n\t\treturn host.handleFatalRuntimeError(\"Failed to resume session\", error);\n\t}\n}\n\nexport async function handleModelCommand(host: SessionFlowHost, searchTerm?: string): Promise<void> {\n\tif (!searchTerm) {\n\t\tawait showModelSelector(host);\n\t\treturn;\n\t}\n\n\tconst model = await findExactModelMatch(host, searchTerm);\n\tif (model) {\n\t\ttry {\n\t\t\tawait host.session.setModel(model);\n\t\t\thost.footer.invalidate();\n\t\t\thost.updateEditorBorderColor();\n\t\t\thost.showStatus(`Model: ${model.id}`);\n\t\t\tvoid host.maybeWarnAboutAnthropicSubscriptionAuth(model);\n\t\t\thost.checkDaxnutsEasterEgg(model);\n\t\t} catch (error) {\n\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t\treturn;\n\t}\n\n\tawait showModelSelector(host, searchTerm);\n}\n\nexport async function findExactModelMatch(host: SessionFlowHost, searchTerm: string): Promise<Model<any> | undefined> {\n\tconst models = await host.getModelCandidates();\n\treturn findExactModelReferenceMatch(searchTerm, models);\n}\n\n// ===========================================================================\n// Prototype-tested leaf commands — narrow host shapes matching their fakes.\n// ===========================================================================\n\nexport interface CloneCommandHost {\n\treadonly sessionManager: { getLeafId: () => string | null };\n\treadonly runtimeHost: {\n\t\tfork: (entryId: string, options?: { position?: \"before\" | \"at\" }) => Promise<{ cancelled: boolean }>;\n\t};\n\trenderCurrentSessionState(): void;\n\treadonly editor: { setText: (text: string) => void };\n\treadonly session: { setSessionName: (name: string) => void };\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\treadonly ui: { requestRender: () => void };\n}\n\nexport async function handleCloneCommand(host: CloneCommandHost, newSessionName?: string): Promise<void> {\n\tconst leafId = host.sessionManager.getLeafId();\n\tif (!leafId) {\n\t\thost.showStatus(\"Nothing to clone yet\");\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst result = await host.runtimeHost.fork(leafId, { position: \"at\" });\n\t\tif (result.cancelled) {\n\t\t\thost.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\thost.renderCurrentSessionState();\n\t\tif (newSessionName) {\n\t\t\thost.session.setSessionName(newSessionName);\n\t\t}\n\t\thost.editor.setText(\"\");\n\t\thost.showStatus(newSessionName ? `Cloned to new session: ${newSessionName}` : \"Cloned to new session\");\n\t} catch (error: unknown) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport type ParsedGoalContinueCommand =\n\t| { ok: true; maxTurns: number; maxStallTurns: number; maxWallClockMinutes: number }\n\t| { ok: false; error: string };\n\nexport interface GoalCommandHost {\n\treadonly session: {\n\t\tgetGoalStateSnapshot: () => GoalState | undefined;\n\t\tsaveGoalStateSnapshot: (state: GoalState) => string;\n\t\tsendUserMessage: (content: string) => Promise<void>;\n\t\tcontinueGoalLoop: (options: GoalContinuationLoopOptions) => Promise<GoalContinuationLoopResult>;\n\t\tgetGoalRuntimeSnapshot: (settings: {\n\t\t\tmaxStallTurns: number;\n\t\t}) => ReturnType<AgentSession[\"getGoalRuntimeSnapshot\"]>;\n\t};\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\trefreshAutonomyFooterStatus(): void;\n}\n\nfunction formatGoalStatus(snapshot: ReturnType<AgentSession[\"getGoalRuntimeSnapshot\"]>): string {\n\tconst state = snapshot.goalState;\n\tif (!state) return `Goal: none (${snapshot.continuation.action}/${snapshot.continuation.reasonCode})`;\n\treturn `Goal: ${state.userGoal}\\nStatus: ${state.status}\\nRequirements: open ${snapshot.continuation.openRequirementIds.length}, blocked ${snapshot.continuation.blockedRequirementIds.length}, satisfied ${snapshot.continuation.satisfiedRequirementIds.length}\\nContinuation: ${snapshot.continuation.action}/${snapshot.continuation.reasonCode}`;\n}\n\nexport async function handleGoalCommand(host: GoalCommandHost, text: string): Promise<void> {\n\tconst goalText = text.replace(/^\\/goal\\s*/, \"\").trim();\n\tif (!goalText) {\n\t\thost.showStatus(formatGoalStatus(host.session.getGoalRuntimeSnapshot({ maxStallTurns: 20 })));\n\t\treturn;\n\t}\n\tconst now = new Date().toISOString();\n\tconst goalId = `goal-${Date.now().toString(36)}`;\n\tconst started = applyGoalAction(\n\t\thost.session.getGoalStateSnapshot(),\n\t\t{ action: \"start\", goalId, userGoal: goalText },\n\t\tnow,\n\t);\n\tif (!started.ok) {\n\t\thost.showError(started.error);\n\t\treturn;\n\t}\n\thost.session.saveGoalStateSnapshot(started.state);\n\tawait host.session.sendUserMessage(\n\t\t`Start goal: ${goalText}\\n\\nUse the goal tool this turn to decompose this goal into concrete open requirements with add_requirement actions.`,\n\t);\n\tconst result = await host.session.continueGoalLoop({ maxTurns: 1, maxStallTurns: 20, maxWallClockMinutes: 0 });\n\tconst continuation = result.finalSnapshot.continuation;\n\tif (continuation.reasonCode === \"no_open_requirements\") {\n\t\thost.showStatus(\n\t\t\t\"Goal started, but no open requirements were added (no_open_requirements). Use the goal tool to add requirements.\",\n\t\t);\n\t} else {\n\t\thost.showStatus(`Goal started: ${continuation.action}/${continuation.reasonCode}.`);\n\t}\n\thost.refreshAutonomyFooterStatus();\n}\n\nexport interface GoalContinueCommandHost {\n\treadonly session: {\n\t\tcontinueGoalLoop: (options: GoalContinuationLoopOptions) => Promise<GoalContinuationLoopResult>;\n\t};\n\tparseGoalContinueCommand(text: string): ParsedGoalContinueCommand;\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n\trefreshAutonomyFooterStatus(): void;\n}\n\nexport async function handleGoalContinueCommand(host: GoalContinueCommandHost, text: string): Promise<void> {\n\tconst parsed = host.parseGoalContinueCommand(text);\n\tif (!parsed.ok) {\n\t\thost.showError(parsed.error);\n\t\treturn;\n\t}\n\n\thost.showStatus(\n\t\t`Goal continuation started: up to ${parsed.maxTurns} turn(s), stall limit ${parsed.maxStallTurns}, wall-clock limit ${parsed.maxWallClockMinutes || \"disabled\"} minute(s).`,\n\t);\n\ttry {\n\t\tconst result = await host.session.continueGoalLoop({\n\t\t\tmaxTurns: parsed.maxTurns,\n\t\t\tmaxStallTurns: parsed.maxStallTurns,\n\t\t\tmaxWallClockMinutes: parsed.maxWallClockMinutes,\n\t\t});\n\t\tconst continuation = result.finalSnapshot.continuation;\n\t\thost.showStatus(\n\t\t\t`Goal continuation stopped: ${result.stopReason}; submitted ${result.turnsSubmitted} turn(s); latest decision ${continuation.action}/${continuation.reasonCode}.`,\n\t\t);\n\t} catch (error) {\n\t\thost.showError(`Goal continuation failed: ${error instanceof Error ? error.message : String(error)}`);\n\t} finally {\n\t\thost.refreshAutonomyFooterStatus();\n\t}\n}\n\nexport interface SessionInfoCommandHost {\n\treadonly session: {\n\t\tgetSessionStats: () => ReturnType<AgentSession[\"getSessionStats\"]>;\n\t\tgetDailyUsageTotals: () => ReturnType<AgentSession[\"getDailyUsageTotals\"]>;\n\t\tgetDailyUsageBreakdown: (formatLabel: (label: string) => string) => string;\n\t\tgetModelRouterStatus: (formatLabel: (label: string) => string) => string;\n\t};\n\treadonly sessionManager: { getSessionName: () => string | undefined };\n\treadonly chatContainer: Container;\n\treadonly ui: { requestRender: () => void };\n}\n\nexport function handleSessionCommand(host: SessionInfoCommandHost): void {\n\tconst stats = host.session.getSessionStats();\n\tconst sessionName = host.sessionManager.getSessionName();\n\n\tlet info = `${theme.bold(\"Session Info\")}\\n\\n`;\n\tif (sessionName) {\n\t\tinfo += `${theme.fg(\"dim\", \"Name:\")} ${sessionName}\\n`;\n\t}\n\tinfo += `${theme.fg(\"dim\", \"File:\")} ${stats.sessionFile ?? \"In-memory\"}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"ID:\")} ${stats.sessionId}\\n\\n`;\n\tinfo += `${theme.bold(\"Messages\")}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"User:\")} ${stats.userMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Assistant:\")} ${stats.assistantMessages}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Tool Calls:\")} ${stats.toolCalls}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Tool Results:\")} ${stats.toolResults}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.totalMessages}\\n\\n`;\n\tinfo += `${theme.bold(\"Tokens\")}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Input:\")} ${stats.tokens.input.toLocaleString()}\\n`;\n\tinfo += `${theme.fg(\"dim\", \"Output:\")} ${stats.tokens.output.toLocaleString()}\\n`;\n\tif (stats.tokens.cacheRead > 0) {\n\t\tinfo += `${theme.fg(\"dim\", \"Cache Read:\")} ${stats.tokens.cacheRead.toLocaleString()}\\n`;\n\t}\n\tif (stats.tokens.cacheWrite > 0) {\n\t\tinfo += `${theme.fg(\"dim\", \"Cache Write:\")} ${stats.tokens.cacheWrite.toLocaleString()}\\n`;\n\t}\n\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.tokens.total.toLocaleString()}\\n`;\n\n\tconst dailyUsage = host.session.getDailyUsageTotals();\n\tif (stats.cost > 0 || dailyUsage.totalCost > 0) {\n\t\tinfo += `\\n${theme.bold(\"Cost\")}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Total:\")} $${stats.cost.toFixed(4)}\\n`;\n\t\tinfo += `${host.session.getDailyUsageBreakdown((label) => theme.fg(\"dim\", label))}`;\n\t}\n\n\tinfo += `\\n\\n${theme.bold(\"Model Router\")}\\n`;\n\tinfo += host.session.getModelRouterStatus((label) => theme.fg(\"dim\", label));\n\n\thost.chatContainer.addChild(new Spacer(1));\n\thost.chatContainer.addChild(new Text(info, 1, 0));\n\thost.ui.requestRender();\n}\n"]}
@@ -11,6 +11,7 @@
11
11
  * tests keep exercising them through interactive-mode's thin wrappers unchanged.
12
12
  */
13
13
  import { Loader, Spacer, Text } from "@caupulican/pi-tui";
14
+ import { applyGoalAction } from "../../core/goals/goal-tool-core.js";
14
15
  import { findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
15
16
  import { MissingSessionCwdError } from "../../core/session-cwd.js";
16
17
  import { listAllSessions, listSessions, openSession } from "../../core/session-manager-factory.js";
@@ -395,6 +396,37 @@ export async function handleCloneCommand(host, newSessionName) {
395
396
  host.showError(error instanceof Error ? error.message : String(error));
396
397
  }
397
398
  }
399
+ function formatGoalStatus(snapshot) {
400
+ const state = snapshot.goalState;
401
+ if (!state)
402
+ return `Goal: none (${snapshot.continuation.action}/${snapshot.continuation.reasonCode})`;
403
+ return `Goal: ${state.userGoal}\nStatus: ${state.status}\nRequirements: open ${snapshot.continuation.openRequirementIds.length}, blocked ${snapshot.continuation.blockedRequirementIds.length}, satisfied ${snapshot.continuation.satisfiedRequirementIds.length}\nContinuation: ${snapshot.continuation.action}/${snapshot.continuation.reasonCode}`;
404
+ }
405
+ export async function handleGoalCommand(host, text) {
406
+ const goalText = text.replace(/^\/goal\s*/, "").trim();
407
+ if (!goalText) {
408
+ host.showStatus(formatGoalStatus(host.session.getGoalRuntimeSnapshot({ maxStallTurns: 20 })));
409
+ return;
410
+ }
411
+ const now = new Date().toISOString();
412
+ const goalId = `goal-${Date.now().toString(36)}`;
413
+ const started = applyGoalAction(host.session.getGoalStateSnapshot(), { action: "start", goalId, userGoal: goalText }, now);
414
+ if (!started.ok) {
415
+ host.showError(started.error);
416
+ return;
417
+ }
418
+ host.session.saveGoalStateSnapshot(started.state);
419
+ await host.session.sendUserMessage(`Start goal: ${goalText}\n\nUse the goal tool this turn to decompose this goal into concrete open requirements with add_requirement actions.`);
420
+ const result = await host.session.continueGoalLoop({ maxTurns: 1, maxStallTurns: 20, maxWallClockMinutes: 0 });
421
+ const continuation = result.finalSnapshot.continuation;
422
+ if (continuation.reasonCode === "no_open_requirements") {
423
+ host.showStatus("Goal started, but no open requirements were added (no_open_requirements). Use the goal tool to add requirements.");
424
+ }
425
+ else {
426
+ host.showStatus(`Goal started: ${continuation.action}/${continuation.reasonCode}.`);
427
+ }
428
+ host.refreshAutonomyFooterStatus();
429
+ }
398
430
  export async function handleGoalContinueCommand(host, text) {
399
431
  const parsed = host.parseGoalContinueCommand(text);
400
432
  if (!parsed.ok) {