apex-code 0.5.0 → 0.5.2
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 +263 -5
- package/README.md +624 -0
- package/dist/bun/cli.d.ts +3 -1
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +3 -9
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/runtime-setup.d.ts +2 -0
- package/dist/bun/runtime-setup.d.ts.map +1 -0
- package/dist/bun/runtime-setup.js +9 -0
- package/dist/bun/runtime-setup.js.map +1 -0
- package/dist/bun/sandbox-env-setup.d.ts +2 -0
- package/dist/bun/sandbox-env-setup.d.ts.map +1 -0
- package/dist/bun/sandbox-env-setup.js +4 -0
- package/dist/bun/sandbox-env-setup.js.map +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +16 -5
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +2 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/file-processor.d.ts +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/session-picker.d.ts +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli/setup.d.ts +2 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +13 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +16 -8
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +85 -15
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +685 -266
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +4 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +55 -0
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bug-report.d.ts +176 -0
- package/dist/core/bug-report.d.ts.map +1 -0
- package/dist/core/bug-report.js +291 -0
- package/dist/core/bug-report.js.map +1 -0
- package/dist/core/cache-stats.d.ts.map +1 -1
- package/dist/core/cache-stats.js +12 -1
- package/dist/core/cache-stats.js.map +1 -1
- package/dist/core/cache-warmer.d.ts +103 -0
- package/dist/core/cache-warmer.d.ts.map +1 -0
- package/dist/core/cache-warmer.js +356 -0
- package/dist/core/cache-warmer.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +4 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +5 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +163 -66
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/context/pipeline.d.ts +8 -8
- package/dist/core/context/pipeline.d.ts.map +1 -1
- package/dist/core/context/pipeline.js +21 -12
- package/dist/core/context/pipeline.js.map +1 -1
- package/dist/core/crash-log.d.ts +27 -0
- package/dist/core/crash-log.d.ts.map +1 -0
- package/dist/core/crash-log.js +139 -0
- package/dist/core/crash-log.js.map +1 -0
- package/dist/core/export-html/template.js +6 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/jiti-loader.d.ts +2 -0
- package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
- package/dist/core/extensions/jiti-loader.js +4 -0
- package/dist/core/extensions/jiti-loader.js.map +1 -0
- package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
- package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
- package/dist/core/extensions/jiti-static-loader.js +4 -0
- package/dist/core/extensions/jiti-static-loader.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +40 -59
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +24 -8
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +195 -72
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +142 -55
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/virtual-modules.d.ts +3 -0
- package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
- package/dist/core/extensions/virtual-modules.js +41 -0
- package/dist/core/extensions/virtual-modules.js.map +1 -0
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +1 -20
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -0
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +6 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +5 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +1 -0
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-config.d.ts +168 -20
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +44 -18
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +8 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +4 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +1 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +12 -6
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/permissions/store.d.ts.map +1 -1
- package/dist/core/permissions/store.js +11 -8
- package/dist/core/permissions/store.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +6 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +61 -35
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/provider-composer.d.ts +4 -2
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +29 -2
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/radius.d.ts +3 -0
- package/dist/core/radius.d.ts.map +1 -1
- package/dist/core/radius.js +6 -0
- package/dist/core/radius.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +6 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +63 -42
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-export.d.ts +6 -2
- package/dist/core/session-export.d.ts.map +1 -1
- package/dist/core/session-export.js +14 -13
- package/dist/core/session-export.js.map +1 -1
- package/dist/core/session-manager.d.ts +66 -15
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +295 -115
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +27 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +55 -7
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +5 -4
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts +49 -6
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +140 -79
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +7 -8
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +20 -115
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +1 -12
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +6 -204
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +4 -55
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +4 -60
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +4 -49
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +4 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +10 -125
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts +18 -0
- package/dist/core/tools/renderers/bash.d.ts.map +1 -0
- package/dist/core/tools/renderers/bash.js +126 -0
- package/dist/core/tools/renderers/bash.js.map +1 -0
- package/dist/core/tools/renderers/edit.d.ts +23 -0
- package/dist/core/tools/renderers/edit.d.ts.map +1 -0
- package/dist/core/tools/renderers/edit.js +207 -0
- package/dist/core/tools/renderers/edit.js.map +1 -0
- package/dist/core/tools/renderers/find.d.ts +10 -0
- package/dist/core/tools/renderers/find.d.ts.map +1 -0
- package/dist/core/tools/renderers/find.js +64 -0
- package/dist/core/tools/renderers/find.js.map +1 -0
- package/dist/core/tools/renderers/grep.d.ts +10 -0
- package/dist/core/tools/renderers/grep.d.ts.map +1 -0
- package/dist/core/tools/renderers/grep.js +69 -0
- package/dist/core/tools/renderers/grep.js.map +1 -0
- package/dist/core/tools/renderers/index.d.ts +34 -0
- package/dist/core/tools/renderers/index.d.ts.map +1 -0
- package/dist/core/tools/renderers/index.js +47 -0
- package/dist/core/tools/renderers/index.js.map +1 -0
- package/dist/core/tools/renderers/ls.d.ts +10 -0
- package/dist/core/tools/renderers/ls.d.ts.map +1 -0
- package/dist/core/tools/renderers/ls.js +58 -0
- package/dist/core/tools/renderers/ls.js.map +1 -0
- package/dist/core/tools/renderers/read.d.ts +11 -0
- package/dist/core/tools/renderers/read.d.ts.map +1 -0
- package/dist/core/tools/renderers/read.js +132 -0
- package/dist/core/tools/renderers/read.js.map +1 -0
- package/dist/core/tools/renderers/write.d.ts +10 -0
- package/dist/core/tools/renderers/write.d.ts.map +1 -0
- package/dist/core/tools/renderers/write.js +152 -0
- package/dist/core/tools/renderers/write.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +6 -146
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/usage-totals.d.ts +1 -1
- package/dist/core/usage-totals.d.ts.map +1 -1
- package/dist/core/usage-totals.js +5 -1
- package/dist/core/usage-totals.js.map +1 -1
- package/dist/extensions/llama/client.d.ts +2 -0
- package/dist/extensions/llama/client.d.ts.map +1 -1
- package/dist/extensions/llama/client.js +7 -3
- package/dist/extensions/llama/client.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +20 -5
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +21 -13
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/bug-report.d.ts +16 -0
- package/dist/modes/interactive/bug-report.d.ts.map +1 -0
- package/dist/modes/interactive/bug-report.js +167 -0
- package/dist/modes/interactive/bug-report.js.map +1 -0
- package/dist/modes/interactive/chat-viewport.d.ts +20 -0
- package/dist/modes/interactive/chat-viewport.d.ts.map +1 -0
- package/dist/modes/interactive/chat-viewport.js +28 -0
- package/dist/modes/interactive/chat-viewport.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +7 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +63 -17
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +12 -5
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +12 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +38 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/error-summary.d.ts +11 -0
- package/dist/modes/interactive/components/error-summary.d.ts.map +1 -0
- package/dist/modes/interactive/components/error-summary.js +31 -0
- package/dist/modes/interactive/components/error-summary.js.map +1 -0
- package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +6 -1
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts +2 -0
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-input.js +6 -0
- package/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +2 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +4 -0
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +3 -3
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +13 -15
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +5 -5
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +74 -48
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +5 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +35 -10
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-indicator.js +10 -3
- package/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +6 -6
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +27 -5
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +64 -42
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +9 -0
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/trust-selector.js +2 -2
- package/dist/modes/interactive/components/trust-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +43 -29
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +431 -166
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +2 -1
- package/dist/modes/interactive/theme/light.json +2 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +5 -0
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
- package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-json.js +130 -0
- package/dist/modes/interactive/theme/theme-json.js.map +1 -0
- package/dist/modes/interactive/theme/theme-schema.json +6 -2
- package/dist/modes/interactive/theme/theme.d.ts +14 -5
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +18 -119
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/tui-renderer.d.ts +21 -0
- package/dist/modes/interactive/tui-renderer.d.ts.map +1 -0
- package/dist/modes/interactive/tui-renderer.js +66 -0
- package/dist/modes/interactive/tui-renderer.js.map +1 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +31 -72
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/testing/replay/recorded-provider.d.ts +2 -2
- package/dist/testing/replay/recorded-provider.d.ts.map +1 -1
- package/dist/testing/replay/recorded-provider.js +2 -5
- package/dist/testing/replay/recorded-provider.js.map +1 -1
- package/dist/testing/replay/runner.d.ts.map +1 -1
- package/dist/testing/replay/runner.js +0 -1
- package/dist/testing/replay/runner.js.map +1 -1
- package/dist/utils/clipboard-command.d.ts +7 -0
- package/dist/utils/clipboard-command.d.ts.map +1 -0
- package/dist/utils/clipboard-command.js +45 -0
- package/dist/utils/clipboard-command.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +53 -81
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +111 -121
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/exif-orientation.d.ts.map +1 -1
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/mime.d.ts.map +1 -1
- package/dist/utils/mime.js +1 -1
- package/dist/utils/mime.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +21 -21
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/tool-result-images.d.ts +3 -1
- package/dist/utils/tool-result-images.d.ts.map +1 -1
- package/dist/utils/tool-result-images.js +4 -1
- package/dist/utils/tool-result-images.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +11 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/wsl.d.ts +3 -0
- package/dist/utils/wsl.d.ts.map +1 -0
- package/dist/utils/wsl.js +15 -0
- package/dist/utils/wsl.js.map +1 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.d.ts.map +1 -0
- package/dist/utils/zip.js +60 -0
- package/dist/utils/zip.js.map +1 -0
- package/docs/cli-integration.md +107 -0
- package/docs/cli.md +269 -0
- package/docs/compaction.md +73 -24
- package/docs/configuration.md +45 -0
- package/docs/containerization.md +22 -21
- package/docs/custom-provider.md +21 -12
- package/docs/development.md +19 -0
- package/docs/docs.json +139 -95
- package/docs/extensions.md +243 -371
- package/docs/how-pi-works.md +49 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/index.md +3 -3
- package/docs/json.md +7 -3
- package/docs/keybindings.md +57 -54
- package/docs/llama-cpp.md +2 -2
- package/docs/message-types.md +261 -0
- package/docs/models.md +103 -49
- package/docs/packages.md +49 -47
- package/docs/prompt-templates.md +29 -56
- package/docs/providers.md +91 -139
- package/docs/quickstart.md +42 -14
- package/docs/rpc-commands.md +854 -0
- package/docs/rpc-extension-ui.md +200 -0
- package/docs/rpc.md +121 -223
- package/docs/sdk.md +99 -124
- package/docs/security.md +7 -7
- package/docs/session-format.md +85 -82
- package/docs/sessions.md +41 -57
- package/docs/settings.md +94 -87
- package/docs/shell-aliases.md +68 -3
- package/docs/skills.md +62 -49
- package/docs/slash-commands.md +60 -0
- package/docs/terminal-setup.md +86 -55
- package/docs/termux.md +65 -75
- package/docs/themes.md +64 -86
- package/docs/tmux.md +29 -7
- package/docs/tui.md +67 -122
- package/docs/usage.md +31 -42
- package/docs/windows.md +41 -13
- package/examples/README.md +16 -2
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
- 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/index.ts +2 -2
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +2 -0
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/prompt-customizer.ts +19 -67
- 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/examples/plugins/pi-example-plugin/README.md +38 -0
- package/examples/plugins/pi-example-plugin/package.json +10 -0
- package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
- package/examples/plugins/pi-example-plugin/src/session.ts +24 -0
- package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
- package/examples/rpc-client.ts +35 -0
- package/examples/rpc-extension-ui.ts +25 -5
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +861 -513
- package/package.json +27 -18
- package/dist/bun/register-bedrock.d.ts +0 -2
- package/dist/bun/register-bedrock.d.ts.map +0 -1
- package/dist/bun/register-bedrock.js +0 -4
- package/dist/bun/register-bedrock.js.map +0 -1
- package/dist/cli/experimental/auth.d.ts +0 -16
- package/dist/cli/experimental/auth.d.ts.map +0 -1
- package/dist/cli/experimental/auth.js +0 -13
- package/dist/cli/experimental/auth.js.map +0 -1
- package/dist/cli/experimental/cli.d.ts +0 -6
- package/dist/cli/experimental/cli.d.ts.map +0 -1
- package/dist/cli/experimental/cli.js +0 -5
- package/dist/cli/experimental/cli.js.map +0 -1
- package/dist/cli/experimental/command-options.d.ts +0 -17
- package/dist/cli/experimental/command-options.d.ts.map +0 -1
- package/dist/cli/experimental/command-options.js +0 -35
- package/dist/cli/experimental/command-options.js.map +0 -1
- package/dist/cli/experimental/command.d.ts +0 -63
- package/dist/cli/experimental/command.d.ts.map +0 -1
- package/dist/cli/experimental/command.js +0 -130
- package/dist/cli/experimental/command.js.map +0 -1
- package/dist/cli/experimental/commands/client.d.ts +0 -13
- package/dist/cli/experimental/commands/client.d.ts.map +0 -1
- package/dist/cli/experimental/commands/client.js +0 -25
- package/dist/cli/experimental/commands/client.js.map +0 -1
- package/dist/cli/experimental/commands/pi.d.ts +0 -15
- package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
- package/dist/cli/experimental/commands/pi.js +0 -28
- package/dist/cli/experimental/commands/pi.js.map +0 -1
- package/dist/cli/experimental/commands/server.d.ts +0 -13
- package/dist/cli/experimental/commands/server.d.ts.map +0 -1
- package/dist/cli/experimental/commands/server.js +0 -25
- package/dist/cli/experimental/commands/server.js.map +0 -1
- package/dist/cli/experimental/transport-address.d.ts +0 -10
- package/dist/cli/experimental/transport-address.d.ts.map +0 -1
- package/dist/cli/experimental/transport-address.js +0 -38
- package/dist/cli/experimental/transport-address.js.map +0 -1
- package/dist/client/index.d.ts +0 -3
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -3
- package/dist/client/index.js.map +0 -1
- package/dist/client/remote-session.d.ts +0 -53
- package/dist/client/remote-session.d.ts.map +0 -1
- package/dist/client/remote-session.js +0 -340
- package/dist/client/remote-session.js.map +0 -1
- package/dist/client/transcript.d.ts +0 -12
- package/dist/client/transcript.d.ts.map +0 -1
- package/dist/client/transcript.js +0 -98
- package/dist/client/transcript.js.map +0 -1
- package/dist/utils/clipboard-native.d.ts +0 -11
- package/dist/utils/clipboard-native.d.ts.map +0 -1
- package/dist/utils/clipboard-native.js +0 -20
- package/dist/utils/clipboard-native.js.map +0 -1
|
@@ -14,16 +14,18 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { readFileSync } from "node:fs";
|
|
16
16
|
import { basename, dirname, isAbsolute, relative } from "node:path";
|
|
17
|
-
import { contentText } from "@earendil-works/pi-ai";
|
|
17
|
+
import { contentText, getCurrentSystemMessage, retryDelayMs } from "@earendil-works/pi-ai";
|
|
18
18
|
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRecoverableLength, isRetryableAssistantError, modelsAreEqual, resetApiProviders, } from "@earendil-works/pi-ai/compat";
|
|
19
19
|
import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
|
|
20
20
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
21
|
+
import { processImage } from "../utils/image-process.js";
|
|
21
22
|
import { sleep } from "../utils/sleep.js";
|
|
22
23
|
import { normalizeToolResultImages } from "../utils/tool-result-images.js";
|
|
23
24
|
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
|
24
25
|
import { executeBashWithOperations } from "./bash-executor.js";
|
|
26
|
+
import { generateBugReportSummary } from "./bug-report.js";
|
|
25
27
|
import { createSessionCheckpoints } from "./checkpoints/session-checkpoints.js";
|
|
26
|
-
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
28
|
+
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateProjectedContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
27
29
|
import { evictionBudget, installContextPipeline, isDefaultStreamFunction } from "./context/pipeline.js";
|
|
28
30
|
import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "./defaults.js";
|
|
29
31
|
import { RESUME_CHILD_PROMPT, runDelegation } from "./delegation/runtime.js";
|
|
@@ -33,6 +35,7 @@ import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
|
33
35
|
import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
34
36
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
35
37
|
import { runFormatterCommand } from "./formatter-lifecycle.js";
|
|
38
|
+
import { convertToLlm } from "./messages.js";
|
|
36
39
|
import { ModelRegistry } from "./model-registry.js";
|
|
37
40
|
import { evaluateToolCall } from "./permissions/gate.js";
|
|
38
41
|
import { authorizeConfiguredCommand } from "./permissions/policy-command.js";
|
|
@@ -41,10 +44,10 @@ import { resolveEffectiveModeWithOrigin } from "./permissions/startup.js";
|
|
|
41
44
|
import { loadPolicyConfiguration } from "./policy-loader.js";
|
|
42
45
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
43
46
|
import { exportSessionToJsonl } from "./session-export.js";
|
|
44
|
-
import { getLatestCompactionEntry, TODO_CUSTOM_ENTRY_TYPE } from "./session-manager.js";
|
|
47
|
+
import { getLatestCompactionEntry, SessionManager, TODO_CUSTOM_ENTRY_TYPE, } from "./session-manager.js";
|
|
45
48
|
import { slugifySkillCommandName } from "./skills.js";
|
|
46
49
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
47
|
-
import { buildSystemPrompt } from "./system-prompt.js";
|
|
50
|
+
import { buildSystemPrompt, buildSystemPromptSections, diffSystemPromptSections, normalizeBuildSystemPromptOptions, } from "./system-prompt.js";
|
|
48
51
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
49
52
|
import { buildToolContractSnapshot, isUnclassifiedTool, } from "./tools/contract-snapshot.js";
|
|
50
53
|
import { createAllToolDefinitions } from "./tools/index.js";
|
|
@@ -103,6 +106,7 @@ export class AgentSession {
|
|
|
103
106
|
_unsubscribeAgent;
|
|
104
107
|
_eventListeners = [];
|
|
105
108
|
_isAgentRunActive = false;
|
|
109
|
+
_agentRunAbortRequested = false;
|
|
106
110
|
_idleWaitPromise;
|
|
107
111
|
_resolveIdleWait;
|
|
108
112
|
/** Tracks pending steering messages for UI display. Removed when delivered. */
|
|
@@ -129,6 +133,15 @@ export class AgentSession {
|
|
|
129
133
|
// Extension system
|
|
130
134
|
_extensionRunner;
|
|
131
135
|
_turnIndex = 0;
|
|
136
|
+
_entryIdsByMessage = new WeakMap();
|
|
137
|
+
_boundaryDispatchedMessages = new WeakSet();
|
|
138
|
+
_lastAssistantMessage;
|
|
139
|
+
_lastAssistantToolResults = [];
|
|
140
|
+
_lastActivityOutcome = "completed";
|
|
141
|
+
_isBeforeSettle = false;
|
|
142
|
+
_abortDuringBeforeSettle = false;
|
|
143
|
+
_isEmittingAgentSettled = false;
|
|
144
|
+
_deferredSettledActions = [];
|
|
132
145
|
_resourceLoader;
|
|
133
146
|
_customTools;
|
|
134
147
|
_baseToolDefinitions = new Map();
|
|
@@ -183,16 +196,16 @@ export class AgentSession {
|
|
|
183
196
|
_extensionErrorListener;
|
|
184
197
|
_extensionErrorUnsubscriber;
|
|
185
198
|
_modelRuntime;
|
|
199
|
+
_cacheWarmer;
|
|
186
200
|
// Tool registry for extension getTools/setTools
|
|
187
201
|
_toolRegistry = new Map();
|
|
188
202
|
_toolDefinitions = new Map();
|
|
189
203
|
_toolPromptSnippets = new Map();
|
|
190
204
|
_toolPromptGuidelines = new Map();
|
|
191
205
|
_loadedDeferredSchemas = new Set();
|
|
192
|
-
// Base system prompt (without extension appends) - used to apply fresh appends each turn
|
|
193
|
-
_baseSystemPrompt = "";
|
|
194
206
|
_baseSystemPromptOptions;
|
|
195
|
-
|
|
207
|
+
/** Prompt options after before_agent_start mutations for the active run. */
|
|
208
|
+
_runSystemPromptOptions;
|
|
196
209
|
constructor(config) {
|
|
197
210
|
this.agent = config.agent;
|
|
198
211
|
this.sessionManager = config.sessionManager;
|
|
@@ -202,6 +215,10 @@ export class AgentSession {
|
|
|
202
215
|
this._customTools = config.customTools ?? [];
|
|
203
216
|
this._cwd = config.cwd;
|
|
204
217
|
this._modelRuntime = config.modelRuntime;
|
|
218
|
+
this._cacheWarmer = config.cacheWarmer;
|
|
219
|
+
if (this._cacheWarmer) {
|
|
220
|
+
this._cacheWarmer.onWarmed = (entry) => this._emit({ type: "entry_appended", entry });
|
|
221
|
+
}
|
|
205
222
|
this._extensionRunnerRef = config.extensionRunnerRef;
|
|
206
223
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
|
207
224
|
this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
|
|
@@ -245,6 +262,9 @@ export class AgentSession {
|
|
|
245
262
|
this._installAgentNextTurnRefresh();
|
|
246
263
|
this._installMidRunCompactionHook();
|
|
247
264
|
this._installContextPipeline();
|
|
265
|
+
this._installAgentRequestProjection();
|
|
266
|
+
this._installAgentBoundaryHooks();
|
|
267
|
+
this._installAgentForcedPromptProjection();
|
|
248
268
|
// Declarative hooks: session_start is observe-only and must neither delay
|
|
249
269
|
// nor fail construction, so it fires detached (emitObserve swallows
|
|
250
270
|
// handler failures already).
|
|
@@ -258,14 +278,16 @@ export class AgentSession {
|
|
|
258
278
|
activeToolNames: this._initialActiveToolNames,
|
|
259
279
|
includeAllExtensionTools: true,
|
|
260
280
|
});
|
|
281
|
+
if (this._initialActiveToolNames === undefined)
|
|
282
|
+
this._restoreToolsFromTranscript();
|
|
261
283
|
}
|
|
262
284
|
get modelRuntime() {
|
|
263
285
|
return this._modelRuntime;
|
|
264
286
|
}
|
|
265
|
-
async _getRequiredRequestAuth(model) {
|
|
287
|
+
async _getRequiredRequestAuth(model, signal) {
|
|
266
288
|
let result;
|
|
267
289
|
try {
|
|
268
|
-
result = await this._modelRuntime.getAuth(model);
|
|
290
|
+
result = await this._modelRuntime.getAuth(model, { signal });
|
|
269
291
|
}
|
|
270
292
|
catch (error) {
|
|
271
293
|
const cause = error instanceof Error ? error.cause : undefined;
|
|
@@ -291,12 +313,12 @@ export class AgentSession {
|
|
|
291
313
|
}
|
|
292
314
|
throw new Error(formatNoApiKeyFoundMessage(model.provider));
|
|
293
315
|
}
|
|
294
|
-
async _getSummarizationRequestAuth(model) {
|
|
316
|
+
async _getSummarizationRequestAuth(model, signal) {
|
|
295
317
|
if (isDefaultStreamFunction(this.agent.streamFunction)) {
|
|
296
|
-
return this._getRequiredRequestAuth(model);
|
|
318
|
+
return this._getRequiredRequestAuth(model, signal);
|
|
297
319
|
}
|
|
298
320
|
try {
|
|
299
|
-
const result = await this._modelRuntime.getAuth(model);
|
|
321
|
+
const result = await this._modelRuntime.getAuth(model, { signal });
|
|
300
322
|
if (!result)
|
|
301
323
|
return { model };
|
|
302
324
|
const requestModel = result.auth.baseUrl ? { ...model, baseUrl: result.auth.baseUrl } : model;
|
|
@@ -307,7 +329,9 @@ export class AgentSession {
|
|
|
307
329
|
env: result.env,
|
|
308
330
|
};
|
|
309
331
|
}
|
|
310
|
-
catch {
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (signal?.aborted)
|
|
334
|
+
throw error;
|
|
311
335
|
return { model };
|
|
312
336
|
}
|
|
313
337
|
}
|
|
@@ -419,8 +443,10 @@ export class AgentSession {
|
|
|
419
443
|
}
|
|
420
444
|
const content = hookResult?.content ?? result.content ?? [];
|
|
421
445
|
// Runs after the extension hook so images injected or replaced by extensions are normalized too.
|
|
446
|
+
const resizeOptions = this.model?.inputLimits?.images?.resize;
|
|
422
447
|
const normalizedContent = await normalizeToolResultImages(content, {
|
|
423
448
|
autoResizeImages: this.settingsManager.getImageAutoResize(),
|
|
449
|
+
...(resizeOptions ? { resizeOptions } : {}),
|
|
424
450
|
});
|
|
425
451
|
if (!hookResult && normalizedContent === content) {
|
|
426
452
|
return undefined;
|
|
@@ -435,16 +461,84 @@ export class AgentSession {
|
|
|
435
461
|
}
|
|
436
462
|
async _compactBeforeNextAssistantResponse(context) {
|
|
437
463
|
const model = this.model;
|
|
438
|
-
const settings = this.settingsManager.getCompactionSettings();
|
|
464
|
+
const settings = this.settingsManager.getCompactionSettings(model);
|
|
465
|
+
const projection = this.sessionManager.buildSessionProjection();
|
|
439
466
|
if (!model ||
|
|
440
467
|
model.contextWindow <= 0 ||
|
|
441
|
-
!shouldCompact(
|
|
442
|
-
return context;
|
|
468
|
+
!shouldCompact(estimateProjectedContextTokens(projection, this.sessionManager.getBranch()).tokens, model.contextWindow, settings)) {
|
|
469
|
+
return { ...context, messages: projection.messages };
|
|
443
470
|
}
|
|
444
471
|
await this._runAutoCompaction("threshold", false);
|
|
445
|
-
return {
|
|
446
|
-
|
|
447
|
-
|
|
472
|
+
return { ...context, messages: this.sessionManager.buildSessionProjection().messages };
|
|
473
|
+
}
|
|
474
|
+
_installAgentRequestProjection() {
|
|
475
|
+
const previousPrepareRequest = this.agent.prepareRequest;
|
|
476
|
+
this.agent.prepareRequest = async (request, signal) => {
|
|
477
|
+
const canonicalContext = {
|
|
478
|
+
...request.context,
|
|
479
|
+
messages: this.sessionManager.buildSessionProjection().messages,
|
|
480
|
+
// Messages declare the provider-visible loadout; context.tools keeps executable implementations.
|
|
481
|
+
tools: this.agent.state.tools.slice(),
|
|
482
|
+
};
|
|
483
|
+
const previous = await previousPrepareRequest?.({
|
|
484
|
+
...request,
|
|
485
|
+
context: canonicalContext,
|
|
486
|
+
model: this.agent.state.model,
|
|
487
|
+
thinkingLevel: this.agent.state.thinkingLevel,
|
|
488
|
+
}, signal);
|
|
489
|
+
return {
|
|
490
|
+
...previous,
|
|
491
|
+
context: previous?.context ?? canonicalContext,
|
|
492
|
+
model: previous?.model ?? this.agent.state.model,
|
|
493
|
+
thinkingLevel: previous?.thinkingLevel ?? this.agent.state.thinkingLevel,
|
|
494
|
+
};
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
async _dispatchTurnEndBoundary(message, toolResults) {
|
|
498
|
+
this._lastActivityOutcome =
|
|
499
|
+
message.stopReason === "aborted" ? "aborted" : message.stopReason === "error" ? "error" : "completed";
|
|
500
|
+
const messageEntryId = this._findPersistedMessageEntryId(message);
|
|
501
|
+
if (!this._extensionRunner.hasHandlers("turn_end"))
|
|
502
|
+
return false;
|
|
503
|
+
if (!messageEntryId) {
|
|
504
|
+
this._extensionRunner.emitError({
|
|
505
|
+
extensionPath: "<boundary>",
|
|
506
|
+
event: "turn_end",
|
|
507
|
+
error: "turn_end could not resolve the persisted assistant entry ID",
|
|
508
|
+
});
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
const toolResultEntryIds = toolResults.flatMap((result) => {
|
|
512
|
+
const entryId = this._findPersistedMessageEntryId(result);
|
|
513
|
+
return entryId ? [entryId] : [];
|
|
514
|
+
});
|
|
515
|
+
const boundary = await this._extensionRunner.emitBoundary({
|
|
516
|
+
type: "turn_end",
|
|
517
|
+
turnIndex: this._turnIndex,
|
|
518
|
+
message,
|
|
519
|
+
toolResults,
|
|
520
|
+
messageEntryId,
|
|
521
|
+
toolResultEntryIds,
|
|
522
|
+
outcome: this._lastActivityOutcome,
|
|
523
|
+
}, (entries) => this._buildBoundaryContext(entries, "turn_end"));
|
|
524
|
+
this._commitBoundaryDrafts(boundary.entries);
|
|
525
|
+
if (boundary.continue && !this._buildBoundaryContext([], "turn_end").canContinue) {
|
|
526
|
+
this._reportInvalidBoundaryContinuation("turn_end");
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
return boundary.continue;
|
|
530
|
+
}
|
|
531
|
+
_installAgentBoundaryHooks() {
|
|
532
|
+
const previousFinishTurn = this.agent.finishTurn;
|
|
533
|
+
this.agent.finishTurn = async (turn, signal) => {
|
|
534
|
+
this._boundaryDispatchedMessages.add(turn.message);
|
|
535
|
+
const extensionContinue = await this._dispatchTurnEndBoundary(turn.message, turn.toolResults);
|
|
536
|
+
const previousDecision = await previousFinishTurn?.(turn, signal);
|
|
537
|
+
if (previousDecision?.action === "end")
|
|
538
|
+
return previousDecision;
|
|
539
|
+
if (extensionContinue || previousDecision?.action === "continue")
|
|
540
|
+
return { action: "continue" };
|
|
541
|
+
return undefined;
|
|
448
542
|
};
|
|
449
543
|
}
|
|
450
544
|
_installAgentNextTurnRefresh() {
|
|
@@ -453,16 +547,31 @@ export class AgentSession {
|
|
|
453
547
|
? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal)
|
|
454
548
|
: undefined);
|
|
455
549
|
this.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
456
|
-
const context = await this._compactBeforeNextAssistantResponse(
|
|
550
|
+
const context = await this._compactBeforeNextAssistantResponse({
|
|
551
|
+
...turn.context,
|
|
552
|
+
messages: this.sessionManager.buildSessionProjection().messages,
|
|
553
|
+
});
|
|
457
554
|
const previousSnapshot = await previousPrepareNextTurnWithContext?.({ ...turn, context }, signal);
|
|
458
555
|
const nextContext = previousSnapshot?.context ?? context;
|
|
556
|
+
const runOptions = this._runSystemPromptOptions ?? this._baseSystemPromptOptions;
|
|
557
|
+
const options = normalizeBuildSystemPromptOptions({
|
|
558
|
+
...runOptions,
|
|
559
|
+
selectedTools: this.getActiveToolNames(),
|
|
560
|
+
toolSnippets: { ...this._baseSystemPromptOptions.toolSnippets, ...runOptions.toolSnippets },
|
|
561
|
+
toolGuidelines: { ...this._baseSystemPromptOptions.toolGuidelines, ...runOptions.toolGuidelines },
|
|
562
|
+
});
|
|
563
|
+
const updateMessage = this._preparePromptAndToolLoadout(options, nextContext.messages);
|
|
564
|
+
// Keep session.systemPrompt and ctx.getSystemPrompt() in step with what the provider sees.
|
|
565
|
+
this._runSystemPromptOptions = options;
|
|
459
566
|
return {
|
|
460
567
|
...previousSnapshot,
|
|
461
568
|
context: {
|
|
462
569
|
...nextContext,
|
|
463
|
-
systemPrompt: this._systemPromptOverride ?? this._baseSystemPrompt,
|
|
464
570
|
tools: this.agent.state.tools.slice(),
|
|
465
571
|
},
|
|
572
|
+
messages: updateMessage
|
|
573
|
+
? [...(previousSnapshot?.messages ?? []), updateMessage]
|
|
574
|
+
: previousSnapshot?.messages,
|
|
466
575
|
model: this.agent.state.model,
|
|
467
576
|
thinkingLevel: this.agent.state.thinkingLevel,
|
|
468
577
|
};
|
|
@@ -540,6 +649,84 @@ export class AgentSession {
|
|
|
540
649
|
// =========================================================================
|
|
541
650
|
// Event Subscription
|
|
542
651
|
// =========================================================================
|
|
652
|
+
_refreshFinalizedContext() {
|
|
653
|
+
const projection = this.sessionManager.buildSessionProjection();
|
|
654
|
+
for (const entry of projection.entries) {
|
|
655
|
+
for (const message of entry.messages)
|
|
656
|
+
this._entryIdsByMessage.set(message, entry.sourceEntry.id);
|
|
657
|
+
}
|
|
658
|
+
this.agent.state.messages = projection.messages;
|
|
659
|
+
}
|
|
660
|
+
_applyBoundaryDrafts(manager, drafts) {
|
|
661
|
+
const appended = [];
|
|
662
|
+
for (const draft of drafts) {
|
|
663
|
+
let entryId;
|
|
664
|
+
switch (draft.type) {
|
|
665
|
+
case "custom":
|
|
666
|
+
entryId = manager.appendCustomEntry(draft.customType, draft.data);
|
|
667
|
+
break;
|
|
668
|
+
case "custom_message":
|
|
669
|
+
entryId = manager.appendCustomMessageEntry(draft.customType, draft.content, draft.display, draft.details);
|
|
670
|
+
break;
|
|
671
|
+
case "context_edit":
|
|
672
|
+
entryId = manager.appendContextEdit(draft.targetId, draft.replacement);
|
|
673
|
+
break;
|
|
674
|
+
case "compaction": {
|
|
675
|
+
const tokensBefore = estimateProjectedContextTokens(manager.buildSessionProjection(), manager.getBranch()).tokens;
|
|
676
|
+
entryId = manager.appendCompaction(draft.summary, draft.firstKeptEntryId, tokensBefore, draft.details, true, draft.usage);
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const entry = manager.getEntry(entryId);
|
|
681
|
+
if (entry)
|
|
682
|
+
appended.push(entry);
|
|
683
|
+
}
|
|
684
|
+
return appended;
|
|
685
|
+
}
|
|
686
|
+
_createBoundaryPreviewManager(drafts) {
|
|
687
|
+
const header = this.sessionManager.getHeader();
|
|
688
|
+
if (!header)
|
|
689
|
+
throw new Error("Session header is missing");
|
|
690
|
+
const manager = SessionManager.inMemory(this._cwd, undefined, [header, ...this.sessionManager.getBranch()]);
|
|
691
|
+
this._applyBoundaryDrafts(manager, drafts);
|
|
692
|
+
return manager;
|
|
693
|
+
}
|
|
694
|
+
_getPendingBoundaryMessages() {
|
|
695
|
+
return [...this.agent.peekQueuedMessages(), ...this._pendingCustomMessages];
|
|
696
|
+
}
|
|
697
|
+
_buildBoundaryContext(drafts, boundary) {
|
|
698
|
+
const projection = this._createBoundaryPreviewManager(drafts).buildSessionProjection();
|
|
699
|
+
const pendingMessages = this._getPendingBoundaryMessages();
|
|
700
|
+
const llmMessages = convertToLlm(projection.messages);
|
|
701
|
+
const finalRole = llmMessages[llmMessages.length - 1]?.role;
|
|
702
|
+
const hasNonSystemContext = llmMessages.some((message) => message.role !== "system");
|
|
703
|
+
const contextCanContinue = hasNonSystemContext && finalRole !== "assistant";
|
|
704
|
+
const pendingCustomContext = this._pendingCustomMessages.length > 0;
|
|
705
|
+
return {
|
|
706
|
+
contextEntries: projection.entries,
|
|
707
|
+
contextMessages: projection.messages,
|
|
708
|
+
llmMessages,
|
|
709
|
+
pendingMessages,
|
|
710
|
+
canContinue: contextCanContinue ||
|
|
711
|
+
pendingCustomContext ||
|
|
712
|
+
(boundary === "turn_end"
|
|
713
|
+
? this.agent.hasQueuedMessages()
|
|
714
|
+
: finalRole === "assistant" && this.agent.hasQueuedMessages()),
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
_commitBoundaryDrafts(drafts) {
|
|
718
|
+
const appended = this._applyBoundaryDrafts(this.sessionManager, drafts);
|
|
719
|
+
this._refreshFinalizedContext();
|
|
720
|
+
for (const entry of appended)
|
|
721
|
+
this._emit({ type: "entry_appended", entry });
|
|
722
|
+
}
|
|
723
|
+
_reportInvalidBoundaryContinuation(event) {
|
|
724
|
+
this._extensionRunner.emitError({
|
|
725
|
+
extensionPath: "<boundary>",
|
|
726
|
+
event,
|
|
727
|
+
error: `${event} requested continuation without runnable model context`,
|
|
728
|
+
});
|
|
729
|
+
}
|
|
543
730
|
/** Emit an event to all listeners */
|
|
544
731
|
_emit(event) {
|
|
545
732
|
for (const l of this._eventListeners) {
|
|
@@ -567,7 +754,7 @@ export class AgentSession {
|
|
|
567
754
|
return this._idleWaitPromise;
|
|
568
755
|
}
|
|
569
756
|
_resolveIdleWaitIfIdle() {
|
|
570
|
-
if (this.
|
|
757
|
+
if (!this.isIdle || !this._resolveIdleWait) {
|
|
571
758
|
return;
|
|
572
759
|
}
|
|
573
760
|
const resolve = this._resolveIdleWait;
|
|
@@ -576,17 +763,29 @@ export class AgentSession {
|
|
|
576
763
|
resolve();
|
|
577
764
|
}
|
|
578
765
|
async _emitAgentSettled() {
|
|
766
|
+
this._cacheWarmer?.onAgentSettled();
|
|
579
767
|
this._isAgentRunActive = false;
|
|
768
|
+
this._isEmittingAgentSettled = true;
|
|
580
769
|
try {
|
|
581
770
|
await this._extensionRunner.emit({ type: "agent_settled" });
|
|
582
771
|
this._emit({ type: "agent_settled" });
|
|
583
772
|
}
|
|
584
773
|
finally {
|
|
585
|
-
this.
|
|
774
|
+
this._isEmittingAgentSettled = false;
|
|
586
775
|
}
|
|
776
|
+
const deferred = this._deferredSettledActions.splice(0);
|
|
777
|
+
if (deferred.length > 0) {
|
|
778
|
+
try {
|
|
779
|
+
for (const action of deferred)
|
|
780
|
+
await action();
|
|
781
|
+
}
|
|
782
|
+
finally {
|
|
783
|
+
this._resolveIdleWaitIfIdle();
|
|
784
|
+
}
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
this._resolveIdleWaitIfIdle();
|
|
587
788
|
}
|
|
588
|
-
// Track last assistant message for auto-compaction check
|
|
589
|
-
_lastAssistantMessage = undefined;
|
|
590
789
|
/** Internal handler for agent events - shared by subscribe and reconnect */
|
|
591
790
|
_handleAgentEvent = async (event) => {
|
|
592
791
|
// When a user message starts, check if it's from either queue and remove it BEFORE emitting
|
|
@@ -611,28 +810,30 @@ export class AgentSession {
|
|
|
611
810
|
}
|
|
612
811
|
}
|
|
613
812
|
}
|
|
614
|
-
// Emit to extensions first
|
|
813
|
+
// Emit to extensions first, then notify public listeners.
|
|
615
814
|
await this._emitExtensionEvent(event);
|
|
616
|
-
// Notify all listeners
|
|
617
815
|
this._emit(event.type === "agent_end" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event);
|
|
618
816
|
// Handle session persistence
|
|
619
817
|
if (event.type === "message_end") {
|
|
818
|
+
let entryId;
|
|
620
819
|
// Check if this is a custom message from extensions
|
|
621
820
|
if (event.message.role === "custom") {
|
|
622
821
|
// Persist as CustomMessageEntry
|
|
623
|
-
this.sessionManager.appendCustomMessageEntry(event.message.customType, event.message.content, event.message.display, event.message.details);
|
|
822
|
+
entryId = this.sessionManager.appendCustomMessageEntry(event.message.customType, event.message.content, event.message.display, event.message.details);
|
|
624
823
|
}
|
|
625
|
-
else if (event.message.role === "
|
|
824
|
+
else if (event.message.role === "system" ||
|
|
825
|
+
event.message.role === "user" ||
|
|
626
826
|
event.message.role === "assistant" ||
|
|
627
827
|
event.message.role === "toolResult") {
|
|
628
828
|
// Regular LLM message - persist as SessionMessageEntry
|
|
629
|
-
this.sessionManager.appendMessage(event.message);
|
|
829
|
+
entryId = this.sessionManager.appendMessage(event.message);
|
|
630
830
|
}
|
|
831
|
+
if (entryId)
|
|
832
|
+
this._entryIdsByMessage.set(event.message, entryId);
|
|
631
833
|
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
|
632
|
-
// Track assistant message for auto-compaction (checked on agent_end)
|
|
633
834
|
if (event.message.role === "assistant") {
|
|
634
|
-
this._lastAssistantMessage = event.message;
|
|
635
835
|
const assistantMsg = event.message;
|
|
836
|
+
this._lastAssistantMessage = assistantMsg;
|
|
636
837
|
if (assistantMsg.stopReason !== "error" && assistantMsg.stopReason !== "length") {
|
|
637
838
|
this._overflowRecoveryAttempted = false;
|
|
638
839
|
}
|
|
@@ -654,10 +855,13 @@ export class AgentSession {
|
|
|
654
855
|
// extension and listener dispatch above also picks up messages that turn_end
|
|
655
856
|
// handlers queued.
|
|
656
857
|
if (event.type === "turn_end") {
|
|
858
|
+
this._lastAssistantToolResults = event.toolResults;
|
|
657
859
|
this._flushPendingCustomMessages();
|
|
658
860
|
}
|
|
659
861
|
};
|
|
660
862
|
_willRetryAfterAgentEnd(event) {
|
|
863
|
+
if (this._agentRunAbortRequested)
|
|
864
|
+
return false;
|
|
661
865
|
const settings = this.settingsManager.getRetrySettings();
|
|
662
866
|
if (!settings.enabled || this._retryAttempt >= settings.maxRetries) {
|
|
663
867
|
return false;
|
|
@@ -670,6 +874,47 @@ export class AgentSession {
|
|
|
670
874
|
}
|
|
671
875
|
return false;
|
|
672
876
|
}
|
|
877
|
+
_findPersistedMessageEntryId(message) {
|
|
878
|
+
const mapped = this._entryIdsByMessage.get(message);
|
|
879
|
+
if (mapped)
|
|
880
|
+
return mapped;
|
|
881
|
+
for (const entry of [...this.sessionManager.getBranch()].reverse()) {
|
|
882
|
+
if (entry.type === "message" && entry.message === message)
|
|
883
|
+
return entry.id;
|
|
884
|
+
}
|
|
885
|
+
const messageIndex = this.agent.state.messages.indexOf(message);
|
|
886
|
+
if (messageIndex < 0)
|
|
887
|
+
return undefined;
|
|
888
|
+
const projection = this.sessionManager.buildSessionProjection();
|
|
889
|
+
let projectedIndex = 0;
|
|
890
|
+
for (const entry of projection.entries) {
|
|
891
|
+
for (let i = 0; i < entry.messages.length; i++) {
|
|
892
|
+
if (projectedIndex === messageIndex) {
|
|
893
|
+
this._entryIdsByMessage.set(message, entry.sourceEntry.id);
|
|
894
|
+
return entry.sourceEntry.id;
|
|
895
|
+
}
|
|
896
|
+
projectedIndex++;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return undefined;
|
|
900
|
+
}
|
|
901
|
+
_omitRecoveryAttempt(message, toolResults = []) {
|
|
902
|
+
const targets = [message, ...toolResults];
|
|
903
|
+
const targetIds = targets.map((target) => this._findPersistedMessageEntryId(target));
|
|
904
|
+
const unresolvedProjectedTarget = targets.some((target, index) => targetIds[index] === undefined && this.agent.state.messages.includes(target));
|
|
905
|
+
if (unresolvedProjectedTarget) {
|
|
906
|
+
throw new Error("Cannot persist recovery omission because a projected message has no source entry");
|
|
907
|
+
}
|
|
908
|
+
for (const targetId of targetIds) {
|
|
909
|
+
if (!targetId)
|
|
910
|
+
continue;
|
|
911
|
+
const editId = this.sessionManager.appendContextEdit(targetId, null);
|
|
912
|
+
const entry = this.sessionManager.getEntry(editId);
|
|
913
|
+
if (entry)
|
|
914
|
+
this._emit({ type: "entry_appended", entry });
|
|
915
|
+
}
|
|
916
|
+
this._refreshFinalizedContext();
|
|
917
|
+
}
|
|
673
918
|
/** Find the last assistant message in agent state (including aborted ones) */
|
|
674
919
|
_findLastAssistantMessage() {
|
|
675
920
|
const messages = this.agent.state.messages;
|
|
@@ -725,13 +970,9 @@ export class AgentSession {
|
|
|
725
970
|
await this._extensionRunner.emit(extensionEvent);
|
|
726
971
|
}
|
|
727
972
|
else if (event.type === "turn_end") {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
message: event.message,
|
|
732
|
-
toolResults: event.toolResults,
|
|
733
|
-
};
|
|
734
|
-
await this._extensionRunner.emit(extensionEvent);
|
|
973
|
+
if (event.message.role === "assistant" && !this._boundaryDispatchedMessages.delete(event.message)) {
|
|
974
|
+
await this._dispatchTurnEndBoundary(event.message, event.toolResults);
|
|
975
|
+
}
|
|
735
976
|
if (this._hookRuntime?.hasHandlers("turn_end")) {
|
|
736
977
|
await this._hookRuntime.emitObserve("turn_end", { type: "turn_end" });
|
|
737
978
|
}
|
|
@@ -1041,15 +1282,32 @@ export class AgentSession {
|
|
|
1041
1282
|
// Every connected MCP server is a child process this session started. Without
|
|
1042
1283
|
// this, one leaks per server per session, because nothing else owns them.
|
|
1043
1284
|
this._mcpRuntime?.close().catch(() => undefined);
|
|
1285
|
+
if (this._cacheWarmer) {
|
|
1286
|
+
this._cacheWarmer.onWarmed = undefined;
|
|
1287
|
+
this._cacheWarmer.cancel();
|
|
1288
|
+
}
|
|
1044
1289
|
cleanupSessionResources(this.sessionId);
|
|
1045
1290
|
}
|
|
1046
1291
|
// =========================================================================
|
|
1047
1292
|
// Read-only State Access
|
|
1048
1293
|
// =========================================================================
|
|
1294
|
+
/** Refresh the public finalized transcript from the canonical session projection. */
|
|
1295
|
+
refreshContext() {
|
|
1296
|
+
this._refreshFinalizedContext();
|
|
1297
|
+
}
|
|
1049
1298
|
/** Full agent state */
|
|
1050
1299
|
get state() {
|
|
1051
1300
|
return this.agent.state;
|
|
1052
1301
|
}
|
|
1302
|
+
/** Current cache-warming state and the policy inputs that produced it. */
|
|
1303
|
+
get cacheWarmingStatus() {
|
|
1304
|
+
return this._cacheWarmer?.status;
|
|
1305
|
+
}
|
|
1306
|
+
/** Persist the cache-warming mode and immediately reconcile active warming. */
|
|
1307
|
+
setCacheWarmingMode(mode) {
|
|
1308
|
+
this.settingsManager.setCacheWarmingMode(mode);
|
|
1309
|
+
this._cacheWarmer?.onModeChanged();
|
|
1310
|
+
}
|
|
1053
1311
|
/** Current model (may be undefined if not yet selected) */
|
|
1054
1312
|
get model() {
|
|
1055
1313
|
return this.agent.state.model;
|
|
@@ -1062,13 +1320,13 @@ export class AgentSession {
|
|
|
1062
1320
|
get isStreaming() {
|
|
1063
1321
|
return this._isAgentRunActive;
|
|
1064
1322
|
}
|
|
1065
|
-
/** Whether the session has no active agent run,
|
|
1323
|
+
/** Whether the session has no active agent run, compaction, branch summary, retry, or queued continuation. */
|
|
1066
1324
|
get isIdle() {
|
|
1067
|
-
return !this._isAgentRunActive;
|
|
1325
|
+
return !this._isAgentRunActive && !this.isCompacting;
|
|
1068
1326
|
}
|
|
1069
|
-
/** Current effective system prompt
|
|
1327
|
+
/** Current effective system prompt, including changes not yet sent to the model. */
|
|
1070
1328
|
get systemPrompt() {
|
|
1071
|
-
return this.
|
|
1329
|
+
return buildSystemPrompt(this._runSystemPromptOptions ?? this._baseSystemPromptOptions);
|
|
1072
1330
|
}
|
|
1073
1331
|
/** Current retry attempt (0 if not retrying) */
|
|
1074
1332
|
get retryAttempt() {
|
|
@@ -1124,9 +1382,7 @@ export class AgentSession {
|
|
|
1124
1382
|
}
|
|
1125
1383
|
}
|
|
1126
1384
|
this.agent.state.tools = tools;
|
|
1127
|
-
|
|
1128
|
-
this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
|
|
1129
|
-
this.agent.state.systemPrompt = this._systemPromptOverride ?? this._baseSystemPrompt;
|
|
1385
|
+
this._rebuildSystemPrompt(validToolNames);
|
|
1130
1386
|
}
|
|
1131
1387
|
/** Whether compaction or branch summarization is currently running */
|
|
1132
1388
|
get isCompacting() {
|
|
@@ -1195,23 +1451,17 @@ export class AgentSession {
|
|
|
1195
1451
|
_rebuildSystemPrompt(toolNames) {
|
|
1196
1452
|
const validToolNames = toolNames.filter((name) => this._toolRegistry.has(name));
|
|
1197
1453
|
const toolSnippets = {};
|
|
1198
|
-
const
|
|
1199
|
-
for (const name of validToolNames) {
|
|
1454
|
+
for (const name of this._toolRegistry.keys()) {
|
|
1200
1455
|
const snippet = this._toolPromptSnippets.get(name);
|
|
1201
|
-
if (snippet)
|
|
1456
|
+
if (snippet)
|
|
1202
1457
|
toolSnippets[name] = snippet;
|
|
1203
|
-
}
|
|
1204
|
-
const toolGuidelines = this._toolPromptGuidelines.get(name);
|
|
1205
|
-
if (toolGuidelines) {
|
|
1206
|
-
promptGuidelines.push(...toolGuidelines);
|
|
1207
|
-
}
|
|
1208
1458
|
}
|
|
1209
1459
|
const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
|
|
1210
1460
|
const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
|
|
1211
|
-
const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") :
|
|
1461
|
+
const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : "";
|
|
1212
1462
|
const loadedSkills = this._resourceLoader.getSkills().skills;
|
|
1213
1463
|
const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
|
|
1214
|
-
this._baseSystemPromptOptions = {
|
|
1464
|
+
this._baseSystemPromptOptions = normalizeBuildSystemPromptOptions({
|
|
1215
1465
|
cwd: this._cwd,
|
|
1216
1466
|
skills: loadedSkills,
|
|
1217
1467
|
contextFiles: loadedContextFiles,
|
|
@@ -1219,23 +1469,95 @@ export class AgentSession {
|
|
|
1219
1469
|
appendSystemPrompt,
|
|
1220
1470
|
selectedTools: validToolNames,
|
|
1221
1471
|
toolSnippets,
|
|
1222
|
-
|
|
1472
|
+
toolGuidelines: Object.fromEntries(this._toolPromptGuidelines),
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Apply a prompt and tool loadout for the next request. Sets the executable tools and
|
|
1477
|
+
* returns a system message patching the prompt sections the model currently has (replayed
|
|
1478
|
+
* from `messages`), or undefined when the prompt is unchanged. Tool changes are declared by
|
|
1479
|
+
* the agent loop before the request.
|
|
1480
|
+
*
|
|
1481
|
+
* A forced prompt does not affect the transcript: the structured sections are still diffed
|
|
1482
|
+
* and persisted, and the forced text is projected onto the request by
|
|
1483
|
+
* {@link _installAgentForcedPromptProjection}.
|
|
1484
|
+
*/
|
|
1485
|
+
_preparePromptAndToolLoadout(options, messages = this.agent.state.messages) {
|
|
1486
|
+
options.selectedTools = [...new Set(options.selectedTools)].filter((name) => this._toolRegistry.has(name));
|
|
1487
|
+
this.agent.state.tools = options.selectedTools.flatMap((name) => {
|
|
1488
|
+
const tool = this._toolRegistry.get(name);
|
|
1489
|
+
return tool ? [tool] : [];
|
|
1490
|
+
});
|
|
1491
|
+
const sections = diffSystemPromptSections(getCurrentSystemMessage(messages)?.sections ?? {}, buildSystemPromptSections(options));
|
|
1492
|
+
return sections ? { role: "system", content: "", sections, timestamp: Date.now() } : undefined;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Send a forced prompt as the provider's leading system prompt without recording it.
|
|
1496
|
+
*
|
|
1497
|
+
* A `before_agent_start` handler that returns `systemPrompt` needs that exact text at the
|
|
1498
|
+
* head of the request; a mid-conversation system message would leave the original prompt
|
|
1499
|
+
* in place. The forced text is a rendering of the current prompt, so the transcript keeps
|
|
1500
|
+
* its structured sections and the request is projected instead: the system messages
|
|
1501
|
+
* collapse into one head holding the forced text and the current tools. Runs after the
|
|
1502
|
+
* `context` extension handlers.
|
|
1503
|
+
*/
|
|
1504
|
+
_installAgentForcedPromptProjection() {
|
|
1505
|
+
const previousTransformContext = this.agent.transformContext;
|
|
1506
|
+
this.agent.transformContext = async (messages, signal) => {
|
|
1507
|
+
const transformed = previousTransformContext ? await previousTransformContext(messages, signal) : messages;
|
|
1508
|
+
const forced = this._runSystemPromptOptions?.forceSystemPrompt;
|
|
1509
|
+
if (forced === undefined)
|
|
1510
|
+
return transformed;
|
|
1511
|
+
const current = getCurrentSystemMessage(transformed);
|
|
1512
|
+
const head = {
|
|
1513
|
+
role: "system",
|
|
1514
|
+
content: forced,
|
|
1515
|
+
...(current?.toolsAdded ? { toolsAdded: current.toolsAdded } : {}),
|
|
1516
|
+
timestamp: current?.timestamp ?? Date.now(),
|
|
1517
|
+
};
|
|
1518
|
+
return [head, ...transformed.filter((message) => message.role !== "system")];
|
|
1223
1519
|
};
|
|
1224
|
-
|
|
1520
|
+
}
|
|
1521
|
+
/** Restore the active tool loadout declared by the session transcript, if it declares one. */
|
|
1522
|
+
_restoreToolsFromTranscript() {
|
|
1523
|
+
const current = getCurrentSystemMessage(this.sessionManager.buildSessionContext().messages);
|
|
1524
|
+
if (!current)
|
|
1525
|
+
return;
|
|
1526
|
+
const toolNames = (current.toolsAdded ?? [])
|
|
1527
|
+
.map((tool) => tool.name)
|
|
1528
|
+
.filter((name) => this._toolRegistry.has(name));
|
|
1529
|
+
this.agent.state.tools = toolNames.flatMap((name) => {
|
|
1530
|
+
const registered = this._toolRegistry.get(name);
|
|
1531
|
+
return registered ? [registered] : [];
|
|
1532
|
+
});
|
|
1533
|
+
this._rebuildSystemPrompt(toolNames);
|
|
1225
1534
|
}
|
|
1226
1535
|
// =========================================================================
|
|
1227
1536
|
// Prompting
|
|
1228
1537
|
// =========================================================================
|
|
1229
1538
|
async _runAgentPrompt(messages) {
|
|
1539
|
+
this._agentRunAbortRequested = false;
|
|
1230
1540
|
this._isAgentRunActive = true;
|
|
1231
1541
|
try {
|
|
1232
1542
|
await this.agent.prompt(messages);
|
|
1233
|
-
while (
|
|
1543
|
+
while (!this._agentRunAbortRequested) {
|
|
1544
|
+
if (await this._handlePostAgentRun()) {
|
|
1545
|
+
if (this._agentRunAbortRequested)
|
|
1546
|
+
break;
|
|
1547
|
+
await this.agent.continue();
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (this._agentRunAbortRequested || !(await this._runBeforeSettleBoundary()))
|
|
1551
|
+
break;
|
|
1552
|
+
if (this._agentRunAbortRequested)
|
|
1553
|
+
break;
|
|
1234
1554
|
await this.agent.continue();
|
|
1235
1555
|
}
|
|
1236
1556
|
}
|
|
1237
1557
|
finally {
|
|
1238
|
-
this.
|
|
1558
|
+
if (this._agentRunAbortRequested)
|
|
1559
|
+
this._finishCancelledRetry();
|
|
1560
|
+
this._runSystemPromptOptions = undefined;
|
|
1239
1561
|
this._flushPendingBashMessages();
|
|
1240
1562
|
this._flushPendingCustomMessages();
|
|
1241
1563
|
await this._emitAgentSettled();
|
|
@@ -1246,29 +1568,96 @@ export class AgentSession {
|
|
|
1246
1568
|
this._midRunCompactionNeeded = false;
|
|
1247
1569
|
return await this._runAutoCompaction("threshold", true);
|
|
1248
1570
|
}
|
|
1249
|
-
const
|
|
1571
|
+
const message = this._lastAssistantMessage;
|
|
1572
|
+
const toolResults = this._lastAssistantToolResults;
|
|
1250
1573
|
this._lastAssistantMessage = undefined;
|
|
1251
|
-
|
|
1574
|
+
this._lastAssistantToolResults = [];
|
|
1575
|
+
if (this._agentRunAbortRequested) {
|
|
1576
|
+
this._finishCancelledRetry();
|
|
1252
1577
|
return false;
|
|
1253
1578
|
}
|
|
1254
|
-
if (
|
|
1255
|
-
return
|
|
1579
|
+
if (!message)
|
|
1580
|
+
return this.agent.hasQueuedMessages();
|
|
1581
|
+
if (this._isRetryableError(message) && (await this._prepareRetry(message))) {
|
|
1582
|
+
if (this._agentRunAbortRequested)
|
|
1583
|
+
this._finishCancelledRetry();
|
|
1584
|
+
return !this._agentRunAbortRequested;
|
|
1585
|
+
}
|
|
1586
|
+
if (this._agentRunAbortRequested) {
|
|
1587
|
+
this._finishCancelledRetry();
|
|
1588
|
+
return false;
|
|
1256
1589
|
}
|
|
1257
|
-
if (
|
|
1590
|
+
if (message.stopReason === "error" && this._retryAttempt > 0) {
|
|
1258
1591
|
this._emit({
|
|
1259
1592
|
type: "auto_retry_end",
|
|
1260
1593
|
success: false,
|
|
1261
1594
|
attempt: this._retryAttempt,
|
|
1262
|
-
finalError:
|
|
1595
|
+
finalError: message.errorMessage,
|
|
1263
1596
|
});
|
|
1264
1597
|
this._retryAttempt = 0;
|
|
1265
1598
|
}
|
|
1266
|
-
if (await this._checkCompaction(
|
|
1267
|
-
return
|
|
1599
|
+
if (await this._checkCompaction(message, true, toolResults)) {
|
|
1600
|
+
return !this._agentRunAbortRequested;
|
|
1601
|
+
}
|
|
1602
|
+
// The low-level loop drains both queues before agent_end. Messages queued by
|
|
1603
|
+
// agent_end handlers require a fresh run before pre-settlement handlers fire.
|
|
1604
|
+
return !this._agentRunAbortRequested && this.agent.hasQueuedMessages();
|
|
1605
|
+
}
|
|
1606
|
+
async _runBeforeSettleBoundary() {
|
|
1607
|
+
if (!this._extensionRunner.hasHandlers("agent_before_settle"))
|
|
1608
|
+
return this.agent.hasQueuedMessages();
|
|
1609
|
+
this._isBeforeSettle = true;
|
|
1610
|
+
this._abortDuringBeforeSettle = false;
|
|
1611
|
+
try {
|
|
1612
|
+
const result = await this._extensionRunner.emitBoundary({ type: "agent_before_settle", outcome: this._lastActivityOutcome }, (entries) => this._buildBoundaryContext(entries, "agent_before_settle"));
|
|
1613
|
+
this._commitBoundaryDrafts(result.entries);
|
|
1614
|
+
this._flushPendingCustomMessages();
|
|
1615
|
+
const finalContext = this._buildBoundaryContext([], "agent_before_settle");
|
|
1616
|
+
if (this._abortDuringBeforeSettle)
|
|
1617
|
+
return false;
|
|
1618
|
+
const shouldContinue = result.continue || this.agent.hasQueuedMessages();
|
|
1619
|
+
if (shouldContinue && !finalContext.canContinue) {
|
|
1620
|
+
if (result.continue)
|
|
1621
|
+
this._reportInvalidBoundaryContinuation("agent_before_settle");
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
return shouldContinue;
|
|
1625
|
+
}
|
|
1626
|
+
finally {
|
|
1627
|
+
this._isBeforeSettle = false;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
async _runInputHandlers(text, images, source, streamingBehavior) {
|
|
1631
|
+
if (!this._extensionRunner.hasHandlers("input")) {
|
|
1632
|
+
return { text, images };
|
|
1633
|
+
}
|
|
1634
|
+
const inputResult = await this._extensionRunner.emitInput(text, images, source, streamingBehavior);
|
|
1635
|
+
if (inputResult.action === "handled") {
|
|
1636
|
+
return undefined;
|
|
1637
|
+
}
|
|
1638
|
+
if (inputResult.action === "transform") {
|
|
1639
|
+
return { text: inputResult.text, images: inputResult.images ?? images };
|
|
1640
|
+
}
|
|
1641
|
+
return { text, images };
|
|
1642
|
+
}
|
|
1643
|
+
async _normalizePromptImages(images) {
|
|
1644
|
+
if (!images)
|
|
1645
|
+
return { images: [], hints: [] };
|
|
1646
|
+
const normalizedImages = [];
|
|
1647
|
+
const hints = [];
|
|
1648
|
+
for (const image of images) {
|
|
1649
|
+
const processed = await processImage(Buffer.from(image.data, "base64"), image.mimeType, {
|
|
1650
|
+
autoResizeImages: this.settingsManager.getImageAutoResize(),
|
|
1651
|
+
resizeOptions: this.model?.inputLimits?.images?.resize,
|
|
1652
|
+
});
|
|
1653
|
+
if (!processed.ok) {
|
|
1654
|
+
hints.push(processed.message);
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
normalizedImages.push({ type: "image", data: processed.data, mimeType: processed.mimeType });
|
|
1658
|
+
hints.push(...processed.hints);
|
|
1268
1659
|
}
|
|
1269
|
-
|
|
1270
|
-
// here were queued by agent_end extension handlers and need a continuation.
|
|
1271
|
-
return this.agent.hasQueuedMessages();
|
|
1660
|
+
return { images: normalizedImages, hints };
|
|
1272
1661
|
}
|
|
1273
1662
|
/**
|
|
1274
1663
|
* Send a prompt to the agent.
|
|
@@ -1280,6 +1669,10 @@ export class AgentSession {
|
|
|
1280
1669
|
* @throws Error if no model selected or no API key available (when not streaming)
|
|
1281
1670
|
*/
|
|
1282
1671
|
async prompt(text, options) {
|
|
1672
|
+
if (this._isEmittingAgentSettled) {
|
|
1673
|
+
this._deferredSettledActions.push(async () => await this.prompt(text, options));
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1283
1676
|
if (this._lifecycle === "closed")
|
|
1284
1677
|
throw new Error("Session is closed.");
|
|
1285
1678
|
if (this._activePrompt) {
|
|
@@ -1325,19 +1718,12 @@ export class AgentSession {
|
|
|
1325
1718
|
throw new Error("Cannot submit a prompt while compaction is in progress. Wait for compaction to finish and retry.");
|
|
1326
1719
|
}
|
|
1327
1720
|
// Emit input event for extension interception (before skill/template expansion)
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
if (inputResult.action === "handled") {
|
|
1333
|
-
preflightResult?.(true);
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
1336
|
-
if (inputResult.action === "transform") {
|
|
1337
|
-
currentText = inputResult.text;
|
|
1338
|
-
currentImages = inputResult.images ?? currentImages;
|
|
1339
|
-
}
|
|
1721
|
+
const processedInput = await this._runInputHandlers(text, options?.images, options?.source ?? "interactive", this.isStreaming ? options?.streamingBehavior : undefined);
|
|
1722
|
+
if (!processedInput) {
|
|
1723
|
+
preflightResult?.(true);
|
|
1724
|
+
return;
|
|
1340
1725
|
}
|
|
1726
|
+
const { text: currentText, images: currentImages } = processedInput;
|
|
1341
1727
|
// Expand skill commands (/skill:name args) and prompt templates (/template args)
|
|
1342
1728
|
let expandedText = currentText;
|
|
1343
1729
|
if (expandPromptTemplates) {
|
|
@@ -1382,13 +1768,23 @@ export class AgentSession {
|
|
|
1382
1768
|
if (lastAssistant) {
|
|
1383
1769
|
await this._checkCompaction(lastAssistant, false);
|
|
1384
1770
|
}
|
|
1385
|
-
//
|
|
1771
|
+
// Emit before_agent_start before normalizing images so extension-driven model
|
|
1772
|
+
// selection determines the resize profile used for the request and history.
|
|
1773
|
+
const selectedToolsBefore = this._baseSystemPromptOptions.selectedTools;
|
|
1774
|
+
const result = await this._extensionRunner.emitBeforeAgentStart(expandedText, currentImages, this._baseSystemPromptOptions);
|
|
1775
|
+
// Handlers may edit event.systemPromptOptions.selectedTools or call setActiveTools(),
|
|
1776
|
+
// which updates the live loadout instead. An explicit edit wins; otherwise the live
|
|
1777
|
+
// loadout is authoritative, so a setActiveTools() call is not undone here.
|
|
1778
|
+
const handlerEditedTools = result.systemPromptOptions.selectedTools.length !== selectedToolsBefore.length ||
|
|
1779
|
+
result.systemPromptOptions.selectedTools.some((name, index) => name !== selectedToolsBefore[index]);
|
|
1780
|
+
if (!handlerEditedTools)
|
|
1781
|
+
result.systemPromptOptions.selectedTools = this.getActiveToolNames();
|
|
1782
|
+
const normalized = await this._normalizePromptImages(currentImages);
|
|
1783
|
+
const userText = normalized.hints.length > 0 ? `${expandedText}\n\n${normalized.hints.join("\n")}` : expandedText;
|
|
1784
|
+
// Build messages only after hooks and image normalization have completed.
|
|
1386
1785
|
messages = [];
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
if (currentImages) {
|
|
1390
|
-
userContent.push(...currentImages);
|
|
1391
|
-
}
|
|
1786
|
+
const userContent = [{ type: "text", text: userText }];
|
|
1787
|
+
userContent.push(...normalized.images);
|
|
1392
1788
|
messages.push({
|
|
1393
1789
|
role: "user",
|
|
1394
1790
|
content: userContent,
|
|
@@ -1399,32 +1795,21 @@ export class AgentSession {
|
|
|
1399
1795
|
messages.push(msg);
|
|
1400
1796
|
}
|
|
1401
1797
|
this._pendingNextTurnMessages = [];
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
display: msg.display,
|
|
1413
|
-
details: msg.details,
|
|
1414
|
-
timestamp: Date.now(),
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
// Apply extension-modified system prompt, or reset to base
|
|
1419
|
-
if (result?.systemPrompt !== undefined) {
|
|
1420
|
-
this._systemPromptOverride = result.systemPrompt;
|
|
1421
|
-
this.agent.state.systemPrompt = result.systemPrompt;
|
|
1422
|
-
}
|
|
1423
|
-
else {
|
|
1424
|
-
// Ensure we're using the base prompt (in case previous turn had modifications)
|
|
1425
|
-
this._systemPromptOverride = undefined;
|
|
1426
|
-
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
1798
|
+
for (const msg of result.messages) {
|
|
1799
|
+
messages.push({
|
|
1800
|
+
role: "custom",
|
|
1801
|
+
customType: msg.customType,
|
|
1802
|
+
// Untyped extensions can pass null/missing content; normalize at ingestion.
|
|
1803
|
+
content: msg.content ?? [],
|
|
1804
|
+
display: msg.display,
|
|
1805
|
+
details: msg.details,
|
|
1806
|
+
timestamp: Date.now(),
|
|
1807
|
+
});
|
|
1427
1808
|
}
|
|
1809
|
+
const updateMessage = this._preparePromptAndToolLoadout(result.systemPromptOptions);
|
|
1810
|
+
this._runSystemPromptOptions = result.systemPromptOptions;
|
|
1811
|
+
if (updateMessage)
|
|
1812
|
+
messages.unshift(updateMessage);
|
|
1428
1813
|
}
|
|
1429
1814
|
catch (error) {
|
|
1430
1815
|
preflightResult?.(false);
|
|
@@ -1504,40 +1889,44 @@ export class AgentSession {
|
|
|
1504
1889
|
return text; // Return original on error
|
|
1505
1890
|
}
|
|
1506
1891
|
}
|
|
1892
|
+
async _queueUserInput(text, images, behavior, source) {
|
|
1893
|
+
if (text.startsWith("/")) {
|
|
1894
|
+
this._throwIfExtensionCommand(text);
|
|
1895
|
+
}
|
|
1896
|
+
const processedInput = await this._runInputHandlers(text, images, source, this.isStreaming ? behavior : undefined);
|
|
1897
|
+
if (!processedInput)
|
|
1898
|
+
return;
|
|
1899
|
+
let expandedText = this._expandSkillCommand(processedInput.text);
|
|
1900
|
+
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
|
|
1901
|
+
if (behavior === "steer") {
|
|
1902
|
+
await this._queueSteer(expandedText, processedInput.images);
|
|
1903
|
+
}
|
|
1904
|
+
else {
|
|
1905
|
+
await this._queueFollowUp(expandedText, processedInput.images);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1507
1908
|
/**
|
|
1508
1909
|
* Queue a steering message while the agent is running.
|
|
1509
1910
|
* Delivered after the current assistant turn finishes executing its tool calls,
|
|
1510
1911
|
* before the next LLM call.
|
|
1511
1912
|
* Expands skill commands and prompt templates. Errors on extension commands.
|
|
1512
1913
|
* @param images Optional image attachments to include with the message
|
|
1914
|
+
* @param options Input source; defaults to interactive
|
|
1513
1915
|
* @throws Error if text is an extension command
|
|
1514
1916
|
*/
|
|
1515
|
-
async steer(text, images) {
|
|
1516
|
-
|
|
1517
|
-
if (text.startsWith("/")) {
|
|
1518
|
-
this._throwIfExtensionCommand(text);
|
|
1519
|
-
}
|
|
1520
|
-
// Expand skill commands and prompt templates
|
|
1521
|
-
let expandedText = this._expandSkillCommand(text);
|
|
1522
|
-
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
|
|
1523
|
-
await this._queueSteer(expandedText, images);
|
|
1917
|
+
async steer(text, images, options) {
|
|
1918
|
+
await this._queueUserInput(text, images, "steer", options?.source ?? "interactive");
|
|
1524
1919
|
}
|
|
1525
1920
|
/**
|
|
1526
1921
|
* Queue a follow-up message to be processed after the agent finishes.
|
|
1527
1922
|
* Delivered only when agent has no more tool calls or steering messages.
|
|
1528
1923
|
* Expands skill commands and prompt templates. Errors on extension commands.
|
|
1529
1924
|
* @param images Optional image attachments to include with the message
|
|
1925
|
+
* @param options Input source; defaults to interactive
|
|
1530
1926
|
* @throws Error if text is an extension command
|
|
1531
1927
|
*/
|
|
1532
|
-
async followUp(text, images) {
|
|
1533
|
-
|
|
1534
|
-
if (text.startsWith("/")) {
|
|
1535
|
-
this._throwIfExtensionCommand(text);
|
|
1536
|
-
}
|
|
1537
|
-
// Expand skill commands and prompt templates
|
|
1538
|
-
let expandedText = this._expandSkillCommand(text);
|
|
1539
|
-
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
|
|
1540
|
-
await this._queueFollowUp(expandedText, images);
|
|
1928
|
+
async followUp(text, images, options) {
|
|
1929
|
+
await this._queueUserInput(text, images, "followUp", options?.source ?? "interactive");
|
|
1541
1930
|
}
|
|
1542
1931
|
/**
|
|
1543
1932
|
* Internal: Queue a steering message (already expanded, no extension command check).
|
|
@@ -1565,11 +1954,7 @@ export class AgentSession {
|
|
|
1565
1954
|
if (images) {
|
|
1566
1955
|
content.push(...images);
|
|
1567
1956
|
}
|
|
1568
|
-
this.agent.followUp({
|
|
1569
|
-
role: "user",
|
|
1570
|
-
content,
|
|
1571
|
-
timestamp: Date.now(),
|
|
1572
|
-
});
|
|
1957
|
+
this.agent.followUp({ role: "user", content, timestamp: Date.now() });
|
|
1573
1958
|
}
|
|
1574
1959
|
/**
|
|
1575
1960
|
* Throw an error if the text is an extension command.
|
|
@@ -1617,6 +2002,10 @@ export class AgentSession {
|
|
|
1617
2002
|
}
|
|
1618
2003
|
}
|
|
1619
2004
|
else if (options?.triggerTurn) {
|
|
2005
|
+
if (this._isEmittingAgentSettled) {
|
|
2006
|
+
this._deferredSettledActions.push(async () => await this._runAgentPrompt(appMessage));
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
1620
2009
|
await this._runAgentPrompt(appMessage);
|
|
1621
2010
|
}
|
|
1622
2011
|
else if (this.isStreaming) {
|
|
@@ -1631,8 +2020,8 @@ export class AgentSession {
|
|
|
1631
2020
|
}
|
|
1632
2021
|
}
|
|
1633
2022
|
_appendCustomMessage(appMessage) {
|
|
1634
|
-
this.agent.state.messages.push(appMessage);
|
|
1635
2023
|
this.sessionManager.appendCustomMessageEntry(appMessage.customType, appMessage.content, appMessage.display, appMessage.details);
|
|
2024
|
+
this._refreshFinalizedContext();
|
|
1636
2025
|
this._emit({ type: "message_start", message: appMessage });
|
|
1637
2026
|
this._emit({ type: "message_end", message: appMessage });
|
|
1638
2027
|
}
|
|
@@ -1721,7 +2110,14 @@ export class AgentSession {
|
|
|
1721
2110
|
async abort() {
|
|
1722
2111
|
if (this._activePrompt && this._lifecycle !== "closed")
|
|
1723
2112
|
this._lifecycle = "interrupted";
|
|
2113
|
+
if (this._isAgentRunActive) {
|
|
2114
|
+
this._agentRunAbortRequested = true;
|
|
2115
|
+
}
|
|
1724
2116
|
this.abortRetry();
|
|
2117
|
+
this.abortCompaction();
|
|
2118
|
+
this.abortBranchSummary();
|
|
2119
|
+
if (this._isBeforeSettle)
|
|
2120
|
+
this._abortDuringBeforeSettle = true;
|
|
1725
2121
|
this.agent.abort();
|
|
1726
2122
|
await this.waitForIdle();
|
|
1727
2123
|
}
|
|
@@ -2195,6 +2591,10 @@ export class AgentSession {
|
|
|
2195
2591
|
this.agent.recordMaintenanceRequest();
|
|
2196
2592
|
return compact(preparation, requestModel, apiKey, headers, customInstructions, signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }), undefined);
|
|
2197
2593
|
}
|
|
2594
|
+
_clearManualCompactionState() {
|
|
2595
|
+
this._compactionAbortController = undefined;
|
|
2596
|
+
this._resolveIdleWaitIfIdle();
|
|
2597
|
+
}
|
|
2198
2598
|
/**
|
|
2199
2599
|
* Manually compact the session context.
|
|
2200
2600
|
*
|
|
@@ -2215,13 +2615,15 @@ export class AgentSession {
|
|
|
2215
2615
|
this._compactionAbortController = new AbortController();
|
|
2216
2616
|
this._emit({ type: "compaction_start", reason: "manual" });
|
|
2217
2617
|
let fromExtension = false;
|
|
2618
|
+
let cancelledByExtension = false;
|
|
2218
2619
|
try {
|
|
2219
|
-
|
|
2620
|
+
const model = this.model;
|
|
2621
|
+
if (!model) {
|
|
2220
2622
|
throw new Error(formatNoModelSelectedMessage());
|
|
2221
2623
|
}
|
|
2222
|
-
const
|
|
2624
|
+
const settings = this.settingsManager.getCompactionSettings(model);
|
|
2625
|
+
const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, this._compactionAbortController.signal);
|
|
2223
2626
|
const pathEntries = this.sessionManager.getBranch();
|
|
2224
|
-
const settings = this.settingsManager.getCompactionSettings();
|
|
2225
2627
|
const preparation = prepareCompaction(pathEntries, settings);
|
|
2226
2628
|
if (!preparation) {
|
|
2227
2629
|
// Check why we can't compact
|
|
@@ -2249,6 +2651,7 @@ export class AgentSession {
|
|
|
2249
2651
|
signal: this._compactionAbortController.signal,
|
|
2250
2652
|
}));
|
|
2251
2653
|
if (result?.cancel) {
|
|
2654
|
+
cancelledByExtension = true;
|
|
2252
2655
|
throw new Error("Compaction cancelled");
|
|
2253
2656
|
}
|
|
2254
2657
|
if (result?.compaction) {
|
|
@@ -2306,9 +2709,8 @@ export class AgentSession {
|
|
|
2306
2709
|
}
|
|
2307
2710
|
}
|
|
2308
2711
|
const newEntries = this.sessionManager.getEntries();
|
|
2309
|
-
|
|
2310
|
-
this.
|
|
2311
|
-
const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
|
|
2712
|
+
this._refreshFinalizedContext();
|
|
2713
|
+
const estimatedTokensAfter = estimateMessagesTokens(this.sessionManager.buildSessionProjection().messages);
|
|
2312
2714
|
// Get the saved compaction entry for the extension event
|
|
2313
2715
|
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
2314
2716
|
if (this._extensionRunner && savedCompactionEntry) {
|
|
@@ -2329,7 +2731,7 @@ export class AgentSession {
|
|
|
2329
2731
|
details,
|
|
2330
2732
|
};
|
|
2331
2733
|
// compaction_end listeners may submit queued prompts, so expose idle state before notifying them.
|
|
2332
|
-
this.
|
|
2734
|
+
this._clearManualCompactionState();
|
|
2333
2735
|
this._emit({
|
|
2334
2736
|
type: "compaction_end",
|
|
2335
2737
|
reason: "manual",
|
|
@@ -2341,9 +2743,9 @@ export class AgentSession {
|
|
|
2341
2743
|
}
|
|
2342
2744
|
catch (error) {
|
|
2343
2745
|
const message = error instanceof Error ? error.message : String(error);
|
|
2344
|
-
const aborted =
|
|
2746
|
+
const aborted = this._compactionAbortController.signal.aborted || cancelledByExtension;
|
|
2345
2747
|
const errorMessage = aborted ? undefined : `Compaction failed: ${message}`;
|
|
2346
|
-
this.
|
|
2748
|
+
this._clearManualCompactionState();
|
|
2347
2749
|
this._emit({
|
|
2348
2750
|
type: "compaction_end",
|
|
2349
2751
|
reason: "manual",
|
|
@@ -2362,7 +2764,7 @@ export class AgentSession {
|
|
|
2362
2764
|
throw error;
|
|
2363
2765
|
}
|
|
2364
2766
|
finally {
|
|
2365
|
-
this.
|
|
2767
|
+
this._clearManualCompactionState();
|
|
2366
2768
|
}
|
|
2367
2769
|
}
|
|
2368
2770
|
/**
|
|
@@ -2399,8 +2801,8 @@ export class AgentSession {
|
|
|
2399
2801
|
* @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
|
|
2400
2802
|
* @returns Whether the post-run loop should call `agent.continue()` for overflow recovery or queued messages
|
|
2401
2803
|
*/
|
|
2402
|
-
async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
|
|
2403
|
-
const settings = this.settingsManager.getCompactionSettings();
|
|
2804
|
+
async _checkCompaction(assistantMessage, skipAbortedCheck = true, toolResults = []) {
|
|
2805
|
+
const settings = this.settingsManager.getCompactionSettings(this.model);
|
|
2404
2806
|
if (!settings.enabled)
|
|
2405
2807
|
return false;
|
|
2406
2808
|
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
@@ -2423,8 +2825,27 @@ export class AgentSession {
|
|
|
2423
2825
|
// Automatic cases 1 and 2: context overflow.
|
|
2424
2826
|
// A length stop is recoverable when output ended below the model's original desired limit,
|
|
2425
2827
|
// independent of the configured context size or any context-clamped provider request limit.
|
|
2426
|
-
const
|
|
2427
|
-
const
|
|
2828
|
+
const currentProjection = this.sessionManager.buildSessionProjection();
|
|
2829
|
+
const assistantEntryId = this._findPersistedMessageEntryId(assistantMessage);
|
|
2830
|
+
const assistantIsProjected = assistantEntryId === undefined ||
|
|
2831
|
+
currentProjection.entries.some((entry) => entry.sourceEntry.id === assistantEntryId &&
|
|
2832
|
+
entry.messages.some((message) => message.role === "assistant"));
|
|
2833
|
+
const branch = this.sessionManager.getBranch();
|
|
2834
|
+
const assistantIndex = assistantEntryId ? branch.findIndex((entry) => entry.id === assistantEntryId) : -1;
|
|
2835
|
+
const entriesAfterAssistant = assistantIndex >= 0 ? branch.slice(assistantIndex + 1) : [];
|
|
2836
|
+
const hasPostAssistantContextEdit = entriesAfterAssistant.some((entry) => entry.type === "context_edit");
|
|
2837
|
+
const latestAssistantEdit = entriesAfterAssistant
|
|
2838
|
+
.filter((entry) => entry.type === "context_edit" && entry.targetId === assistantEntryId)
|
|
2839
|
+
.at(-1);
|
|
2840
|
+
const assistantRetainedForExplicitRecovery = assistantEntryId === undefined ||
|
|
2841
|
+
(!entriesAfterAssistant.some((entry) => entry.type === "compaction") &&
|
|
2842
|
+
latestAssistantEdit?.replacement !== null);
|
|
2843
|
+
const assistantUsageMatchesProjection = assistantIsProjected && !hasPostAssistantContextEdit;
|
|
2844
|
+
const explicitOverflow = assistantMessage.stopReason === "error" && isContextOverflow(assistantMessage);
|
|
2845
|
+
const contextOverflow = sameModel &&
|
|
2846
|
+
((explicitOverflow && assistantRetainedForExplicitRecovery) ||
|
|
2847
|
+
(assistantUsageMatchesProjection && isContextOverflow(assistantMessage, contextWindow)));
|
|
2848
|
+
const recoverableLength = sameModel && assistantIsProjected && isRecoverableLength(assistantMessage, this.model?.maxTokens ?? 0);
|
|
2428
2849
|
if (contextOverflow || recoverableLength) {
|
|
2429
2850
|
const willRetry = assistantMessage.stopReason !== "stop";
|
|
2430
2851
|
// Case 2: the response completed successfully. Compact, but do not retry because
|
|
@@ -2453,13 +2874,9 @@ export class AgentSession {
|
|
|
2453
2874
|
});
|
|
2454
2875
|
return false;
|
|
2455
2876
|
}
|
|
2456
|
-
//
|
|
2457
|
-
// retry once. The message remains in session history but is excluded from retry context.
|
|
2877
|
+
// Persistently omit the selected final attempt before post-run recovery compaction.
|
|
2458
2878
|
this._overflowRecoveryAttempted = true;
|
|
2459
|
-
|
|
2460
|
-
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
2461
|
-
this.agent.state.messages = messages.slice(0, -1);
|
|
2462
|
-
}
|
|
2879
|
+
this._omitRecoveryAttempt(assistantMessage, toolResults);
|
|
2463
2880
|
return await this._runAutoCompaction("overflow", willRetry);
|
|
2464
2881
|
}
|
|
2465
2882
|
// Case 3: threshold compaction without retry.
|
|
@@ -2467,8 +2884,13 @@ export class AgentSession {
|
|
|
2467
2884
|
// This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage
|
|
2468
2885
|
// responses can still compact and do not reset context accounting.
|
|
2469
2886
|
let contextTokens;
|
|
2887
|
+
const projection = currentProjection;
|
|
2888
|
+
const hasContextEdits = projection.entries.some((entry) => entry.sourceEntry.type === "context_edit");
|
|
2470
2889
|
const directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;
|
|
2471
|
-
if (
|
|
2890
|
+
if (hasContextEdits) {
|
|
2891
|
+
contextTokens = estimateProjectedContextTokens(projection, branch).tokens;
|
|
2892
|
+
}
|
|
2893
|
+
else if (assistantMessage.stopReason === "error" || directContextTokens === 0) {
|
|
2472
2894
|
const messages = this.agent.state.messages;
|
|
2473
2895
|
const estimate = estimateContextTokens(messages);
|
|
2474
2896
|
// Without provider usage, estimate.tokens is the pure message-size estimate.
|
|
@@ -2505,22 +2927,28 @@ export class AgentSession {
|
|
|
2505
2927
|
* @returns Whether the post-run loop should call `agent.continue()`
|
|
2506
2928
|
*/
|
|
2507
2929
|
async _runAutoCompaction(reason, willRetry) {
|
|
2508
|
-
const
|
|
2930
|
+
const model = this.model;
|
|
2931
|
+
const settings = this.settingsManager.getCompactionSettings(model);
|
|
2932
|
+
let abortController;
|
|
2509
2933
|
let started = false;
|
|
2510
2934
|
let fromExtension = false;
|
|
2935
|
+
let cancelledByExtension = false;
|
|
2511
2936
|
try {
|
|
2512
|
-
if (!
|
|
2937
|
+
if (!model) {
|
|
2513
2938
|
return false;
|
|
2514
2939
|
}
|
|
2515
|
-
const { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model);
|
|
2516
2940
|
const pathEntries = this.sessionManager.getBranch();
|
|
2517
2941
|
const preparation = prepareCompaction(pathEntries, settings);
|
|
2518
2942
|
if (!preparation) {
|
|
2519
2943
|
return false;
|
|
2520
2944
|
}
|
|
2521
|
-
|
|
2522
|
-
this._autoCompactionAbortController =
|
|
2945
|
+
abortController = new AbortController();
|
|
2946
|
+
this._autoCompactionAbortController = abortController;
|
|
2523
2947
|
started = true;
|
|
2948
|
+
this._emit({ type: "compaction_start", reason });
|
|
2949
|
+
abortController.signal.throwIfAborted();
|
|
2950
|
+
const { model: requestModel, apiKey, headers, env, } = await this._getSummarizationRequestAuth(model, abortController.signal);
|
|
2951
|
+
abortController.signal.throwIfAborted();
|
|
2524
2952
|
let extensionCompaction;
|
|
2525
2953
|
if (this._hookRuntime?.hasHandlers("session_before_compact")) {
|
|
2526
2954
|
await this._hookRuntime.emitObserve("session_before_compact", { type: "session_before_compact", reason });
|
|
@@ -2533,29 +2961,18 @@ export class AgentSession {
|
|
|
2533
2961
|
customInstructions: undefined,
|
|
2534
2962
|
reason,
|
|
2535
2963
|
willRetry,
|
|
2536
|
-
signal:
|
|
2964
|
+
signal: abortController.signal,
|
|
2537
2965
|
}));
|
|
2538
2966
|
if (extensionResult?.cancel) {
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
reason,
|
|
2542
|
-
result: undefined,
|
|
2543
|
-
aborted: true,
|
|
2544
|
-
willRetry: false,
|
|
2545
|
-
});
|
|
2546
|
-
await this._emitSessionCompactFailed({
|
|
2547
|
-
reason,
|
|
2548
|
-
aborted: true,
|
|
2549
|
-
willRetry: false,
|
|
2550
|
-
fromExtension: false,
|
|
2551
|
-
});
|
|
2552
|
-
return false;
|
|
2967
|
+
cancelledByExtension = true;
|
|
2968
|
+
throw new Error("Compaction cancelled");
|
|
2553
2969
|
}
|
|
2554
2970
|
if (extensionResult?.compaction) {
|
|
2555
2971
|
extensionCompaction = extensionResult.compaction;
|
|
2556
2972
|
fromExtension = true;
|
|
2557
2973
|
}
|
|
2558
2974
|
}
|
|
2975
|
+
abortController.signal.throwIfAborted();
|
|
2559
2976
|
let summary;
|
|
2560
2977
|
let firstKeptEntryId;
|
|
2561
2978
|
let tokensBefore;
|
|
@@ -2577,7 +2994,7 @@ export class AgentSession {
|
|
|
2577
2994
|
}
|
|
2578
2995
|
else {
|
|
2579
2996
|
// Shared default summary generator, also used by manual compaction.
|
|
2580
|
-
const compactResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, undefined,
|
|
2997
|
+
const compactResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, undefined, abortController.signal, env, reason);
|
|
2581
2998
|
summary = compactResult.summary;
|
|
2582
2999
|
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
2583
3000
|
tokensBefore = compactResult.tokensBefore;
|
|
@@ -2587,22 +3004,7 @@ export class AgentSession {
|
|
|
2587
3004
|
summary += `\n\n${formatWorkspaceProjection(workspaceRecord)}`;
|
|
2588
3005
|
}
|
|
2589
3006
|
}
|
|
2590
|
-
|
|
2591
|
-
this._emit({
|
|
2592
|
-
type: "compaction_end",
|
|
2593
|
-
reason,
|
|
2594
|
-
result: undefined,
|
|
2595
|
-
aborted: true,
|
|
2596
|
-
willRetry: false,
|
|
2597
|
-
});
|
|
2598
|
-
await this._emitSessionCompactFailed({
|
|
2599
|
-
reason,
|
|
2600
|
-
aborted: true,
|
|
2601
|
-
willRetry: false,
|
|
2602
|
-
fromExtension,
|
|
2603
|
-
});
|
|
2604
|
-
return false;
|
|
2605
|
-
}
|
|
3007
|
+
abortController.signal.throwIfAborted();
|
|
2606
3008
|
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
|
|
2607
3009
|
if (workspaceRecord) {
|
|
2608
3010
|
try {
|
|
@@ -2619,9 +3021,8 @@ export class AgentSession {
|
|
|
2619
3021
|
}
|
|
2620
3022
|
}
|
|
2621
3023
|
const newEntries = this.sessionManager.getEntries();
|
|
2622
|
-
|
|
2623
|
-
this.
|
|
2624
|
-
const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
|
|
3024
|
+
this._refreshFinalizedContext();
|
|
3025
|
+
const estimatedTokensAfter = estimateMessagesTokens(this.sessionManager.buildSessionProjection().messages);
|
|
2625
3026
|
// Get the saved compaction entry for the extension event
|
|
2626
3027
|
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
2627
3028
|
if (this._extensionRunner && savedCompactionEntry) {
|
|
@@ -2642,40 +3043,33 @@ export class AgentSession {
|
|
|
2642
3043
|
details,
|
|
2643
3044
|
};
|
|
2644
3045
|
this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
|
|
2645
|
-
if (willRetry)
|
|
2646
|
-
const messages = this.agent.state.messages;
|
|
2647
|
-
const lastMsg = messages[messages.length - 1];
|
|
2648
|
-
// The overflow response was persisted on message_end before _checkCompaction() removed it
|
|
2649
|
-
// from agent state. Rebuilding state from the new compaction can restore that kept entry,
|
|
2650
|
-
// leaving an assistant as the final message. agent.continue() rejects that state, so remove
|
|
2651
|
-
// the retriable error or truncated-length response again before continuing the interrupted turn.
|
|
2652
|
-
if (lastMsg?.role === "assistant" && (lastMsg.stopReason === "error" || lastMsg.stopReason === "length")) {
|
|
2653
|
-
this.agent.state.messages = messages.slice(0, -1);
|
|
2654
|
-
}
|
|
3046
|
+
if (willRetry)
|
|
2655
3047
|
return true;
|
|
2656
|
-
}
|
|
2657
3048
|
// Auto-compaction can complete while follow-up/steering/custom messages are waiting.
|
|
2658
3049
|
// Continue once so queued messages are delivered.
|
|
2659
3050
|
return this.agent.hasQueuedMessages();
|
|
2660
3051
|
}
|
|
2661
3052
|
catch (error) {
|
|
2662
|
-
const
|
|
3053
|
+
const message = error instanceof Error ? error.message : "compaction failed";
|
|
3054
|
+
const aborted = abortController?.signal.aborted === true || cancelledByExtension;
|
|
2663
3055
|
if (started) {
|
|
2664
|
-
const
|
|
2665
|
-
?
|
|
2666
|
-
:
|
|
3056
|
+
const errorMessage = aborted
|
|
3057
|
+
? undefined
|
|
3058
|
+
: reason === "overflow"
|
|
3059
|
+
? `Context overflow recovery failed: ${message}`
|
|
3060
|
+
: `Auto-compaction failed: ${message}`;
|
|
2667
3061
|
this._emit({
|
|
2668
3062
|
type: "compaction_end",
|
|
2669
3063
|
reason,
|
|
2670
3064
|
result: undefined,
|
|
2671
|
-
aborted
|
|
3065
|
+
aborted,
|
|
2672
3066
|
willRetry: false,
|
|
2673
|
-
errorMessage
|
|
3067
|
+
errorMessage,
|
|
2674
3068
|
});
|
|
2675
3069
|
await this._emitSessionCompactFailed({
|
|
2676
3070
|
reason,
|
|
2677
|
-
errorMessage
|
|
2678
|
-
aborted
|
|
3071
|
+
errorMessage,
|
|
3072
|
+
aborted,
|
|
2679
3073
|
willRetry: false,
|
|
2680
3074
|
fromExtension,
|
|
2681
3075
|
});
|
|
@@ -2683,7 +3077,10 @@ export class AgentSession {
|
|
|
2683
3077
|
return false;
|
|
2684
3078
|
}
|
|
2685
3079
|
finally {
|
|
2686
|
-
this._autoCompactionAbortController
|
|
3080
|
+
if (this._autoCompactionAbortController === abortController) {
|
|
3081
|
+
this._autoCompactionAbortController = undefined;
|
|
3082
|
+
}
|
|
3083
|
+
this._resolveIdleWaitIfIdle();
|
|
2687
3084
|
}
|
|
2688
3085
|
}
|
|
2689
3086
|
/**
|
|
@@ -2733,8 +3130,7 @@ export class AgentSession {
|
|
|
2733
3130
|
themePaths: this.buildExtensionResourcePaths(themePaths),
|
|
2734
3131
|
};
|
|
2735
3132
|
this._resourceLoader.extendResources(extensionPaths);
|
|
2736
|
-
this.
|
|
2737
|
-
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
3133
|
+
this._rebuildSystemPrompt(this.getActiveToolNames());
|
|
2738
3134
|
}
|
|
2739
3135
|
buildExtensionResourcePaths(entries) {
|
|
2740
3136
|
return entries.map((entry) => {
|
|
@@ -3113,6 +3509,18 @@ export class AgentSession {
|
|
|
3113
3509
|
},
|
|
3114
3510
|
};
|
|
3115
3511
|
}
|
|
3512
|
+
_finishCancelledRetry() {
|
|
3513
|
+
if (this._retryAttempt === 0)
|
|
3514
|
+
return;
|
|
3515
|
+
const attempt = this._retryAttempt;
|
|
3516
|
+
this._retryAttempt = 0;
|
|
3517
|
+
this._emit({
|
|
3518
|
+
type: "auto_retry_end",
|
|
3519
|
+
success: false,
|
|
3520
|
+
attempt,
|
|
3521
|
+
finalError: "Retry cancelled",
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
3116
3524
|
/**
|
|
3117
3525
|
* Prepare a retryable error for continuation with exponential backoff.
|
|
3118
3526
|
* @returns true if the caller should continue the agent, false otherwise
|
|
@@ -3128,7 +3536,7 @@ export class AgentSession {
|
|
|
3128
3536
|
this._retryAttempt--;
|
|
3129
3537
|
return false;
|
|
3130
3538
|
}
|
|
3131
|
-
const delayMs = settings
|
|
3539
|
+
const delayMs = retryDelayMs(settings, this._retryAttempt);
|
|
3132
3540
|
this._emit({
|
|
3133
3541
|
type: "auto_retry_start",
|
|
3134
3542
|
attempt: this._retryAttempt,
|
|
@@ -3136,11 +3544,8 @@ export class AgentSession {
|
|
|
3136
3544
|
delayMs,
|
|
3137
3545
|
errorMessage: message.errorMessage || "Unknown error",
|
|
3138
3546
|
});
|
|
3139
|
-
//
|
|
3140
|
-
|
|
3141
|
-
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
3142
|
-
this.agent.state.messages = messages.slice(0, -1);
|
|
3143
|
-
}
|
|
3547
|
+
// Keep the failed attempt in raw history while durably omitting it from model projection.
|
|
3548
|
+
this._omitRecoveryAttempt(message);
|
|
3144
3549
|
// Wait with exponential backoff (abortable)
|
|
3145
3550
|
this._retryAbortController = new AbortController();
|
|
3146
3551
|
try {
|
|
@@ -3148,14 +3553,7 @@ export class AgentSession {
|
|
|
3148
3553
|
}
|
|
3149
3554
|
catch {
|
|
3150
3555
|
// Aborted during sleep - emit end event so UI can clean up
|
|
3151
|
-
|
|
3152
|
-
this._retryAttempt = 0;
|
|
3153
|
-
this._emit({
|
|
3154
|
-
type: "auto_retry_end",
|
|
3155
|
-
success: false,
|
|
3156
|
-
attempt,
|
|
3157
|
-
finalError: "Retry cancelled",
|
|
3158
|
-
});
|
|
3556
|
+
this._finishCancelledRetry();
|
|
3159
3557
|
return false;
|
|
3160
3558
|
}
|
|
3161
3559
|
finally {
|
|
@@ -3239,10 +3637,8 @@ export class AgentSession {
|
|
|
3239
3637
|
this._pendingBashMessages.push(bashMessage);
|
|
3240
3638
|
}
|
|
3241
3639
|
else {
|
|
3242
|
-
// Add to agent state immediately
|
|
3243
|
-
this.agent.state.messages.push(bashMessage);
|
|
3244
|
-
// Save to session
|
|
3245
3640
|
this.sessionManager.appendMessage(bashMessage);
|
|
3641
|
+
this._refreshFinalizedContext();
|
|
3246
3642
|
}
|
|
3247
3643
|
}
|
|
3248
3644
|
/**
|
|
@@ -3269,12 +3665,10 @@ export class AgentSession {
|
|
|
3269
3665
|
if (this._pendingBashMessages.length === 0)
|
|
3270
3666
|
return;
|
|
3271
3667
|
for (const bashMessage of this._pendingBashMessages) {
|
|
3272
|
-
// Add to agent state
|
|
3273
|
-
this.agent.state.messages.push(bashMessage);
|
|
3274
|
-
// Save to session
|
|
3275
3668
|
this.sessionManager.appendMessage(bashMessage);
|
|
3276
3669
|
}
|
|
3277
3670
|
this._pendingBashMessages = [];
|
|
3671
|
+
this._refreshFinalizedContext();
|
|
3278
3672
|
}
|
|
3279
3673
|
// =========================================================================
|
|
3280
3674
|
// Session Management
|
|
@@ -3306,6 +3700,9 @@ export class AgentSession {
|
|
|
3306
3700
|
if (this.isStreaming) {
|
|
3307
3701
|
throw new Error("Wait for the current response to finish before navigating the session tree.");
|
|
3308
3702
|
}
|
|
3703
|
+
if (this.isCompacting) {
|
|
3704
|
+
throw new Error("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
|
|
3705
|
+
}
|
|
3309
3706
|
const oldLeafId = this.sessionManager.getLeafId();
|
|
3310
3707
|
// No-op if already at target
|
|
3311
3708
|
if (targetId === oldLeafId) {
|
|
@@ -3452,9 +3849,9 @@ export class AgentSession {
|
|
|
3452
3849
|
if (label && !summaryText) {
|
|
3453
3850
|
this.sessionManager.appendLabelChange(targetId, label);
|
|
3454
3851
|
}
|
|
3455
|
-
// Update
|
|
3456
|
-
|
|
3457
|
-
this.
|
|
3852
|
+
// Update finalized context from the canonical session projection.
|
|
3853
|
+
this._refreshFinalizedContext();
|
|
3854
|
+
this._restoreToolsFromTranscript();
|
|
3458
3855
|
// Emit session_tree event
|
|
3459
3856
|
await this._extensionRunner.emit({
|
|
3460
3857
|
type: "session_tree",
|
|
@@ -3468,6 +3865,7 @@ export class AgentSession {
|
|
|
3468
3865
|
}
|
|
3469
3866
|
finally {
|
|
3470
3867
|
this._branchSummaryAbortController = undefined;
|
|
3868
|
+
this._resolveIdleWaitIfIdle();
|
|
3471
3869
|
}
|
|
3472
3870
|
}
|
|
3473
3871
|
/**
|
|
@@ -3566,7 +3964,10 @@ export class AgentSession {
|
|
|
3566
3964
|
let toolCalls = 0;
|
|
3567
3965
|
const usageTotals = createUsageTotals();
|
|
3568
3966
|
for (const entry of this.sessionManager.getEntries()) {
|
|
3569
|
-
if (
|
|
3967
|
+
if (entry.type === "usage") {
|
|
3968
|
+
addUsageToTotals(usageTotals, entry.usage);
|
|
3969
|
+
}
|
|
3970
|
+
else if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
|
|
3570
3971
|
addUsageToTotals(usageTotals, entry.usage);
|
|
3571
3972
|
}
|
|
3572
3973
|
if (entry.type !== "message")
|
|
@@ -3620,30 +4021,24 @@ export class AgentSession {
|
|
|
3620
4021
|
// After compaction, the last assistant usage reflects pre-compaction context size.
|
|
3621
4022
|
// We can only trust usage from an assistant that responded after the latest compaction.
|
|
3622
4023
|
// If no such assistant exists, context token count is unknown until the next LLM response.
|
|
3623
|
-
const
|
|
3624
|
-
const
|
|
4024
|
+
const projection = this.sessionManager.buildSessionProjection();
|
|
4025
|
+
const branch = this.sessionManager.getBranch();
|
|
4026
|
+
const latestCompaction = getLatestCompactionEntry(branch);
|
|
3625
4027
|
if (latestCompaction) {
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
break;
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
}
|
|
3641
|
-
}
|
|
3642
|
-
if (!hasPostCompactionUsage) {
|
|
4028
|
+
const projectedAssistants = new Set(projection.entries.flatMap((entry) => entry.messages.some((message) => message.role === "assistant" &&
|
|
4029
|
+
message.stopReason !== "aborted" &&
|
|
4030
|
+
message.stopReason !== "error" &&
|
|
4031
|
+
calculateContextTokens(message.usage) > 0)
|
|
4032
|
+
? [entry.sourceEntry.id]
|
|
4033
|
+
: []));
|
|
4034
|
+
const compactionIndex = branch.findIndex((entry) => entry.id === latestCompaction.id);
|
|
4035
|
+
const hasPostCompactionUsage = branch
|
|
4036
|
+
.slice(compactionIndex + 1)
|
|
4037
|
+
.some((entry) => projectedAssistants.has(entry.id));
|
|
4038
|
+
if (!hasPostCompactionUsage)
|
|
3643
4039
|
return { tokens: null, contextWindow, percent: null };
|
|
3644
|
-
}
|
|
3645
4040
|
}
|
|
3646
|
-
const estimate =
|
|
4041
|
+
const estimate = estimateProjectedContextTokens(projection, branch);
|
|
3647
4042
|
const percent = (estimate.tokens / contextWindow) * 100;
|
|
3648
4043
|
return {
|
|
3649
4044
|
tokens: estimate.tokens,
|
|
@@ -3680,6 +4075,30 @@ export class AgentSession {
|
|
|
3680
4075
|
exportToJsonl(outputPath) {
|
|
3681
4076
|
return exportSessionToJsonl(this.sessionManager, outputPath);
|
|
3682
4077
|
}
|
|
4078
|
+
/**
|
|
4079
|
+
* Ask the current model to describe what went wrong in this session for a bug report.
|
|
4080
|
+
* Used when the user declines to share the transcript itself.
|
|
4081
|
+
*/
|
|
4082
|
+
async summarizeForBugReport(options) {
|
|
4083
|
+
const model = this.model;
|
|
4084
|
+
if (!model) {
|
|
4085
|
+
throw new Error("No model selected");
|
|
4086
|
+
}
|
|
4087
|
+
const { model: requestModel, apiKey, headers, env } = await this._getSummarizationRequestAuth(model);
|
|
4088
|
+
return generateBugReportSummary({
|
|
4089
|
+
messages: this.messages,
|
|
4090
|
+
hint: options.hint,
|
|
4091
|
+
model: requestModel,
|
|
4092
|
+
apiKey,
|
|
4093
|
+
headers,
|
|
4094
|
+
env,
|
|
4095
|
+
signal: options.signal,
|
|
4096
|
+
thinkingLevel: this.thinkingLevel,
|
|
4097
|
+
streamFn: this.agent.streamFunction,
|
|
4098
|
+
retry: this.settingsManager.getRetrySettings(),
|
|
4099
|
+
sessionId: this.sessionId,
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
3683
4102
|
// =========================================================================
|
|
3684
4103
|
// Utilities
|
|
3685
4104
|
// =========================================================================
|