@caupulican/pi-adaptative 0.81.32 → 0.81.37
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.
- package/CHANGELOG.md +46 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +69 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +53 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +1282 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +1453 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/builder-validator.json +14 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/claude-trio.json +32 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/full-provider-review.json +15 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/provider-prompt-smoke.json +14 -0
- package/dist/bundled-resources/prompts/extensionify.md +5 -4
- package/dist/bundled-resources/prompts/skillify.md +5 -5
- package/dist/bundled-resources/skills/pi-harness-learning/SKILL.md +6 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +10 -8
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts +5 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +42 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
- package/dist/core/autonomy/approval-gate.js +2 -0
- package/dist/core/autonomy/approval-gate.js.map +1 -1
- package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
- package/dist/core/autonomy/foreground-envelope.js +2 -0
- package/dist/core/autonomy/foreground-envelope.js.map +1 -1
- package/dist/core/autonomy/gates.d.ts.map +1 -1
- package/dist/core/autonomy/gates.js +9 -2
- package/dist/core/autonomy/gates.js.map +1 -1
- package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
- package/dist/core/autonomy/risk-assessment.js +2 -0
- package/dist/core/autonomy/risk-assessment.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +12 -3
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +67 -26
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/bash-execution-controller.d.ts +9 -4
- package/dist/core/bash-execution-controller.d.ts.map +1 -1
- package/dist/core/bash-execution-controller.js +7 -6
- package/dist/core/bash-execution-controller.js.map +1 -1
- package/dist/core/bash-executor.d.ts +2 -0
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +10 -5
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/context/context-store-retention.d.ts +4 -1
- package/dist/core/context/context-store-retention.d.ts.map +1 -1
- package/dist/core/context/context-store-retention.js +42 -151
- package/dist/core/context/context-store-retention.js.map +1 -1
- package/dist/core/context-pipeline.d.ts.map +1 -1
- package/dist/core/context-pipeline.js +6 -6
- package/dist/core/context-pipeline.js.map +1 -1
- package/dist/core/default-tool-surface.d.ts +7 -0
- package/dist/core/default-tool-surface.d.ts.map +1 -1
- package/dist/core/default-tool-surface.js +29 -10
- package/dist/core/default-tool-surface.js.map +1 -1
- package/dist/core/doctor.d.ts +10 -10
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +30 -20
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/exec.d.ts +2 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +26 -39
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +9 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +105 -21
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +13 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +3 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +2 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/goals/goal-state.d.ts +10 -0
- package/dist/core/goals/goal-state.d.ts.map +1 -1
- package/dist/core/goals/goal-state.js +37 -0
- package/dist/core/goals/goal-state.js.map +1 -1
- package/dist/core/goals/goal-tool-core.d.ts +11 -0
- package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
- package/dist/core/goals/goal-tool-core.js +51 -1
- package/dist/core/goals/goal-tool-core.js.map +1 -1
- package/dist/core/improvement-loop.d.ts.map +1 -1
- package/dist/core/improvement-loop.js +26 -5
- package/dist/core/improvement-loop.js.map +1 -1
- package/dist/core/memory/memory-manager.d.ts.map +1 -1
- package/dist/core/memory/memory-manager.js +1 -0
- package/dist/core/memory/memory-manager.js.map +1 -1
- package/dist/core/model-capability.d.ts.map +1 -1
- package/dist/core/model-capability.js +2 -0
- package/dist/core/model-capability.js.map +1 -1
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
- package/dist/core/model-router/tool-escalation.js +17 -3
- package/dist/core/model-router/tool-escalation.js.map +1 -1
- package/dist/core/models/fitness-store.d.ts.map +1 -1
- package/dist/core/models/fitness-store.js +1 -1
- package/dist/core/models/fitness-store.js.map +1 -1
- package/dist/core/models/local-runtime.d.ts +6 -4
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +108 -36
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +23 -8
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +44 -59
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/profile-resource-selection.d.ts +16 -0
- package/dist/core/profile-resource-selection.d.ts.map +1 -1
- package/dist/core/profile-resource-selection.js +41 -0
- package/dist/core/profile-resource-selection.js.map +1 -1
- package/dist/core/python-runtime.d.ts +46 -0
- package/dist/core/python-runtime.d.ts.map +1 -0
- package/dist/core/python-runtime.js +157 -0
- package/dist/core/python-runtime.js.map +1 -0
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +18 -1
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/reload-blockers.d.ts +1 -0
- package/dist/core/reload-blockers.d.ts.map +1 -1
- package/dist/core/reload-blockers.js +6 -2
- package/dist/core/reload-blockers.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +50 -28
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +4 -0
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +31 -9
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/security/untrusted-boundary.d.ts +2 -2
- package/dist/core/security/untrusted-boundary.d.ts.map +1 -1
- package/dist/core/security/untrusted-boundary.js +4 -3
- package/dist/core/security/untrusted-boundary.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +3 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +9 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +13 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tasks/session-task-state.d.ts +10 -0
- package/dist/core/tasks/session-task-state.d.ts.map +1 -0
- package/dist/core/tasks/session-task-state.js +29 -0
- package/dist/core/tasks/session-task-state.js.map +1 -0
- package/dist/core/tasks/task-command.d.ts +30 -0
- package/dist/core/tasks/task-command.d.ts.map +1 -0
- package/dist/core/tasks/task-command.js +79 -0
- package/dist/core/tasks/task-command.js.map +1 -0
- package/dist/core/tasks/task-state.d.ts +71 -0
- package/dist/core/tasks/task-state.d.ts.map +1 -0
- package/dist/core/tasks/task-state.js +372 -0
- package/dist/core/tasks/task-state.js.map +1 -0
- package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
- package/dist/core/tool-selection/tool-selection-controller.js +1 -1
- package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
- package/dist/core/toolkit/script-runner.d.ts.map +1 -1
- package/dist/core/toolkit/script-runner.js +58 -24
- package/dist/core/toolkit/script-runner.js.map +1 -1
- package/dist/core/tools/bash.d.ts +20 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +132 -220
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/extensionify.d.ts +2 -1
- package/dist/core/tools/extensionify.d.ts.map +1 -1
- package/dist/core/tools/extensionify.js +11 -8
- package/dist/core/tools/extensionify.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +57 -48
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/git-filter.d.ts.map +1 -1
- package/dist/core/tools/git-filter.js +20 -32
- package/dist/core/tools/git-filter.js.map +1 -1
- package/dist/core/tools/goal.d.ts +1 -1
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +10 -3
- package/dist/core/tools/goal.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +92 -80
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +9 -6
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +24 -9
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +2 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +7 -4
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/python.d.ts +64 -0
- package/dist/core/tools/python.d.ts.map +1 -0
- package/dist/core/tools/python.js +241 -0
- package/dist/core/tools/python.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -3
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/shell-contract-router.d.ts +13 -0
- package/dist/core/tools/shell-contract-router.d.ts.map +1 -0
- package/dist/core/tools/shell-contract-router.js +295 -0
- package/dist/core/tools/shell-contract-router.js.map +1 -0
- package/dist/core/tools/task-steps.d.ts +46 -0
- package/dist/core/tools/task-steps.d.ts.map +1 -0
- package/dist/core/tools/task-steps.js +160 -0
- package/dist/core/tools/task-steps.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +23 -4
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +18 -4
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.d.ts +10 -0
- package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.js +94 -27
- package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.js +2 -1
- package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +1 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +8 -0
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +6 -15
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +3 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +20 -8
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts +4 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.js +22 -39
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/config-backup.d.ts +1 -0
- package/dist/modes/interactive/config-backup.d.ts.map +1 -1
- package/dist/modes/interactive/config-backup.js +14 -3
- package/dist/modes/interactive/config-backup.js.map +1 -1
- package/dist/modes/interactive/editor-overlay-host.d.ts +12 -0
- package/dist/modes/interactive/editor-overlay-host.d.ts.map +1 -1
- package/dist/modes/interactive/editor-overlay-host.js +55 -0
- package/dist/modes/interactive/editor-overlay-host.js.map +1 -1
- package/dist/modes/interactive/extension-ui-host.d.ts +2 -1
- package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
- package/dist/modes/interactive/extension-ui-host.js +161 -96
- package/dist/modes/interactive/extension-ui-host.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +23 -28
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +26 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.d.ts +5 -1
- package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.js +118 -24
- package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
- package/dist/modes/interactive/resource-shell-commands.d.ts +1 -0
- package/dist/modes/interactive/resource-shell-commands.d.ts.map +1 -1
- package/dist/modes/interactive/resource-shell-commands.js +5 -1
- package/dist/modes/interactive/resource-shell-commands.js.map +1 -1
- package/dist/modes/interactive/session-flow-commands.d.ts +10 -0
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-flow-commands.js +143 -7
- package/dist/modes/interactive/session-flow-commands.js.map +1 -1
- package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-io-commands.js +80 -50
- package/dist/modes/interactive/session-io-commands.js.map +1 -1
- package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
- package/dist/modes/interactive/startup-checks.js +14 -17
- package/dist/modes/interactive/startup-checks.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +20 -19
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +10 -19
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/child-process.d.ts +13 -7
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +56 -3
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +3 -2
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +17 -10
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/external-editor-command.d.ts +8 -0
- package/dist/utils/external-editor-command.d.ts.map +1 -0
- package/dist/utils/external-editor-command.js +60 -0
- package/dist/utils/external-editor-command.js.map +1 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +10 -2
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/shell.d.ts +6 -8
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +61 -70
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +23 -3
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +117 -21
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/work-directory.d.ts +36 -0
- package/dist/utils/work-directory.d.ts.map +1 -0
- package/dist/utils/work-directory.js +462 -0
- package/dist/utils/work-directory.js.map +1 -0
- package/docs/index.md +4 -0
- package/docs/python.md +43 -0
- package/docs/resources.md +2 -2
- package/docs/rpc.md +1 -1
- package/docs/settings.md +7 -3
- package/docs/task-steps.md +90 -0
- package/docs/termux.md +7 -0
- package/docs/tmux-agent-manager.md +48 -0
- package/docs/usage.md +5 -6
- package/docs/windows.md +104 -8
- package/docs/work-directory.md +41 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +32 -37
- package/package.json +9 -7
- package/scripts/postinstall-python-runtime.mjs +20 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-shell-commands.js","sourceRoot":"","sources":["../../../src/modes/interactive/resource-shell-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resource-install and shell/compaction command bodies extracted from\n * interactive-mode.\n *\n * `/install-resources` copies a trusted external resource tree into the agent\n * dir; `/curate` proposes/applies skill-curation actions; `!`/`!!` bash runs a\n * shell command (rendering a BashExecutionComponent, deferred while streaming);\n * `/compact` triggers manual compaction. Each takes a narrow host seam;\n * `copyResourcesRecursively` stays host-side (referenced directly by the\n * install-resources test), so interactive-mode keeps thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { SessionManager, TruncationResult } from \"@caupulican/pi-agent-core/node\";\nimport type { Component, Container, Loader, TUI } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { BashExecutionComponent } from \"./components/bash-execution.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface InstallResourcesHost {\n\treadonly settingsManager: Pick<\n\t\tSettingsManager,\n\t\t\"canonicalizePath\" | \"getTrustedResourceRoots\" | \"addTrustedResourceRoot\"\n\t>;\n\tshowError(message: string): void;\n\tshowStatus(message: string): void;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\thandleReloadCommand(): Promise<void>;\n\tcopyResourcesRecursively(\n\t\tsrc: string,\n\t\tdest: string,\n\t\tforce: boolean,\n\t\tstats: { installed: string[]; skipped: string[] },\n\t): void;\n}\n\nexport interface CurateCommandHost {\n\treadonly session: Pick<AgentSession, \"archivePromotedSkill\" | \"restorePromotedSkill\" | \"proposeSkillCuration\">;\n\tshowStatus(message: string): void;\n}\n\nexport interface BashCommandHost {\n\treadonly session: Pick<AgentSession, \"extensionRunner\" | \"isStreaming\" | \"recordBashResult\" | \"executeBash\">;\n\treadonly sessionManager: Pick<SessionManager, \"getCwd\">;\n\treadonly ui: TUI;\n\treadonly chatContainer: Container;\n\treadonly pendingMessagesContainer: Container;\n\treadonly pendingBashComponents: BashExecutionComponent[];\n\tbashComponent: BashExecutionComponent | undefined;\n\tshowError(message: string): void;\n}\n\nexport interface CompactCommandHost {\n\treadonly sessionManager: Pick<SessionManager, \"getEntries\">;\n\tshowWarning(message: string): void;\n\tloadingAnimation: Loader | undefined;\n\treadonly statusContainer: { clear: () => void };\n\treadonly session: Pick<AgentSession, \"compact\">;\n}\n\nexport async function handleInstallResourcesCommand(host: InstallResourcesHost, argsString: string): Promise<void> {\n\ttry {\n\t\tconst tokens = argsString.split(/\\s+/).filter(Boolean);\n\t\tlet force = false;\n\t\tlet dir = \"\";\n\t\tfor (const t of tokens) {\n\t\t\tif (t === \"--force\") {\n\t\t\t\tforce = true;\n\t\t\t} else {\n\t\t\t\tdir = t;\n\t\t\t}\n\t\t}\n\n\t\tif (!dir) {\n\t\t\thost.showError(\"Usage: /install-resources <dir> [--force]\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst canonical = host.settingsManager.canonicalizePath(dir);\n\t\tif (!canonical || !fs.existsSync(canonical)) {\n\t\t\thost.showError(`Source directory does not exist: ${dir}`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst trustedRoots = host.settingsManager.getTrustedResourceRoots();\n\t\tconst trusted = trustedRoots.includes(canonical);\n\n\t\tif (!trusted) {\n\t\t\tconst trust = await new Promise<boolean>((resolve) => {\n\t\t\t\thost.showSelector((done) => {\n\t\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\t\"Trust external source for installation?\",\n\t\t\t\t\t\t`The directory \"${canonical}\" contains extensions/resources to install. Extensions can execute arbitrary code on your machine. Do you trust it?`,\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvalue: \"yes\",\n\t\t\t\t\t\t\t\tlabel: \"Yes\",\n\t\t\t\t\t\t\t\tdescription: \"Trust this directory and proceed with installation.\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Do not trust this directory. Abort.\" },\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"no\",\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t\tresolve(value === \"yes\");\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\treturn { component: submenu, focus: submenu.getSelectList() };\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tif (!trust) {\n\t\t\t\thost.showStatus(\"Installation aborted. Source directory was not trusted.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\thost.settingsManager.addTrustedResourceRoot(canonical, \"global\");\n\t\t}\n\n\t\tconst subdirs = [\"skills\", \"extensions\", \"prompts\", \"themes\", \"profiles\", \"agents\"];\n\t\tconst stats = { installed: [] as string[], skipped: [] as string[] };\n\t\tconst userAgentDir = getAgentDir();\n\n\t\tfor (const sub of subdirs) {\n\t\t\tconst srcSub = path.join(canonical, sub);\n\t\t\tconst destSub = path.join(userAgentDir, sub);\n\t\t\tif (fs.existsSync(srcSub)) {\n\t\t\t\thost.copyResourcesRecursively(srcSub, destSub, force, stats);\n\t\t\t}\n\t\t}\n\n\t\tconst installedCount = stats.installed.length;\n\t\tconst skippedCount = stats.skipped.length;\n\t\thost.showStatus(`Installation complete: ${installedCount} resources installed, ${skippedCount} skipped.`);\n\n\t\tawait host.handleReloadCommand();\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\n/**\n * `/curate` — skill curator (#32). With no args, lists reflection-promoted skills proposed for\n * archival (stale/unused) and pairs proposed for consolidation (overlapping). PROPOSE-ONLY: the user\n * applies actions explicitly via `/curate archive <name>` / `/curate restore <name>`. Never touches\n * hand-authored skills; archival is restorable.\n */\nexport function handleCurateCommand(host: CurateCommandHost, args: string): void {\n\tconst [sub, name] = args.split(/\\s+/, 2);\n\tif (sub === \"archive\" && name) {\n\t\thost.showStatus(\n\t\t\thost.session.archivePromotedSkill(name)\n\t\t\t\t? `Archived promoted skill '${name}'`\n\t\t\t\t: `Could not archive '${name}' (not a promoted skill?)`,\n\t\t);\n\t\treturn;\n\t}\n\tif (sub === \"restore\" && name) {\n\t\thost.showStatus(\n\t\t\thost.session.restorePromotedSkill(name) ? `Restored skill '${name}'` : `Could not restore '${name}'`,\n\t\t);\n\t\treturn;\n\t}\n\tconst proposals = host.session.proposeSkillCuration();\n\tif (proposals.archive.length === 0 && proposals.consolidate.length === 0) {\n\t\thost.showStatus(\"Curator: no stale or overlapping promoted skills. Nothing to propose.\");\n\t\treturn;\n\t}\n\tconst lines: string[] = [\"Skill curator proposals (nothing applied automatically):\"];\n\tfor (const a of proposals.archive) {\n\t\tlines.push(` • archive '${a.name}' — ${a.reason} → /curate archive ${a.name}`);\n\t}\n\tfor (const c of proposals.consolidate) {\n\t\tlines.push(` • consider merging '${c.names[0]}' + '${c.names[1]}' (overlap ${(c.overlap * 100) | 0}%)`);\n\t}\n\thost.showStatus(lines.join(\"\\n\"));\n}\n\nexport async function handleBashCommand(\n\thost: BashCommandHost,\n\tcommand: string,\n\texcludeFromContext = false,\n): Promise<void> {\n\tconst extensionRunner = host.session.extensionRunner;\n\n\t// Emit user_bash event to let extensions intercept\n\tconst eventResult = await extensionRunner.emitUserBash({\n\t\ttype: \"user_bash\",\n\t\tcommand,\n\t\texcludeFromContext,\n\t\tcwd: host.sessionManager.getCwd(),\n\t});\n\n\t// If extension returned a full result, use it directly\n\tif (eventResult?.result) {\n\t\tconst result = eventResult.result;\n\n\t\t// Create UI component for display\n\t\thost.bashComponent = new BashExecutionComponent(command, host.ui, excludeFromContext);\n\t\tif (host.session.isStreaming) {\n\t\t\thost.pendingMessagesContainer.addChild(host.bashComponent);\n\t\t\thost.pendingBashComponents.push(host.bashComponent);\n\t\t} else {\n\t\t\thost.chatContainer.addChild(host.bashComponent);\n\t\t}\n\n\t\t// Show output and complete\n\t\tif (result.output) {\n\t\t\thost.bashComponent.appendOutput(result.output);\n\t\t}\n\t\thost.bashComponent.setComplete(\n\t\t\tresult.exitCode,\n\t\t\tresult.cancelled,\n\t\t\tresult.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,\n\t\t\tresult.fullOutputPath,\n\t\t);\n\n\t\t// Record the result in session\n\t\thost.session.recordBashResult(command, result, { excludeFromContext });\n\t\thost.bashComponent = undefined;\n\t\thost.ui.requestRender();\n\t\treturn;\n\t}\n\n\t// Normal execution path (possibly with custom operations)\n\tconst isDeferred = host.session.isStreaming;\n\thost.bashComponent = new BashExecutionComponent(command, host.ui, excludeFromContext);\n\n\tif (isDeferred) {\n\t\t// Show in pending area when agent is streaming\n\t\thost.pendingMessagesContainer.addChild(host.bashComponent);\n\t\thost.pendingBashComponents.push(host.bashComponent);\n\t} else {\n\t\t// Show in chat immediately when agent is idle\n\t\thost.chatContainer.addChild(host.bashComponent);\n\t}\n\thost.ui.requestRender();\n\n\ttry {\n\t\tconst result = await host.session.executeBash(\n\t\t\tcommand,\n\t\t\t(chunk) => {\n\t\t\t\tif (host.bashComponent) {\n\t\t\t\t\thost.bashComponent.appendOutput(chunk);\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ excludeFromContext, operations: eventResult?.operations },\n\t\t);\n\n\t\tif (host.bashComponent) {\n\t\t\thost.bashComponent.setComplete(\n\t\t\t\tresult.exitCode,\n\t\t\t\tresult.cancelled,\n\t\t\t\tresult.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,\n\t\t\t\tresult.fullOutputPath,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\tif (host.bashComponent) {\n\t\t\thost.bashComponent.setComplete(undefined, false);\n\t\t}\n\t\thost.showError(`Bash command failed: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t}\n\n\thost.bashComponent = undefined;\n\thost.ui.requestRender();\n}\n\nexport async function handleCompactCommand(host: CompactCommandHost, customInstructions?: string): Promise<void> {\n\tconst entries = host.sessionManager.getEntries();\n\tconst messageCount = entries.filter((e) => e.type === \"message\").length;\n\n\tif (messageCount < 2) {\n\t\thost.showWarning(\"Nothing to compact (no messages yet)\");\n\t\treturn;\n\t}\n\n\tif (host.loadingAnimation) {\n\t\thost.loadingAnimation.stop();\n\t\thost.loadingAnimation = undefined;\n\t}\n\thost.statusContainer.clear();\n\n\ttry {\n\t\tawait host.session.compact(customInstructions);\n\t} catch {\n\t\t// Ignore, will be emitted as an event\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"resource-shell-commands.js","sourceRoot":"","sources":["../../../src/modes/interactive/resource-shell-commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resource-install and shell/compaction command bodies extracted from\n * interactive-mode.\n *\n * `/install-resources` copies a trusted external resource tree into the agent\n * dir; `/curate` proposes/applies skill-curation actions; `!`/`!!` bash runs a\n * shell command (rendering a BashExecutionComponent, deferred while streaming);\n * `/compact` triggers manual compaction. Each takes a narrow host seam;\n * `copyResourcesRecursively` stays host-side (referenced directly by the\n * install-resources test), so interactive-mode keeps thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { SessionManager, TruncationResult } from \"@caupulican/pi-agent-core/node\";\nimport type { Component, Container, Loader, TUI } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { AgentSession } from \"../../core/agent-session.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { BashExecutionComponent } from \"./components/bash-execution.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface InstallResourcesHost {\n\treadonly settingsManager: Pick<\n\t\tSettingsManager,\n\t\t\"canonicalizePath\" | \"getTrustedResourceRoots\" | \"addTrustedResourceRoot\"\n\t>;\n\tshowError(message: string): void;\n\tshowStatus(message: string): void;\n\tshowSelector(\n\t\tcreate: (done: () => void) => {\n\t\t\tcomponent: Component;\n\t\t\tfocus: Component;\n\t\t\tonSuperseded?: () => void;\n\t\t},\n\t): void;\n\thandleReloadCommand(): Promise<void>;\n\tcopyResourcesRecursively(\n\t\tsrc: string,\n\t\tdest: string,\n\t\tforce: boolean,\n\t\tstats: { installed: string[]; skipped: string[] },\n\t): void;\n}\n\nexport interface CurateCommandHost {\n\treadonly session: Pick<AgentSession, \"archivePromotedSkill\" | \"restorePromotedSkill\" | \"proposeSkillCuration\">;\n\tshowStatus(message: string): void;\n}\n\nexport interface BashCommandHost {\n\treadonly session: Pick<AgentSession, \"extensionRunner\" | \"isStreaming\" | \"recordBashResult\" | \"executeBash\">;\n\treadonly sessionManager: Pick<SessionManager, \"getCwd\">;\n\treadonly ui: TUI;\n\treadonly chatContainer: Container;\n\treadonly pendingMessagesContainer: Container;\n\treadonly pendingBashComponents: BashExecutionComponent[];\n\tbashComponent: BashExecutionComponent | undefined;\n\tshowError(message: string): void;\n}\n\nexport interface CompactCommandHost {\n\treadonly sessionManager: Pick<SessionManager, \"getEntries\">;\n\tshowWarning(message: string): void;\n\tloadingAnimation: Loader | undefined;\n\treadonly statusContainer: { clear: () => void };\n\treadonly session: Pick<AgentSession, \"compact\">;\n}\n\nexport async function handleInstallResourcesCommand(host: InstallResourcesHost, argsString: string): Promise<void> {\n\ttry {\n\t\tconst tokens = argsString.split(/\\s+/).filter(Boolean);\n\t\tlet force = false;\n\t\tlet dir = \"\";\n\t\tfor (const t of tokens) {\n\t\t\tif (t === \"--force\") {\n\t\t\t\tforce = true;\n\t\t\t} else {\n\t\t\t\tdir = t;\n\t\t\t}\n\t\t}\n\n\t\tif (!dir) {\n\t\t\thost.showError(\"Usage: /install-resources <dir> [--force]\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst canonical = host.settingsManager.canonicalizePath(dir);\n\t\tif (!canonical || !fs.existsSync(canonical)) {\n\t\t\thost.showError(`Source directory does not exist: ${dir}`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst trustedRoots = host.settingsManager.getTrustedResourceRoots();\n\t\tconst trusted = trustedRoots.includes(canonical);\n\n\t\tif (!trusted) {\n\t\t\tconst trust = await new Promise<boolean>((resolve) => {\n\t\t\t\thost.showSelector((done) => {\n\t\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\t\"Trust external source for installation?\",\n\t\t\t\t\t\t`The directory \"${canonical}\" contains extensions/resources to install. Extensions can execute arbitrary code on your machine. Do you trust it?`,\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvalue: \"yes\",\n\t\t\t\t\t\t\t\tlabel: \"Yes\",\n\t\t\t\t\t\t\t\tdescription: \"Trust this directory and proceed with installation.\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Do not trust this directory. Abort.\" },\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"no\",\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t\tresolve(value === \"yes\");\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcomponent: submenu,\n\t\t\t\t\t\tfocus: submenu.getSelectList(),\n\t\t\t\t\t\tonSuperseded: () => resolve(false),\n\t\t\t\t\t};\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tif (!trust) {\n\t\t\t\thost.showStatus(\"Installation aborted. Source directory was not trusted.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\thost.settingsManager.addTrustedResourceRoot(canonical, \"global\");\n\t\t}\n\n\t\tconst subdirs = [\"skills\", \"extensions\", \"prompts\", \"themes\", \"profiles\", \"agents\"];\n\t\tconst stats = { installed: [] as string[], skipped: [] as string[] };\n\t\tconst userAgentDir = getAgentDir();\n\n\t\tfor (const sub of subdirs) {\n\t\t\tconst srcSub = path.join(canonical, sub);\n\t\t\tconst destSub = path.join(userAgentDir, sub);\n\t\t\tif (fs.existsSync(srcSub)) {\n\t\t\t\thost.copyResourcesRecursively(srcSub, destSub, force, stats);\n\t\t\t}\n\t\t}\n\n\t\tconst installedCount = stats.installed.length;\n\t\tconst skippedCount = stats.skipped.length;\n\t\thost.showStatus(`Installation complete: ${installedCount} resources installed, ${skippedCount} skipped.`);\n\n\t\tawait host.handleReloadCommand();\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\n/**\n * `/curate` — skill curator (#32). With no args, lists reflection-promoted skills proposed for\n * archival (stale/unused) and pairs proposed for consolidation (overlapping). PROPOSE-ONLY: the user\n * applies actions explicitly via `/curate archive <name>` / `/curate restore <name>`. Never touches\n * hand-authored skills; archival is restorable.\n */\nexport function handleCurateCommand(host: CurateCommandHost, args: string): void {\n\tconst [sub, name] = args.split(/\\s+/, 2);\n\tif (sub === \"archive\" && name) {\n\t\thost.showStatus(\n\t\t\thost.session.archivePromotedSkill(name)\n\t\t\t\t? `Archived promoted skill '${name}'`\n\t\t\t\t: `Could not archive '${name}' (not a promoted skill?)`,\n\t\t);\n\t\treturn;\n\t}\n\tif (sub === \"restore\" && name) {\n\t\thost.showStatus(\n\t\t\thost.session.restorePromotedSkill(name) ? `Restored skill '${name}'` : `Could not restore '${name}'`,\n\t\t);\n\t\treturn;\n\t}\n\tconst proposals = host.session.proposeSkillCuration();\n\tif (proposals.archive.length === 0 && proposals.consolidate.length === 0) {\n\t\thost.showStatus(\"Curator: no stale or overlapping promoted skills. Nothing to propose.\");\n\t\treturn;\n\t}\n\tconst lines: string[] = [\"Skill curator proposals (nothing applied automatically):\"];\n\tfor (const a of proposals.archive) {\n\t\tlines.push(` • archive '${a.name}' — ${a.reason} → /curate archive ${a.name}`);\n\t}\n\tfor (const c of proposals.consolidate) {\n\t\tlines.push(` • consider merging '${c.names[0]}' + '${c.names[1]}' (overlap ${(c.overlap * 100) | 0}%)`);\n\t}\n\thost.showStatus(lines.join(\"\\n\"));\n}\n\nexport async function handleBashCommand(\n\thost: BashCommandHost,\n\tcommand: string,\n\texcludeFromContext = false,\n): Promise<void> {\n\tconst extensionRunner = host.session.extensionRunner;\n\n\t// Emit user_bash event to let extensions intercept\n\tconst eventResult = await extensionRunner.emitUserBash({\n\t\ttype: \"user_bash\",\n\t\tcommand,\n\t\texcludeFromContext,\n\t\tcwd: host.sessionManager.getCwd(),\n\t});\n\n\t// If extension returned a full result, use it directly\n\tif (eventResult?.result) {\n\t\tconst result = eventResult.result;\n\n\t\t// Create UI component for display\n\t\thost.bashComponent = new BashExecutionComponent(command, host.ui, excludeFromContext);\n\t\tif (host.session.isStreaming) {\n\t\t\thost.pendingMessagesContainer.addChild(host.bashComponent);\n\t\t\thost.pendingBashComponents.push(host.bashComponent);\n\t\t} else {\n\t\t\thost.chatContainer.addChild(host.bashComponent);\n\t\t}\n\n\t\t// Show output and complete\n\t\tif (result.output) {\n\t\t\thost.bashComponent.appendOutput(result.output);\n\t\t}\n\t\thost.bashComponent.setComplete(\n\t\t\tresult.exitCode,\n\t\t\tresult.cancelled,\n\t\t\tresult.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,\n\t\t\tresult.fullOutputPath,\n\t\t);\n\n\t\t// Record the result in session\n\t\thost.session.recordBashResult(command, result, { excludeFromContext });\n\t\thost.bashComponent = undefined;\n\t\thost.ui.requestRender();\n\t\treturn;\n\t}\n\n\t// Normal execution path (possibly with custom operations)\n\tconst isDeferred = host.session.isStreaming;\n\thost.bashComponent = new BashExecutionComponent(command, host.ui, excludeFromContext);\n\n\tif (isDeferred) {\n\t\t// Show in pending area when agent is streaming\n\t\thost.pendingMessagesContainer.addChild(host.bashComponent);\n\t\thost.pendingBashComponents.push(host.bashComponent);\n\t} else {\n\t\t// Show in chat immediately when agent is idle\n\t\thost.chatContainer.addChild(host.bashComponent);\n\t}\n\thost.ui.requestRender();\n\n\ttry {\n\t\tconst result = await host.session.executeBash(\n\t\t\tcommand,\n\t\t\t(chunk) => {\n\t\t\t\tif (host.bashComponent) {\n\t\t\t\t\thost.bashComponent.appendOutput(chunk);\n\t\t\t\t\thost.ui.requestRender();\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ excludeFromContext, operations: eventResult?.operations },\n\t\t);\n\n\t\tif (host.bashComponent) {\n\t\t\thost.bashComponent.setComplete(\n\t\t\t\tresult.exitCode,\n\t\t\t\tresult.cancelled,\n\t\t\t\tresult.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,\n\t\t\t\tresult.fullOutputPath,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\tif (host.bashComponent) {\n\t\t\thost.bashComponent.setComplete(undefined, false);\n\t\t}\n\t\thost.showError(`Bash command failed: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t}\n\n\thost.bashComponent = undefined;\n\thost.ui.requestRender();\n}\n\nexport async function handleCompactCommand(host: CompactCommandHost, customInstructions?: string): Promise<void> {\n\tconst entries = host.sessionManager.getEntries();\n\tconst messageCount = entries.filter((e) => e.type === \"message\").length;\n\n\tif (messageCount < 2) {\n\t\thost.showWarning(\"Nothing to compact (no messages yet)\");\n\t\treturn;\n\t}\n\n\tif (host.loadingAnimation) {\n\t\thost.loadingAnimation.stop();\n\t\thost.loadingAnimation = undefined;\n\t}\n\thost.statusContainer.clear();\n\n\ttry {\n\t\tawait host.session.compact(customInstructions);\n\t} catch {\n\t\t// Ignore, will be emitted as an event\n\t}\n}\n"]}
|
|
@@ -21,6 +21,7 @@ import type { GoalState } from "../../core/goals/goal-state.ts";
|
|
|
21
21
|
import type { KeybindingsManager } from "../../core/keybindings.ts";
|
|
22
22
|
import { MissingSessionCwdError } from "../../core/session-cwd.ts";
|
|
23
23
|
import type { SettingsManager } from "../../core/settings-manager.ts";
|
|
24
|
+
import { type TaskStepsState } from "../../core/tasks/task-state.ts";
|
|
24
25
|
import type { CustomEditor } from "./components/custom-editor.ts";
|
|
25
26
|
import type { FooterComponent } from "./components/footer.ts";
|
|
26
27
|
import type { ExtensionUiHost } from "./extension-ui-host.ts";
|
|
@@ -126,6 +127,15 @@ export interface GoalCommandHost {
|
|
|
126
127
|
refreshAutonomyFooterStatus(): void;
|
|
127
128
|
}
|
|
128
129
|
export declare function handleGoalCommand(host: GoalCommandHost, text: string): Promise<void>;
|
|
130
|
+
export interface TaskCommandHost {
|
|
131
|
+
readonly session: {
|
|
132
|
+
getTaskStepsStateSnapshot: () => TaskStepsState | undefined;
|
|
133
|
+
saveTaskStepsStateSnapshot: (state: TaskStepsState) => string;
|
|
134
|
+
};
|
|
135
|
+
showStatus(message: string): void;
|
|
136
|
+
showError(message: string): void;
|
|
137
|
+
}
|
|
138
|
+
export declare function handleTaskCommand(host: TaskCommandHost, text: string): void;
|
|
129
139
|
export interface GoalContinueCommandHost {
|
|
130
140
|
readonly session: {
|
|
131
141
|
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 { 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\t// Deterministic producer completion: a goal with zero open requirements evaluates to\n\t// finalize/no_open_requirements, so the loop would be undrivable whenever the model skips\n\t// decomposition (observed in the field). Seed one requirement from the goal text itself —\n\t// the model's decomposition below refines alongside it.\n\tconst seeded = applyGoalAction(\n\t\tstarted.state,\n\t\t{ action: \"add_requirement\", requirementId: `${goalId}-r1`, text: goalText },\n\t\tnow,\n\t);\n\tif (seeded.ok) {\n\t\thost.session.saveGoalStateSnapshot(seeded.state);\n\t}\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, and satisfy_requirement ${goalId}-r1 once the decomposed requirements cover it.`,\n\t);\n\tconst result = await host.session.continueGoalLoop({ maxTurns: 1, maxStallTurns: 20, maxWallClockMinutes: 0 });\n\tconst continuation = result.finalSnapshot.continuation;\n\thost.showStatus(`Goal started: ${continuation.action}/${continuation.reasonCode}.`);\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\tgetCostSummary: () => ReturnType<AgentSession[\"getCostSummary\"]>;\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 costs = host.session.getCostSummary();\n\tif (costs.currentCost > 0 || costs.todayCost > 0 || costs.subagentReports > 0) {\n\t\tinfo += `\\n${theme.bold(\"Cost\")}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"CURRENT (session):\")} $${costs.currentCost.toFixed(4)}\\n`;\n\t\tif (costs.subagentReports > 0 || costs.subagentCost > 0) {\n\t\t\tinfo += `${theme.fg(\"dim\", \"SUBAGENTS (included in CURRENT):\")} $${costs.subagentCost.toFixed(4)} (${costs.subagentReports} reports)\\n`;\n\t\t}\n\t\tinfo += `${theme.fg(\"dim\", \"TODAY (host local day):\")} $${costs.todayCost.toFixed(4)}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Today rollover:\")} local midnight`;\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, completeGoalManually } 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 { parseTaskCommand } from \"../../core/tasks/task-command.ts\";\nimport {\n\taddTaskStep,\n\tclearTaskSteps,\n\tcompactTaskSteps,\n\tcreateTaskStepsState,\n\tformatTaskSteps,\n\ttype TaskStepsState,\n\tupdateTaskStep,\n} from \"../../core/tasks/task-state.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\tconst requirements = state.requirements.map((requirement) => {\n\t\tconst blockedReason = requirement.blockedReason ? ` — ${requirement.blockedReason}` : \"\";\n\t\treturn `- ${requirement.id}: ${requirement.status}${blockedReason}`;\n\t});\n\tconst blockedReason = state.blockedReason ? `\\nBlocked reason: ${state.blockedReason}` : \"\";\n\tconst requirementDetails = requirements.length > 0 ? `\\n${requirements.join(\"\\n\")}` : \"\";\n\treturn `Goal: ${state.userGoal}\\nStatus: ${state.status}${blockedReason}\\nRequirements: open ${snapshot.continuation.openRequirementIds.length}, blocked ${snapshot.continuation.blockedRequirementIds.length}, satisfied ${snapshot.continuation.satisfiedRequirementIds.length}${requirementDetails}\\nContinuation: ${snapshot.continuation.action}/${snapshot.continuation.reasonCode}\\nControls: /goal resume | /goal reopen <requirement-id> | /goal complete | /goal close | /goal override <text>`;\n}\n\nexport async function handleGoalCommand(host: GoalCommandHost, text: string): Promise<void> {\n\tconst input = text.replace(/^\\/goal\\s*/, \"\").trim();\n\tif (!input || input === \"status\") {\n\t\thost.showStatus(formatGoalStatus(host.session.getGoalRuntimeSnapshot({ maxStallTurns: 20 })));\n\t\treturn;\n\t}\n\n\tconst now = new Date().toISOString();\n\tconst current = host.session.getGoalStateSnapshot();\n\tif (input === \"resume\") {\n\t\tconst resumed = applyGoalAction(current, { action: \"resume_goal\" }, now);\n\t\tif (!resumed.ok) {\n\t\t\thost.showError(resumed.error);\n\t\t\treturn;\n\t\t}\n\t\thost.session.saveGoalStateSnapshot(resumed.state);\n\t\thost.showStatus(\"Goal resumed.\");\n\t\thost.refreshAutonomyFooterStatus();\n\t\treturn;\n\t}\n\n\tif (input === \"complete\") {\n\t\tconst completed = completeGoalManually(current, now);\n\t\tif (!completed.ok) {\n\t\t\thost.showError(completed.error);\n\t\t\treturn;\n\t\t}\n\t\thost.session.saveGoalStateSnapshot(completed.state);\n\t\thost.showStatus(\"Goal completed manually.\");\n\t\thost.refreshAutonomyFooterStatus();\n\t\treturn;\n\t}\n\n\tif (input === \"close\" || input === \"cancel\") {\n\t\tconst cancelled = applyGoalAction(current, { action: \"cancel\" }, now);\n\t\tif (!cancelled.ok) {\n\t\t\thost.showError(cancelled.error);\n\t\t\treturn;\n\t\t}\n\t\thost.session.saveGoalStateSnapshot(cancelled.state);\n\t\thost.showStatus(\"Goal closed.\");\n\t\thost.refreshAutonomyFooterStatus();\n\t\treturn;\n\t}\n\n\tif (input === \"reopen\" || input.startsWith(\"reopen \")) {\n\t\tconst requirementId = input.slice(\"reopen\".length).trim();\n\t\tif (!requirementId) {\n\t\t\thost.showError(\"Usage: /goal reopen <requirement-id>\");\n\t\t\treturn;\n\t\t}\n\t\tif (!current) {\n\t\t\thost.showError(\"No goal exists to update.\");\n\t\t\treturn;\n\t\t}\n\t\tconst requirement = current.requirements.find((candidate) => candidate.id === requirementId);\n\t\tif (!requirement || requirement.status !== \"blocked\") {\n\t\t\thost.showError(\n\t\t\t\trequirement\n\t\t\t\t\t? `Requirement '${requirementId}' is ${requirement.status}; only blocked requirements can be reopened.`\n\t\t\t\t\t: `Unknown requirement '${requirementId}'.`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tlet active = current;\n\t\tif (active.status === \"blocked\") {\n\t\t\tconst resumed = applyGoalAction(active, { action: \"resume_goal\" }, now);\n\t\t\tif (!resumed.ok) {\n\t\t\t\thost.showError(resumed.error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tactive = resumed.state;\n\t\t\thost.session.saveGoalStateSnapshot(active);\n\t\t}\n\t\tconst reopened = applyGoalAction(active, { action: \"reopen_requirement\", requirementId }, now);\n\t\tif (!reopened.ok) {\n\t\t\thost.showError(reopened.error);\n\t\t\treturn;\n\t\t}\n\t\thost.session.saveGoalStateSnapshot(reopened.state);\n\t\thost.showStatus(\n\t\t\t`Requirement '${requirementId}' reopened${current.status === \"blocked\" ? \" and goal resumed\" : \"\"}.`,\n\t\t);\n\t\thost.refreshAutonomyFooterStatus();\n\t\treturn;\n\t}\n\n\tconst overriding = input === \"override\" || input.startsWith(\"override \");\n\tconst goalText = overriding ? input.slice(\"override\".length).trim() : input;\n\tif (!goalText) {\n\t\thost.showError(\"Usage: /goal override <text>\");\n\t\treturn;\n\t}\n\tlet startFrom = current;\n\tif (overriding && startFrom && (startFrom.status === \"active\" || startFrom.status === \"blocked\")) {\n\t\tconst cancelled = applyGoalAction(startFrom, { action: \"cancel\" }, now);\n\t\tif (!cancelled.ok) {\n\t\t\thost.showError(cancelled.error);\n\t\t\treturn;\n\t\t}\n\t\tstartFrom = cancelled.state;\n\t\thost.session.saveGoalStateSnapshot(startFrom);\n\t}\n\n\tconst goalId = `goal-${Date.now().toString(36)}`;\n\tconst started = applyGoalAction(startFrom, { action: \"start\", goalId, userGoal: goalText }, now);\n\tif (!started.ok) {\n\t\thost.showError(`${started.error} Use /goal override <text> to replace it explicitly.`);\n\t\treturn;\n\t}\n\thost.session.saveGoalStateSnapshot(started.state);\n\t// Deterministic producer completion: a goal with zero open requirements evaluates to\n\t// finalize/no_open_requirements, so the loop would be undrivable whenever the model skips\n\t// decomposition (observed in the field). Seed one requirement from the goal text itself —\n\t// the model's decomposition below refines alongside it.\n\tconst seeded = applyGoalAction(\n\t\tstarted.state,\n\t\t{ action: \"add_requirement\", requirementId: `${goalId}-r1`, text: goalText },\n\t\tnow,\n\t);\n\tif (seeded.ok) {\n\t\thost.session.saveGoalStateSnapshot(seeded.state);\n\t}\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, and satisfy_requirement ${goalId}-r1 once the decomposed requirements cover it.`,\n\t);\n\tconst result = await host.session.continueGoalLoop({ maxTurns: 1, maxStallTurns: 20, maxWallClockMinutes: 0 });\n\tconst continuation = result.finalSnapshot.continuation;\n\thost.showStatus(`Goal ${overriding ? \"overridden\" : \"started\"}: ${continuation.action}/${continuation.reasonCode}.`);\n\thost.refreshAutonomyFooterStatus();\n}\n\nexport interface TaskCommandHost {\n\treadonly session: {\n\t\tgetTaskStepsStateSnapshot: () => TaskStepsState | undefined;\n\t\tsaveTaskStepsStateSnapshot: (state: TaskStepsState) => string;\n\t};\n\tshowStatus(message: string): void;\n\tshowError(message: string): void;\n}\n\nexport function handleTaskCommand(host: TaskCommandHost, text: string): void {\n\tconst parsed = parseTaskCommand(text);\n\tif (!parsed.ok) {\n\t\thost.showError(parsed.error);\n\t\treturn;\n\t}\n\tif (parsed.command.type === \"retired_execution\") {\n\t\thost.showStatus(\n\t\t\t`/task ${parsed.command.operation} was retired with the detached extension runner. Ask Pi to use native delegate/delegate_status; session-owned worker lanes notify on completion without polling.`,\n\t\t);\n\t\treturn;\n\t}\n\n\tconst now = new Date().toISOString();\n\tlet state = host.session.getTaskStepsStateSnapshot() ?? createTaskStepsState(now);\n\ttry {\n\t\tswitch (parsed.command.type) {\n\t\t\tcase \"list\":\n\t\t\t\thost.showStatus(formatTaskSteps(state, { includeTerminal: parsed.command.includeTerminal }));\n\t\t\t\treturn;\n\t\t\tcase \"add\":\n\t\t\t\tstate = addTaskStep(state, { content: parsed.command.content }, now);\n\t\t\t\tbreak;\n\t\t\tcase \"update\":\n\t\t\t\tstate = updateTaskStep(\n\t\t\t\t\tstate,\n\t\t\t\t\tparsed.command.selector,\n\t\t\t\t\t{\n\t\t\t\t\t\tstatus: parsed.command.status,\n\t\t\t\t\t\tnote: parsed.command.note,\n\t\t\t\t\t\tevidence: parsed.command.evidence ? [parsed.command.evidence] : undefined,\n\t\t\t\t\t},\n\t\t\t\t\tnow,\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"clear\":\n\t\t\t\tstate = clearTaskSteps(state, now);\n\t\t\t\tbreak;\n\t\t\tcase \"compact\":\n\t\t\t\tstate = compactTaskSteps(state, now);\n\t\t\t\tbreak;\n\t\t}\n\t\thost.session.saveTaskStepsStateSnapshot(state);\n\t\thost.showStatus(formatTaskSteps(state));\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\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\tgetCostSummary: () => ReturnType<AgentSession[\"getCostSummary\"]>;\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 costs = host.session.getCostSummary();\n\tif (costs.currentCost > 0 || costs.todayCost > 0 || costs.subagentReports > 0) {\n\t\tinfo += `\\n${theme.bold(\"Cost\")}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"CURRENT (session):\")} $${costs.currentCost.toFixed(4)}\\n`;\n\t\tif (costs.subagentReports > 0 || costs.subagentCost > 0) {\n\t\t\tinfo += `${theme.fg(\"dim\", \"SUBAGENTS (included in CURRENT):\")} $${costs.subagentCost.toFixed(4)} (${costs.subagentReports} reports)\\n`;\n\t\t}\n\t\tinfo += `${theme.fg(\"dim\", \"TODAY (host local day):\")} $${costs.todayCost.toFixed(4)}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Today rollover:\")} local midnight`;\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,10 +11,12 @@
|
|
|
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
|
+
import { applyGoalAction, completeGoalManually } from "../../core/goals/goal-tool-core.js";
|
|
15
15
|
import { findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
16
16
|
import { MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
17
17
|
import { listAllSessions, listSessions, openSession } from "../../core/session-manager-factory.js";
|
|
18
|
+
import { parseTaskCommand } from "../../core/tasks/task-command.js";
|
|
19
|
+
import { addTaskStep, clearTaskSteps, compactTaskSteps, createTaskStepsState, formatTaskSteps, updateTaskStep, } from "../../core/tasks/task-state.js";
|
|
18
20
|
import { ProjectTrustStore } from "../../core/trust-manager.js";
|
|
19
21
|
import { keyText } from "./components/keybinding-hints.js";
|
|
20
22
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
@@ -400,19 +402,112 @@ function formatGoalStatus(snapshot) {
|
|
|
400
402
|
const state = snapshot.goalState;
|
|
401
403
|
if (!state)
|
|
402
404
|
return `Goal: none (${snapshot.continuation.action}/${snapshot.continuation.reasonCode})`;
|
|
403
|
-
|
|
405
|
+
const requirements = state.requirements.map((requirement) => {
|
|
406
|
+
const blockedReason = requirement.blockedReason ? ` — ${requirement.blockedReason}` : "";
|
|
407
|
+
return `- ${requirement.id}: ${requirement.status}${blockedReason}`;
|
|
408
|
+
});
|
|
409
|
+
const blockedReason = state.blockedReason ? `\nBlocked reason: ${state.blockedReason}` : "";
|
|
410
|
+
const requirementDetails = requirements.length > 0 ? `\n${requirements.join("\n")}` : "";
|
|
411
|
+
return `Goal: ${state.userGoal}\nStatus: ${state.status}${blockedReason}\nRequirements: open ${snapshot.continuation.openRequirementIds.length}, blocked ${snapshot.continuation.blockedRequirementIds.length}, satisfied ${snapshot.continuation.satisfiedRequirementIds.length}${requirementDetails}\nContinuation: ${snapshot.continuation.action}/${snapshot.continuation.reasonCode}\nControls: /goal resume | /goal reopen <requirement-id> | /goal complete | /goal close | /goal override <text>`;
|
|
404
412
|
}
|
|
405
413
|
export async function handleGoalCommand(host, text) {
|
|
406
|
-
const
|
|
407
|
-
if (!
|
|
414
|
+
const input = text.replace(/^\/goal\s*/, "").trim();
|
|
415
|
+
if (!input || input === "status") {
|
|
408
416
|
host.showStatus(formatGoalStatus(host.session.getGoalRuntimeSnapshot({ maxStallTurns: 20 })));
|
|
409
417
|
return;
|
|
410
418
|
}
|
|
411
419
|
const now = new Date().toISOString();
|
|
420
|
+
const current = host.session.getGoalStateSnapshot();
|
|
421
|
+
if (input === "resume") {
|
|
422
|
+
const resumed = applyGoalAction(current, { action: "resume_goal" }, now);
|
|
423
|
+
if (!resumed.ok) {
|
|
424
|
+
host.showError(resumed.error);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
host.session.saveGoalStateSnapshot(resumed.state);
|
|
428
|
+
host.showStatus("Goal resumed.");
|
|
429
|
+
host.refreshAutonomyFooterStatus();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (input === "complete") {
|
|
433
|
+
const completed = completeGoalManually(current, now);
|
|
434
|
+
if (!completed.ok) {
|
|
435
|
+
host.showError(completed.error);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
host.session.saveGoalStateSnapshot(completed.state);
|
|
439
|
+
host.showStatus("Goal completed manually.");
|
|
440
|
+
host.refreshAutonomyFooterStatus();
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (input === "close" || input === "cancel") {
|
|
444
|
+
const cancelled = applyGoalAction(current, { action: "cancel" }, now);
|
|
445
|
+
if (!cancelled.ok) {
|
|
446
|
+
host.showError(cancelled.error);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
host.session.saveGoalStateSnapshot(cancelled.state);
|
|
450
|
+
host.showStatus("Goal closed.");
|
|
451
|
+
host.refreshAutonomyFooterStatus();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (input === "reopen" || input.startsWith("reopen ")) {
|
|
455
|
+
const requirementId = input.slice("reopen".length).trim();
|
|
456
|
+
if (!requirementId) {
|
|
457
|
+
host.showError("Usage: /goal reopen <requirement-id>");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (!current) {
|
|
461
|
+
host.showError("No goal exists to update.");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const requirement = current.requirements.find((candidate) => candidate.id === requirementId);
|
|
465
|
+
if (!requirement || requirement.status !== "blocked") {
|
|
466
|
+
host.showError(requirement
|
|
467
|
+
? `Requirement '${requirementId}' is ${requirement.status}; only blocked requirements can be reopened.`
|
|
468
|
+
: `Unknown requirement '${requirementId}'.`);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
let active = current;
|
|
472
|
+
if (active.status === "blocked") {
|
|
473
|
+
const resumed = applyGoalAction(active, { action: "resume_goal" }, now);
|
|
474
|
+
if (!resumed.ok) {
|
|
475
|
+
host.showError(resumed.error);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
active = resumed.state;
|
|
479
|
+
host.session.saveGoalStateSnapshot(active);
|
|
480
|
+
}
|
|
481
|
+
const reopened = applyGoalAction(active, { action: "reopen_requirement", requirementId }, now);
|
|
482
|
+
if (!reopened.ok) {
|
|
483
|
+
host.showError(reopened.error);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
host.session.saveGoalStateSnapshot(reopened.state);
|
|
487
|
+
host.showStatus(`Requirement '${requirementId}' reopened${current.status === "blocked" ? " and goal resumed" : ""}.`);
|
|
488
|
+
host.refreshAutonomyFooterStatus();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const overriding = input === "override" || input.startsWith("override ");
|
|
492
|
+
const goalText = overriding ? input.slice("override".length).trim() : input;
|
|
493
|
+
if (!goalText) {
|
|
494
|
+
host.showError("Usage: /goal override <text>");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
let startFrom = current;
|
|
498
|
+
if (overriding && startFrom && (startFrom.status === "active" || startFrom.status === "blocked")) {
|
|
499
|
+
const cancelled = applyGoalAction(startFrom, { action: "cancel" }, now);
|
|
500
|
+
if (!cancelled.ok) {
|
|
501
|
+
host.showError(cancelled.error);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
startFrom = cancelled.state;
|
|
505
|
+
host.session.saveGoalStateSnapshot(startFrom);
|
|
506
|
+
}
|
|
412
507
|
const goalId = `goal-${Date.now().toString(36)}`;
|
|
413
|
-
const started = applyGoalAction(
|
|
508
|
+
const started = applyGoalAction(startFrom, { action: "start", goalId, userGoal: goalText }, now);
|
|
414
509
|
if (!started.ok) {
|
|
415
|
-
host.showError(started.error);
|
|
510
|
+
host.showError(`${started.error} Use /goal override <text> to replace it explicitly.`);
|
|
416
511
|
return;
|
|
417
512
|
}
|
|
418
513
|
host.session.saveGoalStateSnapshot(started.state);
|
|
@@ -427,9 +522,50 @@ export async function handleGoalCommand(host, text) {
|
|
|
427
522
|
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, and satisfy_requirement ${goalId}-r1 once the decomposed requirements cover it.`);
|
|
428
523
|
const result = await host.session.continueGoalLoop({ maxTurns: 1, maxStallTurns: 20, maxWallClockMinutes: 0 });
|
|
429
524
|
const continuation = result.finalSnapshot.continuation;
|
|
430
|
-
host.showStatus(`Goal started: ${continuation.action}/${continuation.reasonCode}.`);
|
|
525
|
+
host.showStatus(`Goal ${overriding ? "overridden" : "started"}: ${continuation.action}/${continuation.reasonCode}.`);
|
|
431
526
|
host.refreshAutonomyFooterStatus();
|
|
432
527
|
}
|
|
528
|
+
export function handleTaskCommand(host, text) {
|
|
529
|
+
const parsed = parseTaskCommand(text);
|
|
530
|
+
if (!parsed.ok) {
|
|
531
|
+
host.showError(parsed.error);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (parsed.command.type === "retired_execution") {
|
|
535
|
+
host.showStatus(`/task ${parsed.command.operation} was retired with the detached extension runner. Ask Pi to use native delegate/delegate_status; session-owned worker lanes notify on completion without polling.`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const now = new Date().toISOString();
|
|
539
|
+
let state = host.session.getTaskStepsStateSnapshot() ?? createTaskStepsState(now);
|
|
540
|
+
try {
|
|
541
|
+
switch (parsed.command.type) {
|
|
542
|
+
case "list":
|
|
543
|
+
host.showStatus(formatTaskSteps(state, { includeTerminal: parsed.command.includeTerminal }));
|
|
544
|
+
return;
|
|
545
|
+
case "add":
|
|
546
|
+
state = addTaskStep(state, { content: parsed.command.content }, now);
|
|
547
|
+
break;
|
|
548
|
+
case "update":
|
|
549
|
+
state = updateTaskStep(state, parsed.command.selector, {
|
|
550
|
+
status: parsed.command.status,
|
|
551
|
+
note: parsed.command.note,
|
|
552
|
+
evidence: parsed.command.evidence ? [parsed.command.evidence] : undefined,
|
|
553
|
+
}, now);
|
|
554
|
+
break;
|
|
555
|
+
case "clear":
|
|
556
|
+
state = clearTaskSteps(state, now);
|
|
557
|
+
break;
|
|
558
|
+
case "compact":
|
|
559
|
+
state = compactTaskSteps(state, now);
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
host.session.saveTaskStepsStateSnapshot(state);
|
|
563
|
+
host.showStatus(formatTaskSteps(state));
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
host.showError(error instanceof Error ? error.message : String(error));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
433
569
|
export async function handleGoalContinueCommand(host, text) {
|
|
434
570
|
const parsed = host.parseGoalContinueCommand(text);
|
|
435
571
|
if (!parsed.ok) {
|