@ashx-j/lunr 0.2.21 → 0.2.23
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 +4 -2
- package/README.md +23 -5
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +1 -0
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-browser.d.ts +9 -0
- package/dist/builtin-extensions/lunr-browser.d.ts.map +1 -0
- package/dist/builtin-extensions/lunr-browser.js +169 -0
- package/dist/builtin-extensions/lunr-browser.js.map +1 -0
- package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-todos.js +15 -0
- package/dist/builtin-extensions/lunr-todos.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.js +3 -0
- package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.js +5 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +18 -5
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +3 -0
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/communication.d.ts +17 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/communication.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/communication.js +45 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/communication.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js +4 -11
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts +4 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +160 -51
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js +5 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js +3 -2
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/auto-drain.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/auto-drain.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/auto-drain.js +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/auto-drain.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts +3 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js +11 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +41 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +4 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +3 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-control.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-control.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-control.js +3 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-control.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts +18 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.js +48 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.js.map +1 -0
- package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.js +11 -7
- package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/install-cli.d.ts.map +1 -1
- package/dist/cli/install-cli.js +15 -45
- package/dist/cli/install-cli.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +9 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +126 -29
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +22 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +250 -105
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/browser/network.d.ts +9 -0
- package/dist/core/browser/network.d.ts.map +1 -0
- package/dist/core/browser/network.js +146 -0
- package/dist/core/browser/network.js.map +1 -0
- package/dist/core/browser/runtime.d.ts +24 -0
- package/dist/core/browser/runtime.d.ts.map +1 -0
- package/dist/core/browser/runtime.js +289 -0
- package/dist/core/browser/runtime.js.map +1 -0
- package/dist/core/browser/schema.d.ts +16 -0
- package/dist/core/browser/schema.d.ts.map +1 -0
- package/dist/core/browser/schema.js +36 -0
- package/dist/core/browser/schema.js.map +1 -0
- package/dist/core/browser/settings.d.ts +8 -0
- package/dist/core/browser/settings.d.ts.map +1 -0
- package/dist/core/browser/settings.js +30 -0
- package/dist/core/browser/settings.js.map +1 -0
- package/dist/core/browser/setup.d.ts +2 -0
- package/dist/core/browser/setup.d.ts.map +1 -0
- package/dist/core/browser/setup.js +16 -0
- package/dist/core/browser/setup.js.map +1 -0
- package/dist/core/customize.d.ts.map +1 -1
- package/dist/core/customize.js +15 -14
- package/dist/core/customize.js.map +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +1 -1
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +1 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/install-features.d.ts.map +1 -1
- package/dist/core/install-features.js +10 -8
- package/dist/core/install-features.js.map +1 -1
- package/dist/core/memory-cap.d.ts.map +1 -1
- package/dist/core/memory-cap.js +5 -3
- package/dist/core/memory-cap.js.map +1 -1
- package/dist/core/model-tiers.d.ts.map +1 -1
- package/dist/core/model-tiers.js +9 -6
- package/dist/core/model-tiers.js.map +1 -1
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +9 -0
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +3 -0
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/process-registry.d.ts.map +1 -1
- package/dist/core/process-registry.js +6 -4
- package/dist/core/process-registry.js.map +1 -1
- package/dist/core/runtime-scope.d.ts +11 -0
- package/dist/core/runtime-scope.d.ts.map +1 -0
- package/dist/core/runtime-scope.js +3 -0
- package/dist/core/runtime-scope.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +13 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-handoff.d.ts +35 -0
- package/dist/core/session-handoff.d.ts.map +1 -0
- package/dist/core/session-handoff.js +214 -0
- package/dist/core/session-handoff.js.map +1 -0
- package/dist/core/session-manager.d.ts +20 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +184 -22
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-ownership.d.ts +28 -0
- package/dist/core/session-ownership.d.ts.map +1 -0
- package/dist/core/session-ownership.js +138 -0
- package/dist/core/session-ownership.js.map +1 -0
- package/dist/core/session-retention.d.ts.map +1 -1
- package/dist/core/session-retention.js +11 -2
- package/dist/core/session-retention.js.map +1 -1
- package/dist/core/settings-manager.d.ts +11 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +27 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-wait-interruption.d.ts +11 -0
- package/dist/core/subagent-wait-interruption.d.ts.map +1 -0
- package/dist/core/subagent-wait-interruption.js +19 -0
- package/dist/core/subagent-wait-interruption.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +7 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/todo-settings.d.ts +5 -0
- package/dist/core/todo-settings.d.ts.map +1 -0
- package/dist/core/todo-settings.js +7 -0
- package/dist/core/todo-settings.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +4 -4
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +5 -3
- 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 -4
- 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 -4
- 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 -4
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -4
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +14 -5
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +34 -7
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +4 -4
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/usage-service.d.ts.map +1 -1
- package/dist/core/usage-service.js +5 -3
- package/dist/core/usage-service.js.map +1 -1
- package/dist/gateway/adapters/discord.d.ts +12 -1
- package/dist/gateway/adapters/discord.d.ts.map +1 -1
- package/dist/gateway/adapters/discord.js +112 -17
- package/dist/gateway/adapters/discord.js.map +1 -1
- package/dist/gateway/adapters/telegram.d.ts +4 -2
- package/dist/gateway/adapters/telegram.d.ts.map +1 -1
- package/dist/gateway/adapters/telegram.js +34 -11
- package/dist/gateway/adapters/telegram.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts +14 -3
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +252 -60
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/approval.d.ts +1 -0
- package/dist/gateway/approval.d.ts.map +1 -1
- package/dist/gateway/approval.js +60 -18
- package/dist/gateway/approval.js.map +1 -1
- package/dist/gateway/authz.d.ts +2 -0
- package/dist/gateway/authz.d.ts.map +1 -1
- package/dist/gateway/authz.js +13 -0
- package/dist/gateway/authz.js.map +1 -1
- package/dist/gateway/buttons.d.ts +3 -1
- package/dist/gateway/buttons.d.ts.map +1 -1
- package/dist/gateway/buttons.js +18 -3
- package/dist/gateway/buttons.js.map +1 -1
- package/dist/gateway/command.d.ts.map +1 -1
- package/dist/gateway/command.js +49 -4
- package/dist/gateway/command.js.map +1 -1
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +18 -3
- package/dist/gateway/commands.js.map +1 -1
- package/dist/gateway/config.d.ts +5 -0
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +5 -0
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/conversations.d.ts +14 -0
- package/dist/gateway/conversations.d.ts.map +1 -0
- package/dist/gateway/conversations.js +30 -0
- package/dist/gateway/conversations.js.map +1 -0
- package/dist/gateway/download.d.ts +2 -0
- package/dist/gateway/download.d.ts.map +1 -0
- package/dist/gateway/download.js +36 -0
- package/dist/gateway/download.js.map +1 -0
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +85 -26
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mobile-commands.d.ts +24 -0
- package/dist/gateway/mobile-commands.d.ts.map +1 -0
- package/dist/gateway/mobile-commands.js +422 -0
- package/dist/gateway/mobile-commands.js.map +1 -0
- package/dist/gateway/presenter.d.ts +16 -0
- package/dist/gateway/presenter.d.ts.map +1 -0
- package/dist/gateway/presenter.js +210 -0
- package/dist/gateway/presenter.js.map +1 -0
- package/dist/gateway/router.d.ts +1 -0
- package/dist/gateway/router.d.ts.map +1 -1
- package/dist/gateway/router.js +32 -3
- package/dist/gateway/router.js.map +1 -1
- package/dist/gateway/service.d.ts +46 -0
- package/dist/gateway/service.d.ts.map +1 -0
- package/dist/gateway/service.js +395 -0
- package/dist/gateway/service.js.map +1 -0
- package/dist/gateway/setup.d.ts +4 -0
- package/dist/gateway/setup.d.ts.map +1 -0
- package/dist/gateway/setup.js +146 -0
- package/dist/gateway/setup.js.map +1 -0
- package/dist/gateway/types.d.ts +4 -2
- package/dist/gateway/types.d.ts.map +1 -1
- package/dist/gateway/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +19 -2
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +78 -17
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/copyable-text.d.ts +27 -0
- package/dist/modes/interactive/components/copyable-text.d.ts.map +1 -0
- package/dist/modes/interactive/components/copyable-text.js +156 -0
- package/dist/modes/interactive/components/copyable-text.js.map +1 -0
- package/dist/modes/interactive/components/custom-entry.d.ts +1 -0
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-entry.js +8 -0
- package/dist/modes/interactive/components/custom-entry.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +1 -0
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +8 -0
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +16 -0
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +8 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +51 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.d.ts +9 -2
- package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.js +43 -1
- package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +4 -3
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +34 -45
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +267 -44
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +1 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +23 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/node-runtime/agent-session-services-GZVSOGYR.js +49 -0
- package/dist/node-runtime/{builtin-extensions-JCOPAAB5.js → builtin-extensions-QVCXNQT4.js} +6 -5
- package/dist/node-runtime/{chunk-LTYWTR3V.js → chunk-2MI5OPMN.js} +17 -2
- package/dist/node-runtime/{chunk-W3CVH5RX.js → chunk-2TKODGBZ.js} +11 -6
- package/dist/node-runtime/chunk-4EULYYR4.js +62 -0
- package/dist/node-runtime/{chunk-6N22HULO.js → chunk-5D7LC2ZQ.js} +7 -2
- package/dist/node-runtime/{chunk-KBKMCJDU.js → chunk-5ZQXRPMC.js} +19 -4
- package/dist/node-runtime/{chunk-BOHIDQT4.js → chunk-6VYZ5UFP.js} +1 -1
- package/dist/node-runtime/chunk-AZJHNQKQ.js +205 -0
- package/dist/node-runtime/{chunk-A3KMVBZV.js → chunk-BBUVIEZB.js} +1 -10
- package/dist/node-runtime/{chunk-IRPJXI6U.js → chunk-BX2RB477.js} +1 -1
- package/dist/node-runtime/chunk-CCCN2UCX.js +14 -0
- package/dist/node-runtime/{chunk-25I2BHAA.js → chunk-CSUZVNYE.js} +1 -1
- package/dist/node-runtime/chunk-DD5YWFSJ.js +708 -0
- package/dist/node-runtime/chunk-DQMGU675.js +3409 -0
- package/dist/node-runtime/{chunk-QZMODZQ2.js → chunk-E24EO34J.js} +2 -1
- package/dist/node-runtime/chunk-EKVFP6YF.js +355 -0
- package/dist/node-runtime/chunk-GJ2RDBRL.js +672 -0
- package/dist/node-runtime/{chunk-W3SILANP.js → chunk-GMHJCCEN.js} +8 -8
- package/dist/node-runtime/{chunk-DDWHZ633.js → chunk-GS42T6MX.js} +1 -1
- package/dist/node-runtime/chunk-GS66LV4M.js +2830 -0
- package/dist/node-runtime/chunk-H4K3JDRB.js +47 -0
- package/dist/node-runtime/{chunk-2SJB2AWP.js → chunk-HMBGROQK.js} +3 -3
- package/dist/node-runtime/{chunk-GB2MGFLI.js → chunk-HPUP7E4T.js} +216 -201
- package/dist/node-runtime/{chunk-YMEAYRBE.js → chunk-HTFRRDSF.js} +2 -7
- package/dist/node-runtime/{chunk-OPEK5RRY.js → chunk-IF24A3FH.js} +1 -1
- package/dist/node-runtime/chunk-IMLCWLPI.js +9 -0
- package/dist/node-runtime/chunk-KKUTQQV7.js +144 -0
- package/dist/node-runtime/chunk-LFZDVKSP.js +521 -0
- package/dist/node-runtime/{chunk-2BUAYZ7K.js → chunk-LLUIPGDR.js} +581 -197
- package/dist/node-runtime/{chunk-7NBZ5P7I.js → chunk-NAY6UUQZ.js} +1 -1
- package/dist/node-runtime/chunk-OAR4TYZE.js +146 -0
- package/dist/node-runtime/{chunk-STERBGUB.js → chunk-OFKSVRV7.js} +2 -2
- package/dist/node-runtime/{chunk-JZQ7F36T.js → chunk-PCEW76PM.js} +19 -3
- package/dist/node-runtime/{chunk-WLLDXJRT.js → chunk-QAZOULTU.js} +29 -1014
- package/dist/node-runtime/{chunk-6KDIOJL3.js → chunk-QH6YL7VK.js} +3 -3
- package/dist/node-runtime/chunk-R3K2F5GI.js +122 -0
- package/dist/node-runtime/{chunk-RNBHABBA.js → chunk-RDDAZOMU.js} +23 -0
- package/dist/node-runtime/{chunk-SY2TFMLO.js → chunk-RIR5EDDJ.js} +46 -151
- package/dist/node-runtime/chunk-RMQZU2YU.js +117 -0
- package/dist/node-runtime/{chunk-6BEIBLQ3.js → chunk-RWTKOC3B.js} +1 -1
- package/dist/node-runtime/{chunk-G6LRAP36.js → chunk-TQGPP4SC.js} +1 -1
- package/dist/node-runtime/{chunk-Y5KOEMBH.js → chunk-TVJUOJZJ.js} +9 -674
- package/dist/node-runtime/chunk-UAEOTO52.js +119 -0
- package/dist/node-runtime/{chunk-AY6CAJSL.js → chunk-UJOCPOMX.js} +6 -3
- package/dist/node-runtime/chunk-UP2XBVH5.js +40 -0
- package/dist/node-runtime/{chunk-WJVFOIB5.js → chunk-UQUCFAJ3.js} +1 -1
- package/dist/node-runtime/chunk-URD2SLIT.js +1019 -0
- package/dist/node-runtime/chunk-US544NN6.js +13 -0
- package/dist/node-runtime/chunk-UWPJL22I.js +341 -0
- package/dist/node-runtime/{chunk-4I23VTC4.js → chunk-VWCE2HSZ.js} +21 -124
- package/dist/node-runtime/{chunk-VZR3NCM7.js → chunk-W64JC4E7.js} +18 -14
- package/dist/node-runtime/{chunk-AWVD5IEY.js → chunk-X3URCYCU.js} +23 -12
- package/dist/node-runtime/{chunk-GKLEQ3I4.js → chunk-XLTL257W.js} +126 -27
- package/dist/node-runtime/{chunk-WKDK4C5P.js → chunk-YDO2F2IA.js} +38 -164
- package/dist/node-runtime/{chunk-PDNXVXMD.js → chunk-YHQ3NBB3.js} +58 -48
- package/dist/node-runtime/chunk-YPHAIRZT.js +10 -0
- package/dist/node-runtime/cli-runtime.js +2 -2
- package/dist/node-runtime/command-PDVTBRWA.js +45 -0
- package/dist/node-runtime/{commands-MV5KOZ64.js → commands-KAMO2T6P.js} +10 -10
- package/dist/node-runtime/commands-XDCFGIGW.js +36 -0
- package/dist/node-runtime/config-RORQUQFG.js +20 -0
- package/dist/node-runtime/{customize-LF5VST47.js → customize-VFJV3U6A.js} +2 -1
- package/dist/node-runtime/{direct-tool-executor-BK7CLEBX.js → direct-tool-executor-YAVOLDFY.js} +8 -8
- package/dist/node-runtime/{export-html-TEXVV5RP.js → export-html-KSOIMVPI.js} +4 -3
- package/dist/node-runtime/{extension-input-O4HSAEM4.js → extension-input-E7BJZ35H.js} +4 -4
- package/dist/node-runtime/{extension-selector-X5FCX43D.js → extension-selector-WU42XDTU.js} +4 -4
- package/dist/node-runtime/{extract-XLQSJN4V.js → extract-EQJE7FZZ.js} +2 -2
- package/dist/node-runtime/{gateway-BM6NLOH4.js → gateway-AYWM5DIO.js} +275 -994
- package/dist/node-runtime/{gemini-search-U7SUMPQA.js → gemini-search-35OCRFLN.js} +1 -1
- package/dist/node-runtime/{gemini-url-context-PXWCBQHK.js → gemini-url-context-UQY3PHHW.js} +2 -2
- package/dist/node-runtime/index.js +104 -65
- package/dist/node-runtime/{init-3C7Z24OI.js → init-X2LIUB2H.js} +3 -3
- package/dist/node-runtime/{install-cli-EXMJQESX.js → install-cli-3BO4MMQ6.js} +21 -82
- package/dist/node-runtime/interactive-mode-4MFRKAHG.js +72 -0
- package/dist/node-runtime/{interactive-view-ILS6TAN5.js → interactive-view-TUZHCQM3.js} +8 -8
- package/dist/node-runtime/lunr-browser-RYFIIVJN.js +174 -0
- package/dist/node-runtime/{lunr-cron-4VHP4E4B.js → lunr-cron-YWXEPZMQ.js} +19 -5
- package/dist/node-runtime/main.js +44 -31
- package/dist/node-runtime/{memory-cap-3S4XRLNP.js → memory-cap-EL3KL4JJ.js} +2 -1
- package/dist/node-runtime/mobile-commands-LVHDAUDP.js +32 -0
- package/dist/node-runtime/model-runtime-PLQKAG4B.js +16 -0
- package/dist/node-runtime/{model-tiers-NJLHUEGK.js → model-tiers-OU3U5CZG.js} +2 -1
- package/dist/node-runtime/{package-manager-cli-N37546V5.js → package-manager-cli-6GT3Z3CK.js} +22 -15
- package/dist/node-runtime/{pi-intercom-NV2LW6X7.js → pi-intercom-HBPK5HSJ.js} +4 -1
- package/dist/node-runtime/{pi-mcp-adapter-GUFZWW54.js → pi-mcp-adapter-E6WULBKF.js} +4 -4
- package/dist/node-runtime/{pi-subagents-6MUIFNDM.js → pi-subagents-CDWK2AKY.js} +236 -79
- package/dist/node-runtime/{pi-web-access-2CQGFLT2.js → pi-web-access-WTGMNZ32.js} +15 -15
- package/dist/node-runtime/process-registry-4FAKZQOL.js +31 -0
- package/dist/node-runtime/{proxy-modes-V6YVFH3C.js → proxy-modes-OSQCWRBI.js} +9 -9
- package/dist/node-runtime/rpc-entry.js +44 -31
- package/dist/node-runtime/{rpc-mode-W5ZTLTMZ.js → rpc-mode-O3XV4SID.js} +2 -2
- package/dist/node-runtime/runtime-4VA6JKNF.js +373 -0
- package/dist/node-runtime/{runtime-bridges-2VQTLPOZ.js → runtime-bridges-3XUQ46XW.js} +6 -5
- package/dist/node-runtime/service-ISSP6VHE.js +36 -0
- package/dist/node-runtime/session-handoff-HC7R5E24.js +33 -0
- package/dist/node-runtime/{session-manager-E7AREDTV.js → session-manager-CFN4UFM2.js} +2 -1
- package/dist/node-runtime/{session-selector-FJ7M2AIX.js → session-selector-GIM4DHQ2.js} +5 -4
- package/dist/node-runtime/{settings-manager-CUGAZWAB.js → settings-manager-6VEMXRDL.js} +7 -2
- package/dist/node-runtime/setup-5SR2J35A.js +155 -0
- package/dist/node-runtime/setup-JBKOLJ6F.js +18 -0
- package/dist/node-runtime/{subagent-executor-MH4A7CCF.js → subagent-executor-M5VWB3D3.js} +26 -18
- package/dist/node-runtime/theme-UOO5RVFN.js +68 -0
- package/dist/node-runtime/trust-manager-MMISNCYO.js +17 -0
- package/dist/node-runtime/{update-cli-XTZMZGOL.js → update-cli-UVKJWRRJ.js} +7 -2
- package/dist/node-runtime/usage-service-LFB2SWYV.js +37 -0
- package/dist/node-runtime/{video-extract-OYT5WKS5.js → video-extract-OOZHR7SU.js} +2 -2
- package/dist/node-runtime/{youtube-extract-TQYKGFGS.js → youtube-extract-FNPAUNHU.js} +6 -6
- package/docs/browser.md +86 -0
- package/docs/extensions.md +2 -0
- package/docs/features.md +96 -16
- package/docs/interactive-startup.md +2 -1
- package/docs/sessions.md +10 -0
- package/docs/settings.md +2 -0
- package/docs/tui.md +2 -0
- package/docs/usage.md +16 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +7 -4
- package/scripts/install-browser.mjs +20 -0
- package/dist/node-runtime/agent-session-services-C743TYQE.js +0 -37
- package/dist/node-runtime/chunk-VGYZ3APQ.js +0 -59
- package/dist/node-runtime/chunk-XI77KVKA.js +0 -6009
- package/dist/node-runtime/command-P4NYCJ2M.js +0 -13
- package/dist/node-runtime/interactive-mode-EVVBA64D.js +0 -59
- package/dist/node-runtime/{chunk-UWQYRSYQ.js → chunk-YJMMG7C6.js} +25 -25
- package/dist/node-runtime/{mcp-panel-IIGMXQGP.js → mcp-panel-KTTFXJZ4.js} +3 -3
- package/dist/node-runtime/{src-BPQFJR76.js → src-IVPNZ2BX.js} +9 -9
|
@@ -0,0 +1,3409 @@
|
|
|
1
|
+
import { createRequire as __lunrRequire } from "node:module"; const require = __lunrRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
TODO_TOOL_NAMES
|
|
4
|
+
} from "./chunk-CCCN2UCX.js";
|
|
5
|
+
import {
|
|
6
|
+
DefaultResourceLoader,
|
|
7
|
+
createSyntheticSourceInfo,
|
|
8
|
+
expandPromptTemplate,
|
|
9
|
+
formatSkillsForPrompt
|
|
10
|
+
} from "./chunk-TVJUOJZJ.js";
|
|
11
|
+
import {
|
|
12
|
+
stripFrontmatter
|
|
13
|
+
} from "./chunk-QFWG2RVL.js";
|
|
14
|
+
import {
|
|
15
|
+
createAllToolDefinitions,
|
|
16
|
+
createLocalBashOperations,
|
|
17
|
+
createToolDefinitionFromAgentTool,
|
|
18
|
+
wrapToolDefinition
|
|
19
|
+
} from "./chunk-YDO2F2IA.js";
|
|
20
|
+
import {
|
|
21
|
+
DEFAULT_MAX_BYTES,
|
|
22
|
+
truncateTail
|
|
23
|
+
} from "./chunk-53YJI7JU.js";
|
|
24
|
+
import {
|
|
25
|
+
stripAnsi
|
|
26
|
+
} from "./chunk-5ZQXRPMC.js";
|
|
27
|
+
import {
|
|
28
|
+
ModelRuntime,
|
|
29
|
+
isAuthInvalidError,
|
|
30
|
+
isUsageLimitError
|
|
31
|
+
} from "./chunk-GS66LV4M.js";
|
|
32
|
+
import {
|
|
33
|
+
SettingsManager
|
|
34
|
+
} from "./chunk-QAZOULTU.js";
|
|
35
|
+
import {
|
|
36
|
+
DEFAULT_THINKING_LEVEL,
|
|
37
|
+
calculateContextTokens,
|
|
38
|
+
collectEntriesForBranchSummary,
|
|
39
|
+
compact,
|
|
40
|
+
estimateContextTokens,
|
|
41
|
+
estimateTokens,
|
|
42
|
+
findInitialModel,
|
|
43
|
+
generateBranchSummary,
|
|
44
|
+
prepareCompaction,
|
|
45
|
+
shouldCompact
|
|
46
|
+
} from "./chunk-OFKSVRV7.js";
|
|
47
|
+
import {
|
|
48
|
+
ExtensionRunner,
|
|
49
|
+
emitSessionShutdownEvent
|
|
50
|
+
} from "./chunk-GJ2RDBRL.js";
|
|
51
|
+
import {
|
|
52
|
+
getSubagentCancellation
|
|
53
|
+
} from "./chunk-BBUVIEZB.js";
|
|
54
|
+
import {
|
|
55
|
+
gateToolCall
|
|
56
|
+
} from "./chunk-5D7LC2ZQ.js";
|
|
57
|
+
import {
|
|
58
|
+
isUserInstructionsPath,
|
|
59
|
+
loadSelectedUserInstructions
|
|
60
|
+
} from "./chunk-M3LZLQW2.js";
|
|
61
|
+
import {
|
|
62
|
+
list
|
|
63
|
+
} from "./chunk-KKUTQQV7.js";
|
|
64
|
+
import {
|
|
65
|
+
sanitizeBinaryOutput
|
|
66
|
+
} from "./chunk-CSFI6YXL.js";
|
|
67
|
+
import {
|
|
68
|
+
SessionTransferError
|
|
69
|
+
} from "./chunk-AZJHNQKQ.js";
|
|
70
|
+
import {
|
|
71
|
+
CURRENT_SESSION_VERSION,
|
|
72
|
+
SessionManager,
|
|
73
|
+
getDefaultSessionDir,
|
|
74
|
+
getLatestCompactionEntry
|
|
75
|
+
} from "./chunk-XLTL257W.js";
|
|
76
|
+
import {
|
|
77
|
+
convertToLlm
|
|
78
|
+
} from "./chunk-FVDOXRUD.js";
|
|
79
|
+
import {
|
|
80
|
+
MEMORY_TOOL_NAMES
|
|
81
|
+
} from "./chunk-UJOCPOMX.js";
|
|
82
|
+
import {
|
|
83
|
+
SessionOwnership,
|
|
84
|
+
canonicalSessionPath
|
|
85
|
+
} from "./chunk-UAEOTO52.js";
|
|
86
|
+
import {
|
|
87
|
+
measureStartup,
|
|
88
|
+
time
|
|
89
|
+
} from "./chunk-PVCDKGR6.js";
|
|
90
|
+
import {
|
|
91
|
+
getThemeByName,
|
|
92
|
+
theme
|
|
93
|
+
} from "./chunk-RDDAZOMU.js";
|
|
94
|
+
import {
|
|
95
|
+
CONFIG_DIR_NAME,
|
|
96
|
+
getAgentDir,
|
|
97
|
+
getDocsPath,
|
|
98
|
+
getExamplesPath,
|
|
99
|
+
getReadmePath,
|
|
100
|
+
resolvePath
|
|
101
|
+
} from "./chunk-FQMM2EZ2.js";
|
|
102
|
+
|
|
103
|
+
// packages/coding-agent/dist/core/agent-session-services.js
|
|
104
|
+
import { join as join7 } from "node:path";
|
|
105
|
+
|
|
106
|
+
// packages/coding-agent/dist/core/sdk.js
|
|
107
|
+
import { join as join6 } from "node:path";
|
|
108
|
+
import { Agent } from "@ashx-j/lunr-agent";
|
|
109
|
+
import { clampThinkingLevel as clampThinkingLevel2 } from "@ashx-j/lunr-ai";
|
|
110
|
+
|
|
111
|
+
// packages/coding-agent/dist/core/agent-session.js
|
|
112
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
113
|
+
import { basename, dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
|
|
114
|
+
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple } from "@ashx-j/lunr-ai/compat";
|
|
115
|
+
|
|
116
|
+
// packages/coding-agent/dist/utils/sleep.js
|
|
117
|
+
function sleep(ms, signal) {
|
|
118
|
+
return new Promise((resolve3, reject) => {
|
|
119
|
+
if (signal?.aborted) {
|
|
120
|
+
reject(new Error("Aborted"));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let timeout = setTimeout(resolve3, ms);
|
|
124
|
+
signal?.addEventListener("abort", () => {
|
|
125
|
+
clearTimeout(timeout), reject(new Error("Aborted"));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// packages/coding-agent/dist/core/auth-guidance.js
|
|
131
|
+
import { join } from "node:path";
|
|
132
|
+
var UNKNOWN_PROVIDER = "unknown";
|
|
133
|
+
function getProviderLoginHelp() {
|
|
134
|
+
return [
|
|
135
|
+
"Use /login to log into a provider via OAuth or API key. See:",
|
|
136
|
+
` ${join(getDocsPath(), "providers.md")}`,
|
|
137
|
+
` ${join(getDocsPath(), "models.md")}`
|
|
138
|
+
].join(`
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
function formatNoModelsAvailableMessage() {
|
|
142
|
+
return `No models available. ${getProviderLoginHelp()}`;
|
|
143
|
+
}
|
|
144
|
+
function formatNoModelSelectedMessage() {
|
|
145
|
+
return `No model selected.
|
|
146
|
+
|
|
147
|
+
${getProviderLoginHelp()}
|
|
148
|
+
|
|
149
|
+
Then use /model to select a model.`;
|
|
150
|
+
}
|
|
151
|
+
function formatNoApiKeyFoundMessage(provider) {
|
|
152
|
+
return `No API key found for ${provider === UNKNOWN_PROVIDER ? "the selected model" : provider}.
|
|
153
|
+
|
|
154
|
+
${getProviderLoginHelp()}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// packages/coding-agent/dist/core/bash-executor.js
|
|
158
|
+
import { randomBytes } from "node:crypto";
|
|
159
|
+
import { createWriteStream } from "node:fs";
|
|
160
|
+
import { tmpdir } from "node:os";
|
|
161
|
+
import { join as join2 } from "node:path";
|
|
162
|
+
async function executeBashWithOperations(command, cwd, operations, options) {
|
|
163
|
+
let outputChunks = [], outputBytes = 0, maxOutputBytes = 51200 * 2, tempFilePath, tempFileStream, totalBytes = 0, ensureTempFile = () => {
|
|
164
|
+
if (tempFilePath)
|
|
165
|
+
return;
|
|
166
|
+
let id = randomBytes(8).toString("hex");
|
|
167
|
+
tempFilePath = join2(tmpdir(), `lunr-bash-${id}.log`), tempFileStream = createWriteStream(tempFilePath);
|
|
168
|
+
for (let chunk of outputChunks)
|
|
169
|
+
tempFileStream.write(chunk);
|
|
170
|
+
}, decoder = new TextDecoder(), onData = (data) => {
|
|
171
|
+
totalBytes += data.length;
|
|
172
|
+
let text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: !0 }))).replace(/\r/g, "");
|
|
173
|
+
for (totalBytes > 51200 && ensureTempFile(), tempFileStream && tempFileStream.write(text), outputChunks.push(text), outputBytes += text.length; outputBytes > maxOutputBytes && outputChunks.length > 1; ) {
|
|
174
|
+
let removed = outputChunks.shift();
|
|
175
|
+
outputBytes -= removed.length;
|
|
176
|
+
}
|
|
177
|
+
options?.onChunk && options.onChunk(text);
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
let result = await operations.exec(command, cwd, {
|
|
181
|
+
onData,
|
|
182
|
+
signal: options?.signal
|
|
183
|
+
}), fullOutput = outputChunks.join(""), truncationResult = truncateTail(fullOutput);
|
|
184
|
+
truncationResult.truncated && ensureTempFile(), tempFileStream && tempFileStream.end();
|
|
185
|
+
let cancelled = options?.signal?.aborted ?? !1;
|
|
186
|
+
return {
|
|
187
|
+
output: truncationResult.truncated ? truncationResult.content : fullOutput,
|
|
188
|
+
exitCode: cancelled ? void 0 : result.exitCode ?? void 0,
|
|
189
|
+
cancelled,
|
|
190
|
+
truncated: truncationResult.truncated,
|
|
191
|
+
fullOutputPath: tempFilePath
|
|
192
|
+
};
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (options?.signal?.aborted) {
|
|
195
|
+
let fullOutput = outputChunks.join(""), truncationResult = truncateTail(fullOutput);
|
|
196
|
+
return truncationResult.truncated && ensureTempFile(), tempFileStream && tempFileStream.end(), {
|
|
197
|
+
output: truncationResult.truncated ? truncationResult.content : fullOutput,
|
|
198
|
+
exitCode: void 0,
|
|
199
|
+
cancelled: !0,
|
|
200
|
+
truncated: truncationResult.truncated,
|
|
201
|
+
fullOutputPath: tempFilePath
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
throw tempFileStream && tempFileStream.end(), err;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// packages/coding-agent/dist/core/extensions/wrapper.js
|
|
209
|
+
function wrapRegisteredTool(registeredTool, runner) {
|
|
210
|
+
let tool = wrapToolDefinition(registeredTool.definition, () => runner.createContext()), execute = tool.execute;
|
|
211
|
+
return {
|
|
212
|
+
...tool,
|
|
213
|
+
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
214
|
+
let activeBefore = runner.getActiveTools(), result = await execute(toolCallId, params, signal, onUpdate), activeAfter = runner.getActiveTools();
|
|
215
|
+
if (!activeBefore.every((name) => activeAfter.includes(name)))
|
|
216
|
+
return result;
|
|
217
|
+
let beforeNames = new Set(activeBefore), addedToolNames = activeAfter.filter((name) => !beforeNames.has(name));
|
|
218
|
+
return addedToolNames.length === 0 ? result : {
|
|
219
|
+
...result,
|
|
220
|
+
addedToolNames: [.../* @__PURE__ */ new Set([...result.addedToolNames ?? [], ...addedToolNames])]
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function wrapRegisteredTools(registeredTools, runner) {
|
|
226
|
+
return registeredTools.map((tool) => wrapRegisteredTool(tool, runner));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// packages/coding-agent/dist/core/model-registry.js
|
|
230
|
+
var ModelRegistry = class {
|
|
231
|
+
runtime;
|
|
232
|
+
constructor(runtime) {
|
|
233
|
+
this.runtime = runtime;
|
|
234
|
+
}
|
|
235
|
+
/** Reload models.json asynchronously. Await before making synchronous registry reads. */
|
|
236
|
+
refresh() {
|
|
237
|
+
return this.runtime.reloadConfig();
|
|
238
|
+
}
|
|
239
|
+
getError() {
|
|
240
|
+
return this.runtime.getError();
|
|
241
|
+
}
|
|
242
|
+
getAll() {
|
|
243
|
+
return [...this.runtime.getModels()];
|
|
244
|
+
}
|
|
245
|
+
getAvailable() {
|
|
246
|
+
return [...this.runtime.getAvailableSnapshot()];
|
|
247
|
+
}
|
|
248
|
+
find(provider, modelId) {
|
|
249
|
+
return this.runtime.getModel(provider, modelId);
|
|
250
|
+
}
|
|
251
|
+
hasConfiguredAuth(model) {
|
|
252
|
+
return this.runtime.hasConfiguredAuth(model.provider);
|
|
253
|
+
}
|
|
254
|
+
async getApiKeyAndHeaders(model) {
|
|
255
|
+
try {
|
|
256
|
+
let resolution = await this.runtime.getAuth(model);
|
|
257
|
+
if (!resolution) {
|
|
258
|
+
let compatibility = this.runtime.getCompatibilityRequestConfig(model);
|
|
259
|
+
return compatibility.authHeader ? { ok: !1, error: `No API key found for "${model.provider}"` } : { ok: !0, headers: compatibility.headers ? Object.fromEntries(Object.entries(compatibility.headers).filter((entry) => entry[1] !== null)) : void 0 };
|
|
260
|
+
}
|
|
261
|
+
let headers = resolution.auth.headers ? Object.fromEntries(Object.entries(resolution.auth.headers).filter((entry) => entry[1] !== null)) : void 0;
|
|
262
|
+
return { ok: !0, apiKey: resolution.auth.apiKey, headers, env: resolution.env };
|
|
263
|
+
} catch (error) {
|
|
264
|
+
let cause = error instanceof Error ? error.cause : void 0, message = cause instanceof Error ? cause.message : error instanceof Error ? error.message : String(error);
|
|
265
|
+
return {
|
|
266
|
+
ok: !1,
|
|
267
|
+
error: message === "authHeader requires a resolved API key" ? `No API key found for "${model.provider}"` : message
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
getProviderAuthStatus(provider) {
|
|
272
|
+
return this.runtime.getProviderAuthStatus(provider);
|
|
273
|
+
}
|
|
274
|
+
getProviderDisplayName(provider) {
|
|
275
|
+
return this.runtime.getProvider(provider)?.name ?? provider;
|
|
276
|
+
}
|
|
277
|
+
async getApiKeyForProvider(provider) {
|
|
278
|
+
try {
|
|
279
|
+
return (await this.runtime.getAuth(provider))?.auth.apiKey;
|
|
280
|
+
} catch {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
isUsingOAuth(model) {
|
|
285
|
+
return this.runtime.isUsingOAuth(model.provider);
|
|
286
|
+
}
|
|
287
|
+
registerProvider(providerName, config) {
|
|
288
|
+
this.runtime.registerProvider(providerName, config);
|
|
289
|
+
}
|
|
290
|
+
unregisterProvider(providerName) {
|
|
291
|
+
this.runtime.unregisterProvider(providerName);
|
|
292
|
+
}
|
|
293
|
+
getRegisteredProviderConfig(providerName) {
|
|
294
|
+
return this.runtime.getRegisteredProviderConfig(providerName);
|
|
295
|
+
}
|
|
296
|
+
getRegisteredProviderIds() {
|
|
297
|
+
return this.runtime.getRegisteredProviderIds();
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// packages/coding-agent/dist/core/rollback.js
|
|
302
|
+
import { spawnSync } from "node:child_process";
|
|
303
|
+
import { createHash } from "node:crypto";
|
|
304
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
305
|
+
import { homedir } from "node:os";
|
|
306
|
+
import { dirname, join as join3, normalize, resolve } from "node:path";
|
|
307
|
+
var ROLLBACK_BASE = join3(homedir(), ".lunr", "rollback"), contexts = /* @__PURE__ */ new Map(), lastSessionId, globalSettingsManager, warningHandler;
|
|
308
|
+
function setRollbackWarningHandler(handler) {
|
|
309
|
+
warningHandler = handler;
|
|
310
|
+
}
|
|
311
|
+
function createContext(sessionId) {
|
|
312
|
+
return {
|
|
313
|
+
sessionId,
|
|
314
|
+
settingsManager: globalSettingsManager,
|
|
315
|
+
sessionForceEnabled: !1,
|
|
316
|
+
turns: [],
|
|
317
|
+
currentTurnIndex: -1,
|
|
318
|
+
repoWarningShown: !1,
|
|
319
|
+
externalModWarningShown: !1
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function getContext(sessionId) {
|
|
323
|
+
let sid = sessionId ?? lastSessionId ?? "default", ctx = contexts.get(sid);
|
|
324
|
+
return ctx || (ctx = createContext(sid), contexts.set(sid, ctx)), ctx;
|
|
325
|
+
}
|
|
326
|
+
function initRollback(sm, sid) {
|
|
327
|
+
globalSettingsManager = sm, lastSessionId = sid, contexts.set(sid, createContext(sid));
|
|
328
|
+
let ctx = contexts.get(sid);
|
|
329
|
+
ctx.settingsManager = sm, loadPersistedTurns(ctx);
|
|
330
|
+
}
|
|
331
|
+
function isRollbackEnabled(sessionId) {
|
|
332
|
+
let ctx = getContext(sessionId);
|
|
333
|
+
return (ctx.settingsManager?.getRollbackEnabled() ?? !1) || ctx.sessionForceEnabled;
|
|
334
|
+
}
|
|
335
|
+
function isRollbackSessionForceEnabled(sessionId) {
|
|
336
|
+
return getContext(sessionId).sessionForceEnabled;
|
|
337
|
+
}
|
|
338
|
+
function enableRollbackForSession(sessionId) {
|
|
339
|
+
getContext(sessionId).sessionForceEnabled = !0;
|
|
340
|
+
}
|
|
341
|
+
function disableRollbackForSession(sessionId) {
|
|
342
|
+
getContext(sessionId).sessionForceEnabled = !1;
|
|
343
|
+
}
|
|
344
|
+
function pruneTurns(ctx) {
|
|
345
|
+
let maxTurns = ctx.settingsManager?.getRollbackTurns() ?? 2;
|
|
346
|
+
for (; ctx.turns.length > maxTurns; ) {
|
|
347
|
+
let old = ctx.turns.shift();
|
|
348
|
+
old && cleanupTurnFiles(ctx, old);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function beginTurn(cwd, sessionId) {
|
|
352
|
+
let ctx = getContext(sessionId);
|
|
353
|
+
if (!isRollbackEnabled(ctx.sessionId))
|
|
354
|
+
return;
|
|
355
|
+
cwd && (ctx.lastCwd = cwd), ctx.currentTurnIndex++;
|
|
356
|
+
let turn = { turnIndex: ctx.currentTurnIndex, cwd, files: /* @__PURE__ */ new Map() };
|
|
357
|
+
ctx.turns.push(turn), pruneTurns(ctx), cwd && ctx.settingsManager?.getRollbackScope() === "tree" && captureTreeBaseline(ctx, cwd);
|
|
358
|
+
}
|
|
359
|
+
function rollbackSnapshotBeforeWrite(absPath, sessionId) {
|
|
360
|
+
let ctx = getContext(sessionId);
|
|
361
|
+
if (!isRollbackEnabled(ctx.sessionId))
|
|
362
|
+
return;
|
|
363
|
+
(ctx.currentTurnIndex < 0 || ctx.turns.length === 0) && beginTurn(ctx.lastCwd, ctx.sessionId);
|
|
364
|
+
let turn = ctx.turns[ctx.turns.length - 1];
|
|
365
|
+
if (turn && !turn.files.has(absPath))
|
|
366
|
+
try {
|
|
367
|
+
let existed = existsSync(absPath), content;
|
|
368
|
+
existed && (content = readFileSync(absPath)), turn.files.set(absPath, {
|
|
369
|
+
existed,
|
|
370
|
+
content,
|
|
371
|
+
createdByTool: !0
|
|
372
|
+
}), persistSnapshot(ctx, turn, absPath);
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function captureTreeBaseline(ctx, cwd) {
|
|
377
|
+
let turn = ctx.turns[ctx.turns.length - 1];
|
|
378
|
+
if (!turn)
|
|
379
|
+
return;
|
|
380
|
+
let entries = gitPorcelain(ctx, cwd);
|
|
381
|
+
if (entries)
|
|
382
|
+
for (let { path } of entries) {
|
|
383
|
+
if (!path)
|
|
384
|
+
continue;
|
|
385
|
+
let abs = join3(cwd, path);
|
|
386
|
+
if (!turn.files.has(abs))
|
|
387
|
+
try {
|
|
388
|
+
let existed = existsSync(abs);
|
|
389
|
+
turn.files.set(abs, {
|
|
390
|
+
existed,
|
|
391
|
+
content: existed ? readFileSync(abs) : void 0,
|
|
392
|
+
createdByTool: !1
|
|
393
|
+
}), persistSnapshot(ctx, turn, abs);
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function captureTreeChanges(cwd, sessionId) {
|
|
399
|
+
let ctx = getContext(sessionId);
|
|
400
|
+
if (!isRollbackEnabled(ctx.sessionId) || ctx.settingsManager?.getRollbackScope() !== "tree" || ctx.currentTurnIndex < 0 || ctx.turns.length === 0)
|
|
401
|
+
return;
|
|
402
|
+
let turn = ctx.turns[ctx.turns.length - 1];
|
|
403
|
+
if (!turn)
|
|
404
|
+
return;
|
|
405
|
+
let entries = gitPorcelain(ctx, cwd);
|
|
406
|
+
if (entries)
|
|
407
|
+
for (let { status, path, source } of entries) {
|
|
408
|
+
if (!path)
|
|
409
|
+
continue;
|
|
410
|
+
let abs = join3(cwd, path);
|
|
411
|
+
if (!turn.files.has(abs))
|
|
412
|
+
try {
|
|
413
|
+
if (status === "??" || status.includes("R")) {
|
|
414
|
+
if (turn.files.set(abs, { existed: !1, createdByTool: !1 }), persistSnapshot(ctx, turn, abs), status.includes("R") && source) {
|
|
415
|
+
let sourceAbs = join3(cwd, source);
|
|
416
|
+
if (!turn.files.has(sourceAbs)) {
|
|
417
|
+
let headSrc = spawnSync("git", ["show", `HEAD:${source}`], {
|
|
418
|
+
cwd,
|
|
419
|
+
timeout: 5e3,
|
|
420
|
+
maxBuffer: 33554432
|
|
421
|
+
});
|
|
422
|
+
!headSrc.error && headSrc.status === 0 && headSrc.stdout && (turn.files.set(sourceAbs, {
|
|
423
|
+
existed: !0,
|
|
424
|
+
content: headSrc.stdout,
|
|
425
|
+
createdByTool: !1
|
|
426
|
+
}), persistSnapshot(ctx, turn, sourceAbs));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
let head = spawnSync("git", ["show", `HEAD:${path}`], {
|
|
431
|
+
cwd,
|
|
432
|
+
timeout: 5e3,
|
|
433
|
+
maxBuffer: 33554432
|
|
434
|
+
});
|
|
435
|
+
if (head.error || head.status !== 0 || !head.stdout)
|
|
436
|
+
continue;
|
|
437
|
+
turn.files.set(abs, {
|
|
438
|
+
existed: !0,
|
|
439
|
+
content: head.stdout,
|
|
440
|
+
createdByTool: !1
|
|
441
|
+
}), persistSnapshot(ctx, turn, abs);
|
|
442
|
+
}
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function gitPorcelain(ctx, cwd) {
|
|
448
|
+
try {
|
|
449
|
+
let result = spawnSync("git", ["status", "--porcelain", "-z"], { cwd, encoding: "utf8", timeout: 5e3 });
|
|
450
|
+
if (result.error || result.status !== 0) {
|
|
451
|
+
warnOnce(ctx, "Rollback tree scope: not a git repository (or git failed) \u2014 bash side-effects won't be captured.");
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
let fields = (result.stdout ?? "").split("\0").filter((f) => f.length > 0), entries = [];
|
|
455
|
+
for (let i = 0; i < fields.length; i++) {
|
|
456
|
+
let field = fields[i], status = field.slice(0, 2), path = field.slice(3), source;
|
|
457
|
+
(status.includes("R") || status.includes("C")) && (source = fields[i + 1], i++), entries.push({ status, path, source });
|
|
458
|
+
}
|
|
459
|
+
return entries;
|
|
460
|
+
} catch {
|
|
461
|
+
warnOnce(ctx, "Rollback tree scope: not a git repository (or git failed) \u2014 bash side-effects won't be captured.");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function warnOnce(ctx, message) {
|
|
466
|
+
if (!ctx.repoWarningShown) {
|
|
467
|
+
ctx.repoWarningShown = !0;
|
|
468
|
+
try {
|
|
469
|
+
warningHandler?.(message);
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function persistSnapshot(ctx, turn, absPath) {
|
|
475
|
+
try {
|
|
476
|
+
let snap = turn.files.get(absPath);
|
|
477
|
+
if (!snap)
|
|
478
|
+
return;
|
|
479
|
+
let turnDir = getTurnDir(ctx, turn.turnIndex);
|
|
480
|
+
mkdirSync(turnDir, { recursive: !0 });
|
|
481
|
+
let manifestPath = join3(turnDir, "manifest.json"), manifest = readManifest(manifestPath), entry = { existed: snap.existed, createdByTool: snap.createdByTool };
|
|
482
|
+
if (snap.content) {
|
|
483
|
+
let snapFile = snapFileName(absPath);
|
|
484
|
+
writeFileSync(join3(turnDir, snapFile), snap.content), entry.snapFile = snapFile;
|
|
485
|
+
}
|
|
486
|
+
manifest.files[absPath] = entry, turn.cwd && (manifest.cwd = turn.cwd), writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function readManifest(path) {
|
|
491
|
+
if (!existsSync(path))
|
|
492
|
+
return { files: {} };
|
|
493
|
+
try {
|
|
494
|
+
let parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
495
|
+
if (parsed && typeof parsed == "object" && !Array.isArray(parsed)) {
|
|
496
|
+
let files = parsed.files;
|
|
497
|
+
return {
|
|
498
|
+
cwd: typeof parsed.cwd == "string" ? parsed.cwd : void 0,
|
|
499
|
+
files: files && typeof files == "object" && !Array.isArray(files) ? files : {}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return { files: {} };
|
|
503
|
+
} catch {
|
|
504
|
+
return { files: {} };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function loadPersistedTurns(ctx) {
|
|
508
|
+
try {
|
|
509
|
+
let sessionDir = join3(ROLLBACK_BASE, ctx.sessionId);
|
|
510
|
+
if (!existsSync(sessionDir))
|
|
511
|
+
return;
|
|
512
|
+
let loaded = [];
|
|
513
|
+
for (let dirName of readdirSync(sessionDir)) {
|
|
514
|
+
let match = /^turn-(\d+)$/.exec(dirName);
|
|
515
|
+
if (!match)
|
|
516
|
+
continue;
|
|
517
|
+
let turnIndex = Number(match[1]), turnDir = join3(sessionDir, dirName), manifest = readManifest(join3(turnDir, "manifest.json")), files = /* @__PURE__ */ new Map();
|
|
518
|
+
for (let [absPath, meta] of Object.entries(manifest.files)) {
|
|
519
|
+
let content;
|
|
520
|
+
if (meta.snapFile)
|
|
521
|
+
try {
|
|
522
|
+
content = readFileSync(join3(turnDir, meta.snapFile));
|
|
523
|
+
} catch {
|
|
524
|
+
content = void 0;
|
|
525
|
+
}
|
|
526
|
+
files.set(absPath, { existed: meta.existed, content, createdByTool: meta.createdByTool });
|
|
527
|
+
}
|
|
528
|
+
loaded.push({ turnIndex, cwd: manifest.cwd, files });
|
|
529
|
+
}
|
|
530
|
+
loaded.sort((a, b) => a.turnIndex - b.turnIndex), ctx.turns = loaded, ctx.currentTurnIndex = loaded.length > 0 ? loaded[loaded.length - 1].turnIndex : -1, pruneTurns(ctx);
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function getTurnDir(ctx, turnIndex) {
|
|
535
|
+
return join3(ROLLBACK_BASE, ctx.sessionId, `turn-${turnIndex}`);
|
|
536
|
+
}
|
|
537
|
+
function cleanupTurnFiles(ctx, turn) {
|
|
538
|
+
try {
|
|
539
|
+
let dir = getTurnDir(ctx, turn.turnIndex);
|
|
540
|
+
existsSync(dir) && rmSync(dir, { recursive: !0, force: !0 });
|
|
541
|
+
} catch {
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function isPathUnderRoot(absPath, root) {
|
|
545
|
+
let normRoot = normalize(root).replace(/\\$/g, ""), normPath = normalize(absPath).replace(/\\$/g, "");
|
|
546
|
+
return process.platform === "win32" && (normRoot = normRoot.toLowerCase(), normPath = normPath.toLowerCase()), normPath === normRoot || normPath.startsWith(`${normRoot}/`) || normPath.startsWith(`${normRoot}\\`);
|
|
547
|
+
}
|
|
548
|
+
function isWithinAllowedRoots(absPath, turn) {
|
|
549
|
+
return turn.cwd ? [
|
|
550
|
+
ROLLBACK_BASE,
|
|
551
|
+
join3(homedir(), CONFIG_DIR_NAME),
|
|
552
|
+
// lunr: agent settings / cron jobs.json live under the config dir
|
|
553
|
+
// lunr: simple-memory lives next to the lunr agent dir (~/.lunr/simple-memory)
|
|
554
|
+
join3(dirname(getAgentDir()), "simple-memory"),
|
|
555
|
+
resolve(turn.cwd)
|
|
556
|
+
].some((root) => isPathUnderRoot(absPath, root)) : !0;
|
|
557
|
+
}
|
|
558
|
+
function warnExternalModification(ctx, message) {
|
|
559
|
+
if (!(ctx.externalModWarningShown || !warningHandler)) {
|
|
560
|
+
ctx.externalModWarningShown = !0;
|
|
561
|
+
try {
|
|
562
|
+
warningHandler(message);
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function peekRollbackTurnsConsumed(sessionId) {
|
|
568
|
+
let ctx = getContext(sessionId);
|
|
569
|
+
if (!isRollbackEnabled(ctx.sessionId) || ctx.turns.length === 0)
|
|
570
|
+
return 0;
|
|
571
|
+
let consumed = 0;
|
|
572
|
+
for (let i = ctx.turns.length - 1; i >= 0 && (consumed++, !(ctx.turns[i].files.size > 0)); i--)
|
|
573
|
+
;
|
|
574
|
+
return consumed;
|
|
575
|
+
}
|
|
576
|
+
function rollbackLastTurn(sessionId) {
|
|
577
|
+
let ctx = getContext(sessionId), empty = { restored: [], deleted: [], turnsConsumed: 0 };
|
|
578
|
+
if (!isRollbackEnabled(ctx.sessionId))
|
|
579
|
+
return empty;
|
|
580
|
+
let turnsConsumed = 0;
|
|
581
|
+
for (; ctx.turns.length > 0 && ctx.turns[ctx.turns.length - 1].files.size === 0; ) {
|
|
582
|
+
let skipped = ctx.turns.pop();
|
|
583
|
+
skipped && (cleanupTurnFiles(ctx, skipped), turnsConsumed++);
|
|
584
|
+
}
|
|
585
|
+
if (ctx.turns.length === 0)
|
|
586
|
+
return empty;
|
|
587
|
+
turnsConsumed++;
|
|
588
|
+
let turn = ctx.turns[ctx.turns.length - 1], capture = ctx.settingsManager?.getRollbackCapture() ?? "copies", restored = [], deleted = [];
|
|
589
|
+
for (let [absPath, snap] of turn.files)
|
|
590
|
+
try {
|
|
591
|
+
if (!isWithinAllowedRoots(absPath, turn)) {
|
|
592
|
+
warnExternalModification(ctx, `Rollback skipped ${absPath}: outside the session working directory or lunR config dir.`);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (snap.existed && snap.content) {
|
|
596
|
+
if (!snap.createdByTool && existsSync(absPath) && readFileSync(absPath).equals(snap.content))
|
|
597
|
+
continue;
|
|
598
|
+
mkdirSync(dirname(absPath), { recursive: !0 }), writeFileSync(absPath, snap.content), restored.push(absPath);
|
|
599
|
+
} else snap.existed || (snap.createdByTool || capture === "hybrid" || capture === "shadow-git") && existsSync(absPath) && (unlinkSync(absPath), deleted.push(absPath));
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
return cleanupTurnFiles(ctx, turn), ctx.turns.pop(), { restored, deleted, turnsConsumed };
|
|
603
|
+
}
|
|
604
|
+
function migrateRollbackSession(oldSessionId, newSessionId) {
|
|
605
|
+
if (oldSessionId === newSessionId)
|
|
606
|
+
return;
|
|
607
|
+
let ctx = contexts.get(oldSessionId);
|
|
608
|
+
ctx && (contexts.delete(oldSessionId), ctx.sessionId = newSessionId, contexts.set(newSessionId, ctx));
|
|
609
|
+
try {
|
|
610
|
+
let oldDir = join3(ROLLBACK_BASE, oldSessionId), newDir = join3(ROLLBACK_BASE, newSessionId);
|
|
611
|
+
if (existsSync(oldDir)) {
|
|
612
|
+
existsSync(newDir) && rmSync(newDir, { recursive: !0, force: !0 });
|
|
613
|
+
try {
|
|
614
|
+
renameSync(oldDir, newDir);
|
|
615
|
+
} catch {
|
|
616
|
+
cpSync(oldDir, newDir, { recursive: !0 }), rmSync(oldDir, { recursive: !0, force: !0 });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function clearRollback(sessionId) {
|
|
623
|
+
if (sessionId === void 0) {
|
|
624
|
+
for (let ctx2 of contexts.values()) {
|
|
625
|
+
for (let turn of ctx2.turns)
|
|
626
|
+
cleanupTurnFiles(ctx2, turn);
|
|
627
|
+
try {
|
|
628
|
+
let sessionDir = join3(ROLLBACK_BASE, ctx2.sessionId);
|
|
629
|
+
existsSync(sessionDir) && rmSync(sessionDir, { recursive: !0, force: !0 });
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
contexts.clear();
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
let ctx = contexts.get(sessionId);
|
|
637
|
+
if (ctx) {
|
|
638
|
+
for (let turn of ctx.turns)
|
|
639
|
+
cleanupTurnFiles(ctx, turn);
|
|
640
|
+
try {
|
|
641
|
+
let sessionDir = join3(ROLLBACK_BASE, sessionId);
|
|
642
|
+
existsSync(sessionDir) && rmSync(sessionDir, { recursive: !0, force: !0 });
|
|
643
|
+
} catch {
|
|
644
|
+
}
|
|
645
|
+
contexts.delete(sessionId);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function snapFileName(absPath) {
|
|
649
|
+
return `${createHash("sha1").update(absPath).digest("hex")}.snap`;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// packages/coding-agent/dist/core/subagent-wait-interruption.js
|
|
653
|
+
var SUBAGENT_WAIT_INTERRUPTION_SYMBOL = /* @__PURE__ */ Symbol.for("@lunr/subagent-wait-interruption");
|
|
654
|
+
function getBridge() {
|
|
655
|
+
let globals = globalThis, existing = globals[SUBAGENT_WAIT_INTERRUPTION_SYMBOL];
|
|
656
|
+
if (existing)
|
|
657
|
+
return existing;
|
|
658
|
+
let bridge = { owners: /* @__PURE__ */ new Map() };
|
|
659
|
+
return globals[SUBAGENT_WAIT_INTERRUPTION_SYMBOL] = bridge, bridge;
|
|
660
|
+
}
|
|
661
|
+
function registerSubagentWaitInterruptionOwner(sessionId, owner) {
|
|
662
|
+
let owners = getBridge().owners;
|
|
663
|
+
return owners.set(sessionId, owner), () => {
|
|
664
|
+
owners.get(sessionId) === owner && owners.delete(sessionId);
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// packages/coding-agent/dist/core/system-prompt.js
|
|
669
|
+
function buildSystemPrompt(options) {
|
|
670
|
+
let { customPrompt, modelSlug, selectedTools, appendSystemPrompt, cwd, contextFiles: providedContextFiles, skills: providedSkills } = options, promptCwd = cwd.replace(/\\/g, "/"), appendSection = appendSystemPrompt ? `
|
|
671
|
+
|
|
672
|
+
${appendSystemPrompt}` : "", contextFiles = providedContextFiles ?? [], skills = providedSkills ?? [];
|
|
673
|
+
if (customPrompt) {
|
|
674
|
+
let prompt2 = customPrompt;
|
|
675
|
+
if (appendSection && (prompt2 += appendSection), contextFiles.length > 0) {
|
|
676
|
+
prompt2 += `
|
|
677
|
+
|
|
678
|
+
<project_context>
|
|
679
|
+
|
|
680
|
+
`, prompt2 += `Project-specific instructions and guidelines:
|
|
681
|
+
|
|
682
|
+
`;
|
|
683
|
+
for (let { path: filePath, content } of contextFiles)
|
|
684
|
+
prompt2 += `<project_instructions path="${filePath}">
|
|
685
|
+
${content}
|
|
686
|
+
</project_instructions>
|
|
687
|
+
|
|
688
|
+
`;
|
|
689
|
+
prompt2 += `</project_context>
|
|
690
|
+
`;
|
|
691
|
+
}
|
|
692
|
+
return (!selectedTools || selectedTools.includes("read")) && skills.length > 0 && (prompt2 += formatSkillsForPrompt(skills)), prompt2 += `
|
|
693
|
+
Current working directory: ${promptCwd}`, prompt2;
|
|
694
|
+
}
|
|
695
|
+
let readmePath = getReadmePath(), docsPath = getDocsPath(), examplesPath = getExamplesPath(), currentModel = modelSlug?.trim() || "no model selected", hasRead = !selectedTools || selectedTools.includes("read"), todoGuideline = !selectedTools || selectedTools.includes("todo") ? `- Use todo for meaningful multi-step work. Every update must send the complete list, with exactly one item in progress at a time.
|
|
696
|
+
` : "", prompt = `You are an expert coding assistant currently running ''${currentModel}'', operating inside lunR, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.
|
|
697
|
+
Current working directory: ''${promptCwd}''
|
|
698
|
+
|
|
699
|
+
Behavior guidelines:
|
|
700
|
+
- Default to writing no comments. Only add one if the why is non-obvious: a hidden constraint, a subtle invariant, a workaround for a specific bug, something that would surprise the reader. If removing the comment wouldn't confuse a future reader, don't write it.
|
|
701
|
+
- Keep comments up to date! When making changes, it's important to keep things in sync. An outdated comment is worse than no comment at all.
|
|
702
|
+
- Carefully consider the reversibility and blast radius of actions. Generally you can freely take local, reversible actions like editing files or running tests. But for actions that are hard to reverse, affect shared systems beyond your local environment, or could otherwise be risky or destructive, check with the user before proceeding.
|
|
703
|
+
- Orchestrate subagents with intent! Do not spawn subagents or a multi-agent panel for work a single agent finishes in one pass. Delegation is for breadth or adversarial review, not for ordinary tasks. Use subagents as a tool when there is genuine benefit to it or the user explicitly asks for it.
|
|
704
|
+
- When several agents work in parallel, state file ownership up front so they do not collide.
|
|
705
|
+
- Tests are good! Endless smoke tests, regressions tests for feature deletions, etc, much less good. Tests should be focused, not slop.
|
|
706
|
+
- Prefer editing existing files to creating new ones.
|
|
707
|
+
- When a reusable excerpt, prompt, or code fragment should be click-to-copy, wrap only that payload in a fenced Markdown block with the info string \`lunr-copy\`. Keep explanation outside the block. Use a longer backtick or tilde fence when the payload contains fenced code.
|
|
708
|
+
- Only use emojis if the user explicitly requests it.
|
|
709
|
+
- Never exfil private data on public platforms like github or any other services under any circumstances.
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
Guidelines:
|
|
713
|
+
- Use bash for file operations like ls, rg, find
|
|
714
|
+
- Use read to examine files instead of cat or sed.
|
|
715
|
+
- Use edit for precise changes (edits[].oldText must match exactly)
|
|
716
|
+
- When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls
|
|
717
|
+
- Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.
|
|
718
|
+
- Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions.
|
|
719
|
+
- Use write only for new files or complete rewrites.
|
|
720
|
+
${todoGuideline}- Use subagents for independent parallel work, specialist analysis, or substantial research. Subagents start with fresh sessions. Use intercom instead to coordinate with an existing lunR session.
|
|
721
|
+
- Resume a subagent only when its work is unfinished or the next task genuinely needs the context it built up. Otherwise launch a new subagent.
|
|
722
|
+
- Use ast_search for structural code matches.
|
|
723
|
+
- Use cron only when the user asks to schedule or manage unattended prompts.
|
|
724
|
+
- For gateway work, inspect with \`lunr gateway status\` or \`lunr gateway doctor\`, configure with \`lunr gateway setup\`, and run \`lunr gateway start\` or \`lunr gateway stop\` only with user approval. Test by asking the user to send \`/whoami\` or a normal message to the bot; there is no arbitrary outbound send command. Keep bot tokens out of commands and output. See ${docsPath}/features.md for pairing, logs, and service details.
|
|
725
|
+
- Memory stores established, durable facts and stable preferences. Do not store behavior instructions, transient task details, transcripts, guesses, or secrets. Change memory only with the memory tools.
|
|
726
|
+
- ~/.lunr/agent/agents/ contains optional global and per-model AGENTS.md instructions written by the user. Never modify this tree, including through shell commands.
|
|
727
|
+
- Use web search when information is current, uncertain, externally referenced, or research-heavy, and cite the sources used.
|
|
728
|
+
- Use MCP only for capabilities exposed by configured MCP servers. Call native lunR tools directly.
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
lunR documentation (read only when the user asks about lunR itself, or the task concerns lunR itself, its SDK, extensions, themes, skills, or TUI):
|
|
732
|
+
- README: ${readmePath}
|
|
733
|
+
- Documentation: ${docsPath}
|
|
734
|
+
- Examples: ${examplesPath}
|
|
735
|
+
- Always read lunR .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;
|
|
736
|
+
if (appendSection && (prompt += appendSection), contextFiles.length > 0) {
|
|
737
|
+
prompt += `
|
|
738
|
+
|
|
739
|
+
<project_context>
|
|
740
|
+
|
|
741
|
+
`, prompt += `Project-specific instructions and guidelines:
|
|
742
|
+
|
|
743
|
+
`;
|
|
744
|
+
for (let { path: filePath, content } of contextFiles)
|
|
745
|
+
prompt += `<project_instructions path="${filePath}">
|
|
746
|
+
${content}
|
|
747
|
+
</project_instructions>
|
|
748
|
+
|
|
749
|
+
`;
|
|
750
|
+
prompt += `</project_context>
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
return hasRead && skills.length > 0 && (prompt += formatSkillsForPrompt(skills)), prompt;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// packages/coding-agent/dist/core/agent-session.js
|
|
757
|
+
function parseSkillBlock(text) {
|
|
758
|
+
let match = text.match(/^<skill name="([^"]+)" location="([^"]+)">\n([\s\S]*?)\n<\/skill>(?:\n\n([\s\S]+))?$/);
|
|
759
|
+
return match ? {
|
|
760
|
+
name: match[1],
|
|
761
|
+
location: match[2],
|
|
762
|
+
content: match[3],
|
|
763
|
+
userMessage: match[4]?.trim() || void 0
|
|
764
|
+
} : null;
|
|
765
|
+
}
|
|
766
|
+
function withoutDeletedHeaders(headers) {
|
|
767
|
+
return headers ? Object.fromEntries(Object.entries(headers).filter((entry) => entry[1] !== null)) : void 0;
|
|
768
|
+
}
|
|
769
|
+
function estimateMessagesTokens(messages) {
|
|
770
|
+
let tokens = 0;
|
|
771
|
+
for (let message of messages)
|
|
772
|
+
tokens += estimateTokens(message);
|
|
773
|
+
return tokens;
|
|
774
|
+
}
|
|
775
|
+
var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"], AgentSession = class {
|
|
776
|
+
agent;
|
|
777
|
+
sessionManager;
|
|
778
|
+
settingsManager;
|
|
779
|
+
_scopedModels;
|
|
780
|
+
// Event subscription state
|
|
781
|
+
_unsubscribeAgent;
|
|
782
|
+
_eventListeners = [];
|
|
783
|
+
_isAgentRunActive = !1;
|
|
784
|
+
_idleWaitPromise;
|
|
785
|
+
_resolveIdleWait;
|
|
786
|
+
_activePromptRun;
|
|
787
|
+
_waitPromptHandoff;
|
|
788
|
+
_waitPromptAcceptance = Promise.resolve();
|
|
789
|
+
_waitPromptGeneration = 0;
|
|
790
|
+
_unregisterSubagentWaitInterruptionOwners = [];
|
|
791
|
+
/** Tracks pending steering messages for UI display. Removed when delivered. */
|
|
792
|
+
_steeringMessages = [];
|
|
793
|
+
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
|
794
|
+
_followUpMessages = [];
|
|
795
|
+
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
796
|
+
_pendingNextTurnMessages = [];
|
|
797
|
+
// Compaction state
|
|
798
|
+
_compactionAbortController = void 0;
|
|
799
|
+
_manualCompactionPromise = void 0;
|
|
800
|
+
_autoCompactionAbortController = void 0;
|
|
801
|
+
_overflowRecoveryAttempted = !1;
|
|
802
|
+
// Branch summarization state
|
|
803
|
+
_branchSummaryAbortController = void 0;
|
|
804
|
+
// Retry state
|
|
805
|
+
_retryAbortController = void 0;
|
|
806
|
+
_retryAttempt = 0;
|
|
807
|
+
// Bash execution state
|
|
808
|
+
_bashAbortController = void 0;
|
|
809
|
+
_pendingBashMessages = [];
|
|
810
|
+
// Extension system
|
|
811
|
+
_extensionRunner;
|
|
812
|
+
_turnIndex = 0;
|
|
813
|
+
_resourceLoader;
|
|
814
|
+
_customTools;
|
|
815
|
+
_baseToolDefinitions = /* @__PURE__ */ new Map();
|
|
816
|
+
_cwd;
|
|
817
|
+
_agentDir;
|
|
818
|
+
_extensionRunnerRef;
|
|
819
|
+
_initialActiveToolNames;
|
|
820
|
+
_allowedToolNames;
|
|
821
|
+
_excludedToolNames;
|
|
822
|
+
_baseToolsOverride;
|
|
823
|
+
_sessionStartEvent;
|
|
824
|
+
_extensionUIContext;
|
|
825
|
+
_extensionMode = "print";
|
|
826
|
+
_extensionCommandContextActions;
|
|
827
|
+
_extensionAbortHandler;
|
|
828
|
+
_extensionShutdownHandler;
|
|
829
|
+
_extensionErrorListener;
|
|
830
|
+
_extensionErrorUnsubscriber;
|
|
831
|
+
_modelRuntime;
|
|
832
|
+
// Tool registry for extension getTools/setTools
|
|
833
|
+
_toolRegistry = /* @__PURE__ */ new Map();
|
|
834
|
+
_toolDefinitions = /* @__PURE__ */ new Map();
|
|
835
|
+
_toolPromptSnippets = /* @__PURE__ */ new Map();
|
|
836
|
+
_toolPromptGuidelines = /* @__PURE__ */ new Map();
|
|
837
|
+
// Base system prompt (without extension appends) - used to apply fresh appends each turn
|
|
838
|
+
_baseSystemPrompt = "";
|
|
839
|
+
_baseSystemPromptOptions;
|
|
840
|
+
_systemPromptOverride;
|
|
841
|
+
// lunr: core-owned system-prompt append (plan mode addendum) applied on top of base/override
|
|
842
|
+
_systemPromptAppend;
|
|
843
|
+
// lunr: core-owned tool-call gates (plan mode) checked before extension tool_call handlers
|
|
844
|
+
_toolCallGates = [];
|
|
845
|
+
constructor(config) {
|
|
846
|
+
this.agent = config.agent, this.sessionManager = config.sessionManager, this.settingsManager = config.settingsManager, this._scopedModels = config.scopedModels ?? [], this._resourceLoader = config.resourceLoader, this._customTools = config.customTools ?? [], this._cwd = config.cwd, this._agentDir = config.agentDir ?? getAgentDir(), this._modelRuntime = config.modelRuntime, this._syncOpenAIServiceTier(), this._extensionRunnerRef = config.extensionRunnerRef, this._initialActiveToolNames = config.initialActiveToolNames, this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : void 0, this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : void 0, this._baseToolsOverride = config.baseToolsOverride, this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
|
847
|
+
let waitInterruptionOwner = { register: () => this._registerSubagentWaitInterruption() };
|
|
848
|
+
for (let identity of new Set([this.sessionId, this.sessionFile].filter((value) => !!value)))
|
|
849
|
+
this._unregisterSubagentWaitInterruptionOwners.push(registerSubagentWaitInterruptionOwner(identity, waitInterruptionOwner));
|
|
850
|
+
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent), this._installAgentToolHooks(), this._installAgentNextTurnRefresh(), this._buildRuntime({
|
|
851
|
+
activeToolNames: this._initialActiveToolNames,
|
|
852
|
+
includeAllExtensionTools: !0
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
get modelRuntime() {
|
|
856
|
+
return this._modelRuntime;
|
|
857
|
+
}
|
|
858
|
+
get openaiFastMode() {
|
|
859
|
+
return this.settingsManager.getOpenAIFastMode();
|
|
860
|
+
}
|
|
861
|
+
setOpenAIFastMode(enabled) {
|
|
862
|
+
this.settingsManager.setOpenAIFastMode(enabled), this._syncOpenAIServiceTier();
|
|
863
|
+
}
|
|
864
|
+
_syncOpenAIServiceTier() {
|
|
865
|
+
this.agent.serviceTier = this.agent.state.model.provider === "openai-codex" && this.settingsManager.getOpenAIFastMode() ? "fast" : void 0;
|
|
866
|
+
}
|
|
867
|
+
async _getRequiredRequestAuth(model) {
|
|
868
|
+
let result;
|
|
869
|
+
try {
|
|
870
|
+
result = await this._modelRuntime.getAuth(model);
|
|
871
|
+
} catch (error) {
|
|
872
|
+
let cause = error instanceof Error ? error.cause : void 0;
|
|
873
|
+
throw cause instanceof Error && cause.message === "authHeader requires a resolved API key" ? new Error(formatNoApiKeyFoundMessage(model.provider)) : error;
|
|
874
|
+
}
|
|
875
|
+
if (result?.auth.apiKey)
|
|
876
|
+
return {
|
|
877
|
+
apiKey: result.auth.apiKey,
|
|
878
|
+
headers: withoutDeletedHeaders(result.auth.headers),
|
|
879
|
+
env: result.env
|
|
880
|
+
};
|
|
881
|
+
throw this._modelRuntime.isUsingOAuth(model.provider) ? new Error(`Authentication failed for "${model.provider}". Credentials may have expired or network is unavailable. Run '/login ${model.provider}' to re-authenticate.`) : new Error(formatNoApiKeyFoundMessage(model.provider));
|
|
882
|
+
}
|
|
883
|
+
async _getSummarizationRequestAuth(model) {
|
|
884
|
+
if (this.agent.streamFn === streamSimple)
|
|
885
|
+
return this._getRequiredRequestAuth(model);
|
|
886
|
+
try {
|
|
887
|
+
let result = await this._modelRuntime.getAuth(model);
|
|
888
|
+
return result ? { apiKey: result.auth.apiKey, headers: withoutDeletedHeaders(result.auth.headers), env: result.env } : {};
|
|
889
|
+
} catch {
|
|
890
|
+
return {};
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Install tool hooks once on the Agent instance.
|
|
895
|
+
*
|
|
896
|
+
* The callbacks read `this._extensionRunner` at execution time, so extension reload swaps in the
|
|
897
|
+
* new runner without reinstalling hooks. Extension-specific tool wrappers are still used to adapt
|
|
898
|
+
* registered tool execution to the extension context. Tool call and tool result interception now
|
|
899
|
+
* happens here instead of in wrappers.
|
|
900
|
+
*/
|
|
901
|
+
_installAgentToolHooks() {
|
|
902
|
+
this.agent.beforeToolCall = async ({ toolCall, args, assistantMessage }) => {
|
|
903
|
+
let permBlock = await gateToolCall(toolCall.name, args, this._cwd, this.sessionId, {
|
|
904
|
+
confirmLargeSubagentLaunches: this.settingsManager.getConfirmLargeSubagentLaunches(),
|
|
905
|
+
assistantMessage
|
|
906
|
+
});
|
|
907
|
+
if (permBlock)
|
|
908
|
+
return { block: !0, reason: permBlock.reason };
|
|
909
|
+
try {
|
|
910
|
+
let toolName = toolCall.name, argPath = args && typeof args == "object" && "path" in args ? resolve2(this._cwd, String(args.path)) : void 0, pathsToSnapshot = [];
|
|
911
|
+
if (toolName === "edit" || toolName === "write")
|
|
912
|
+
argPath && pathsToSnapshot.push(argPath);
|
|
913
|
+
else if (toolName === "memory_add" || toolName === "memory_remove")
|
|
914
|
+
pathsToSnapshot.push(join4(dirname2(getAgentDir()), "simple-memory", "memory.md"));
|
|
915
|
+
else if (toolName === "cron" && args && typeof args == "object") {
|
|
916
|
+
let action = String(args.action ?? "");
|
|
917
|
+
(action === "create" || action === "update" || action === "remove") && pathsToSnapshot.push(join4(getAgentDir(), "cron", "jobs.json"));
|
|
918
|
+
}
|
|
919
|
+
for (let p of pathsToSnapshot)
|
|
920
|
+
rollbackSnapshotBeforeWrite(p, this.sessionId);
|
|
921
|
+
} catch {
|
|
922
|
+
}
|
|
923
|
+
for (let gate of this._toolCallGates) {
|
|
924
|
+
let gateResult = gate(toolCall.name, args);
|
|
925
|
+
if (gateResult?.block)
|
|
926
|
+
return { block: !0, reason: gateResult.reason };
|
|
927
|
+
}
|
|
928
|
+
let runner = this._extensionRunner;
|
|
929
|
+
if (runner.hasHandlers("tool_call"))
|
|
930
|
+
try {
|
|
931
|
+
return await runner.emitToolCall({
|
|
932
|
+
type: "tool_call",
|
|
933
|
+
toolName: toolCall.name,
|
|
934
|
+
toolCallId: toolCall.id,
|
|
935
|
+
input: args
|
|
936
|
+
});
|
|
937
|
+
} catch (err) {
|
|
938
|
+
throw err instanceof Error ? err : new Error(`Extension failed, blocking execution: ${String(err)}`);
|
|
939
|
+
}
|
|
940
|
+
}, this.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {
|
|
941
|
+
let runner = this._extensionRunner;
|
|
942
|
+
if (!runner.hasHandlers("tool_result"))
|
|
943
|
+
return;
|
|
944
|
+
let hookResult = await runner.emitToolResult({
|
|
945
|
+
type: "tool_result",
|
|
946
|
+
toolName: toolCall.name,
|
|
947
|
+
toolCallId: toolCall.id,
|
|
948
|
+
input: args,
|
|
949
|
+
content: result.content,
|
|
950
|
+
details: result.details,
|
|
951
|
+
isError
|
|
952
|
+
});
|
|
953
|
+
if (hookResult)
|
|
954
|
+
return {
|
|
955
|
+
content: hookResult.content,
|
|
956
|
+
details: hookResult.details,
|
|
957
|
+
isError: hookResult.isError ?? isError
|
|
958
|
+
};
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
_installAgentNextTurnRefresh() {
|
|
962
|
+
let previousPrepareNextTurnWithContext = this.agent.prepareNextTurnWithContext ?? (this.agent.prepareNextTurn ? async (_turn, signal) => await this.agent.prepareNextTurn?.(signal) : void 0);
|
|
963
|
+
this.agent.prepareNextTurnWithContext = async (turn, signal) => {
|
|
964
|
+
let previousSnapshot = await previousPrepareNextTurnWithContext?.(turn, signal), previousContext = previousSnapshot?.context ?? turn.context, model = this.model, settings = this.settingsManager.getCompactionSettings();
|
|
965
|
+
if (model?.provider === "openai-codex" && settings.enabled && shouldCompact(estimateContextTokens(previousContext.messages).tokens, model.contextWindow, settings)) {
|
|
966
|
+
let previousCompactionId = getLatestCompactionEntry(this.sessionManager.getBranch())?.id;
|
|
967
|
+
await this._runAutoCompaction("threshold", !0);
|
|
968
|
+
let currentCompaction = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
969
|
+
currentCompaction && currentCompaction.id !== previousCompactionId && (previousContext = { ...previousContext, messages: this.agent.state.messages.slice() });
|
|
970
|
+
}
|
|
971
|
+
return {
|
|
972
|
+
...previousSnapshot,
|
|
973
|
+
context: {
|
|
974
|
+
...previousContext,
|
|
975
|
+
systemPrompt: this._withSystemPromptAppend(this._systemPromptOverride ?? this._baseSystemPrompt),
|
|
976
|
+
tools: this.agent.state.tools.slice()
|
|
977
|
+
},
|
|
978
|
+
model: this.agent.state.model,
|
|
979
|
+
thinkingLevel: this.agent.state.thinkingLevel
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
// =========================================================================
|
|
984
|
+
// Event Subscription
|
|
985
|
+
// =========================================================================
|
|
986
|
+
/** Emit an event to all listeners */
|
|
987
|
+
_emit(event) {
|
|
988
|
+
for (let l of this._eventListeners)
|
|
989
|
+
l(event);
|
|
990
|
+
}
|
|
991
|
+
_emitQueueUpdate() {
|
|
992
|
+
this._emit({
|
|
993
|
+
type: "queue_update",
|
|
994
|
+
steering: [...this._steeringMessages],
|
|
995
|
+
followUp: [...this._followUpMessages]
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
_getIdleWaitPromise() {
|
|
999
|
+
return this._idleWaitPromise || (this._idleWaitPromise = new Promise((resolve3) => {
|
|
1000
|
+
this._resolveIdleWait = resolve3;
|
|
1001
|
+
})), this._idleWaitPromise;
|
|
1002
|
+
}
|
|
1003
|
+
_resolveIdleWaitIfIdle() {
|
|
1004
|
+
if (this._isAgentRunActive || !this._resolveIdleWait)
|
|
1005
|
+
return;
|
|
1006
|
+
let resolve3 = this._resolveIdleWait;
|
|
1007
|
+
this._idleWaitPromise = void 0, this._resolveIdleWait = void 0, resolve3();
|
|
1008
|
+
}
|
|
1009
|
+
async _emitAgentSettled() {
|
|
1010
|
+
this._isAgentRunActive = !1;
|
|
1011
|
+
try {
|
|
1012
|
+
await this._extensionRunner.emit({ type: "agent_settled" }), this._emit({ type: "agent_settled" });
|
|
1013
|
+
} finally {
|
|
1014
|
+
this._resolveIdleWaitIfIdle();
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
// Track last assistant message for auto-compaction check
|
|
1018
|
+
_lastAssistantMessage = void 0;
|
|
1019
|
+
/** Internal handler for agent events - shared by subscribe and reconnect */
|
|
1020
|
+
_handleAgentEvent = async (event) => {
|
|
1021
|
+
if (event.type === "message_start" && event.message.role === "user") {
|
|
1022
|
+
this._overflowRecoveryAttempted = !1;
|
|
1023
|
+
let messageText = this._getUserMessageText(event.message);
|
|
1024
|
+
if (messageText) {
|
|
1025
|
+
let steeringIndex = this._steeringMessages.indexOf(messageText);
|
|
1026
|
+
if (steeringIndex !== -1)
|
|
1027
|
+
this._steeringMessages.splice(steeringIndex, 1), this._emitQueueUpdate();
|
|
1028
|
+
else {
|
|
1029
|
+
let followUpIndex = this._followUpMessages.indexOf(messageText);
|
|
1030
|
+
followUpIndex !== -1 && (this._followUpMessages.splice(followUpIndex, 1), this._emitQueueUpdate());
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (await this._emitExtensionEvent(event), this._emit(event.type === "agent_end" ? { ...event, willRetry: this._willRetryAfterAgentEnd(event) } : event), event.type === "message_end" && (event.message.role === "custom" ? this.sessionManager.appendCustomMessageEntry(event.message.customType, event.message.content, event.message.display, event.message.details) : (event.message.role === "user" || event.message.role === "assistant" || event.message.role === "toolResult") && this.sessionManager.appendMessage(event.message), event.message.role === "assistant")) {
|
|
1035
|
+
this._lastAssistantMessage = event.message;
|
|
1036
|
+
let assistantMsg = event.message;
|
|
1037
|
+
assistantMsg.stopReason !== "error" && (this._overflowRecoveryAttempted = !1), assistantMsg.stopReason !== "error" && this._retryAttempt > 0 && (this._emit({
|
|
1038
|
+
type: "auto_retry_end",
|
|
1039
|
+
success: !0,
|
|
1040
|
+
attempt: this._retryAttempt
|
|
1041
|
+
}), this._retryAttempt = 0);
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
_willRetryAfterAgentEnd(event) {
|
|
1045
|
+
let settings = this.settingsManager.getRetrySettings();
|
|
1046
|
+
if (!settings.enabled || this._retryAttempt >= settings.maxRetries)
|
|
1047
|
+
return !1;
|
|
1048
|
+
for (let i = event.messages.length - 1; i >= 0; i--) {
|
|
1049
|
+
let message = event.messages[i];
|
|
1050
|
+
if (message.role === "assistant")
|
|
1051
|
+
return this._isRetryableError(message);
|
|
1052
|
+
}
|
|
1053
|
+
return !1;
|
|
1054
|
+
}
|
|
1055
|
+
/** Extract text content from a message */
|
|
1056
|
+
_getUserMessageText(message) {
|
|
1057
|
+
if (message.role !== "user")
|
|
1058
|
+
return "";
|
|
1059
|
+
let content = message.content;
|
|
1060
|
+
return typeof content == "string" ? content : content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
1061
|
+
}
|
|
1062
|
+
/** Find the last assistant message in agent state (including aborted ones) */
|
|
1063
|
+
_findLastAssistantMessage() {
|
|
1064
|
+
let messages = this.agent.state.messages;
|
|
1065
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1066
|
+
let msg = messages[i];
|
|
1067
|
+
if (msg.role === "assistant")
|
|
1068
|
+
return msg;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
_replaceMessageInPlace(target, replacement) {
|
|
1072
|
+
if (target === replacement)
|
|
1073
|
+
return;
|
|
1074
|
+
let targetRecord = target;
|
|
1075
|
+
for (let key of Object.keys(targetRecord))
|
|
1076
|
+
delete targetRecord[key];
|
|
1077
|
+
Object.assign(targetRecord, replacement);
|
|
1078
|
+
}
|
|
1079
|
+
/** Emit extension events based on agent events */
|
|
1080
|
+
async _emitExtensionEvent(event) {
|
|
1081
|
+
if (event.type === "agent_start")
|
|
1082
|
+
this._turnIndex = 0, await this._extensionRunner.emit({ type: "agent_start" });
|
|
1083
|
+
else if (event.type === "agent_end")
|
|
1084
|
+
await this._extensionRunner.emit({ type: "agent_end", messages: event.messages });
|
|
1085
|
+
else if (event.type === "turn_start") {
|
|
1086
|
+
let extensionEvent = {
|
|
1087
|
+
type: "turn_start",
|
|
1088
|
+
turnIndex: this._turnIndex,
|
|
1089
|
+
timestamp: Date.now()
|
|
1090
|
+
};
|
|
1091
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
1092
|
+
} else if (event.type === "turn_end") {
|
|
1093
|
+
let extensionEvent = {
|
|
1094
|
+
type: "turn_end",
|
|
1095
|
+
turnIndex: this._turnIndex,
|
|
1096
|
+
message: event.message,
|
|
1097
|
+
toolResults: event.toolResults
|
|
1098
|
+
};
|
|
1099
|
+
await this._extensionRunner.emit(extensionEvent), this._turnIndex++;
|
|
1100
|
+
} else if (event.type === "message_start") {
|
|
1101
|
+
let extensionEvent = {
|
|
1102
|
+
type: "message_start",
|
|
1103
|
+
message: event.message
|
|
1104
|
+
};
|
|
1105
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
1106
|
+
} else if (event.type === "message_update") {
|
|
1107
|
+
let extensionEvent = {
|
|
1108
|
+
type: "message_update",
|
|
1109
|
+
message: event.message,
|
|
1110
|
+
assistantMessageEvent: event.assistantMessageEvent
|
|
1111
|
+
};
|
|
1112
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
1113
|
+
} else if (event.type === "message_end") {
|
|
1114
|
+
let extensionEvent = {
|
|
1115
|
+
type: "message_end",
|
|
1116
|
+
message: event.message
|
|
1117
|
+
}, replacement = await this._extensionRunner.emitMessageEnd(extensionEvent);
|
|
1118
|
+
if (replacement) {
|
|
1119
|
+
let normalized = (replacement.role === "user" || replacement.role === "assistant" || replacement.role === "toolResult" || replacement.role === "custom") && replacement.content == null ? { ...replacement, content: [] } : replacement;
|
|
1120
|
+
this._replaceMessageInPlace(event.message, normalized);
|
|
1121
|
+
}
|
|
1122
|
+
} else if (event.type === "tool_execution_start") {
|
|
1123
|
+
let extensionEvent = {
|
|
1124
|
+
type: "tool_execution_start",
|
|
1125
|
+
toolCallId: event.toolCallId,
|
|
1126
|
+
toolName: event.toolName,
|
|
1127
|
+
args: event.args
|
|
1128
|
+
};
|
|
1129
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
1130
|
+
} else if (event.type === "tool_execution_update") {
|
|
1131
|
+
let extensionEvent = {
|
|
1132
|
+
type: "tool_execution_update",
|
|
1133
|
+
toolCallId: event.toolCallId,
|
|
1134
|
+
toolName: event.toolName,
|
|
1135
|
+
args: event.args,
|
|
1136
|
+
partialResult: event.partialResult
|
|
1137
|
+
};
|
|
1138
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
1139
|
+
} else if (event.type === "tool_execution_end") {
|
|
1140
|
+
let extensionEvent = {
|
|
1141
|
+
type: "tool_execution_end",
|
|
1142
|
+
toolCallId: event.toolCallId,
|
|
1143
|
+
toolName: event.toolName,
|
|
1144
|
+
result: event.result,
|
|
1145
|
+
isError: event.isError
|
|
1146
|
+
};
|
|
1147
|
+
await this._extensionRunner.emit(extensionEvent);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Subscribe to agent events.
|
|
1152
|
+
* Session persistence is handled internally (saves messages on message_end).
|
|
1153
|
+
* Multiple listeners can be added. Returns unsubscribe function for this listener.
|
|
1154
|
+
*/
|
|
1155
|
+
subscribe(listener) {
|
|
1156
|
+
return this._eventListeners.push(listener), () => {
|
|
1157
|
+
let index = this._eventListeners.indexOf(listener);
|
|
1158
|
+
index !== -1 && this._eventListeners.splice(index, 1);
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Temporarily disconnect from agent events.
|
|
1163
|
+
* User listeners are preserved and will receive events again after resubscribe().
|
|
1164
|
+
* Used internally during operations that need to pause event processing.
|
|
1165
|
+
*/
|
|
1166
|
+
_disconnectFromAgent() {
|
|
1167
|
+
this._unsubscribeAgent && (this._unsubscribeAgent(), this._unsubscribeAgent = void 0);
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Reconnect to agent events after _disconnectFromAgent().
|
|
1171
|
+
* Preserves all existing listeners.
|
|
1172
|
+
*/
|
|
1173
|
+
_reconnectToAgent() {
|
|
1174
|
+
this._unsubscribeAgent || (this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent));
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Remove all listeners and disconnect from agent.
|
|
1178
|
+
* Call this when completely done with the session.
|
|
1179
|
+
*/
|
|
1180
|
+
transferring = !1;
|
|
1181
|
+
setTransferring(value) {
|
|
1182
|
+
this.transferring = value, value && this._invalidateWaitPromptHandoffs("Session transfer interrupted the pending prompt.");
|
|
1183
|
+
}
|
|
1184
|
+
assertCanStartWork() {
|
|
1185
|
+
if (this.sessionManager.assertWritable(), this.transferring)
|
|
1186
|
+
throw new Error("Session transfer is in progress. Wait or reclaim the session.");
|
|
1187
|
+
}
|
|
1188
|
+
dispose() {
|
|
1189
|
+
this._invalidateWaitPromptHandoffs("Session closed before the pending prompt could run.");
|
|
1190
|
+
for (let unregister of this._unregisterSubagentWaitInterruptionOwners.splice(0))
|
|
1191
|
+
unregister();
|
|
1192
|
+
try {
|
|
1193
|
+
this.abortRetry(), this.abortCompaction(), this.abortBranchSummary(), this.abortBash(), this.agent.abort();
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload()."), this._disconnectFromAgent(), this._eventListeners = [], cleanupSessionResources(this.sessionId), this.sessionManager.dispose();
|
|
1197
|
+
}
|
|
1198
|
+
// =========================================================================
|
|
1199
|
+
// Read-only State Access
|
|
1200
|
+
// =========================================================================
|
|
1201
|
+
/** Full agent state */
|
|
1202
|
+
get state() {
|
|
1203
|
+
return this.agent.state;
|
|
1204
|
+
}
|
|
1205
|
+
/** Current model (may be undefined if not yet selected) */
|
|
1206
|
+
get model() {
|
|
1207
|
+
return this.agent.state.model;
|
|
1208
|
+
}
|
|
1209
|
+
/** Current thinking level */
|
|
1210
|
+
get thinkingLevel() {
|
|
1211
|
+
return this.agent.state.thinkingLevel;
|
|
1212
|
+
}
|
|
1213
|
+
/** Whether the session is currently processing an agent run or post-run continuation. */
|
|
1214
|
+
get isStreaming() {
|
|
1215
|
+
return this._isAgentRunActive;
|
|
1216
|
+
}
|
|
1217
|
+
/** Whether the session has no active agent run, retry, auto-compaction, or queued continuation. */
|
|
1218
|
+
get isIdle() {
|
|
1219
|
+
return !this._isAgentRunActive;
|
|
1220
|
+
}
|
|
1221
|
+
/** Whether normal Enter is still being reserved for an interrupted wait handoff. */
|
|
1222
|
+
get isWaitPromptHandoffActive() {
|
|
1223
|
+
return this._waitPromptHandoff?.accepting === !0;
|
|
1224
|
+
}
|
|
1225
|
+
/** Current effective system prompt (includes any per-turn extension modifications) */
|
|
1226
|
+
get systemPrompt() {
|
|
1227
|
+
return this.agent.state.systemPrompt;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* lunr: register a core-owned tool-call gate (plan mode). Gates run before extension
|
|
1231
|
+
* tool_call handlers and even when no extension handles tool_call. Returns an
|
|
1232
|
+
* unsubscribe function.
|
|
1233
|
+
*/
|
|
1234
|
+
addToolCallGate(gate) {
|
|
1235
|
+
return this._toolCallGates.push(gate), () => {
|
|
1236
|
+
let index = this._toolCallGates.indexOf(gate);
|
|
1237
|
+
index >= 0 && this._toolCallGates.splice(index, 1);
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* lunr: append (or clear, with undefined) a core-owned section on the effective system
|
|
1242
|
+
* prompt. Applied on top of the base prompt and any per-turn extension override.
|
|
1243
|
+
*/
|
|
1244
|
+
setSystemPromptAppend(text) {
|
|
1245
|
+
let next = text && text.trim().length > 0 ? text : void 0;
|
|
1246
|
+
next !== this._systemPromptAppend && (this._systemPromptAppend = next, this.agent.state.systemPrompt = this._withSystemPromptAppend(this._systemPromptOverride ?? this._baseSystemPrompt));
|
|
1247
|
+
}
|
|
1248
|
+
_withSystemPromptAppend(prompt) {
|
|
1249
|
+
return this._systemPromptAppend ? `${prompt}
|
|
1250
|
+
|
|
1251
|
+
${this._systemPromptAppend}` : prompt;
|
|
1252
|
+
}
|
|
1253
|
+
/** Current retry attempt (0 if not retrying) */
|
|
1254
|
+
get retryAttempt() {
|
|
1255
|
+
return this._retryAttempt;
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Get the names of currently active tools.
|
|
1259
|
+
* Returns the names of tools currently set on the agent.
|
|
1260
|
+
*/
|
|
1261
|
+
getActiveToolNames() {
|
|
1262
|
+
return this.agent.state.tools.map((t) => t.name);
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Get all configured tools with name, description, parameter schema, prompt guidelines, and source metadata.
|
|
1266
|
+
*/
|
|
1267
|
+
getAllTools() {
|
|
1268
|
+
return Array.from(this._toolDefinitions.values()).map(({ definition, sourceInfo }) => ({
|
|
1269
|
+
name: definition.name,
|
|
1270
|
+
description: definition.description,
|
|
1271
|
+
parameters: definition.parameters,
|
|
1272
|
+
promptGuidelines: definition.promptGuidelines,
|
|
1273
|
+
sourceInfo
|
|
1274
|
+
}));
|
|
1275
|
+
}
|
|
1276
|
+
getToolDefinition(name) {
|
|
1277
|
+
return this._toolDefinitions.get(name)?.definition;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Set active tools by name.
|
|
1281
|
+
* Only tools in the registry can be enabled. Unknown tool names are ignored.
|
|
1282
|
+
* Also rebuilds the system prompt to reflect the new tool set.
|
|
1283
|
+
* Changes take effect on the next agent turn.
|
|
1284
|
+
*/
|
|
1285
|
+
setActiveToolsByName(toolNames) {
|
|
1286
|
+
let tools = [], validToolNames = [];
|
|
1287
|
+
for (let name of toolNames) {
|
|
1288
|
+
let tool = this._toolRegistry.get(name);
|
|
1289
|
+
tool && (tools.push(tool), validToolNames.push(name));
|
|
1290
|
+
}
|
|
1291
|
+
this.agent.state.tools = tools, this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames), this.agent.state.systemPrompt = this._withSystemPromptAppend(this._systemPromptOverride ?? this._baseSystemPrompt);
|
|
1292
|
+
}
|
|
1293
|
+
/** Rebuild the registry after a settings-backed feature changes availability. */
|
|
1294
|
+
refreshToolRegistry() {
|
|
1295
|
+
this._refreshToolRegistry();
|
|
1296
|
+
}
|
|
1297
|
+
/** Whether compaction or branch summarization is currently running */
|
|
1298
|
+
get isCompacting() {
|
|
1299
|
+
return this._autoCompactionAbortController !== void 0 || this._compactionAbortController !== void 0 || this._branchSummaryAbortController !== void 0;
|
|
1300
|
+
}
|
|
1301
|
+
/** All messages including custom types like BashExecutionMessage */
|
|
1302
|
+
get messages() {
|
|
1303
|
+
return this.agent.state.messages;
|
|
1304
|
+
}
|
|
1305
|
+
/** Current steering mode */
|
|
1306
|
+
get steeringMode() {
|
|
1307
|
+
return this.agent.steeringMode;
|
|
1308
|
+
}
|
|
1309
|
+
/** Current follow-up mode */
|
|
1310
|
+
get followUpMode() {
|
|
1311
|
+
return this.agent.followUpMode;
|
|
1312
|
+
}
|
|
1313
|
+
/** Current session file path, or undefined if sessions are disabled */
|
|
1314
|
+
get sessionFile() {
|
|
1315
|
+
return this.sessionManager.getSessionFile();
|
|
1316
|
+
}
|
|
1317
|
+
/** Current session ID */
|
|
1318
|
+
get sessionId() {
|
|
1319
|
+
return this.sessionManager.getSessionId();
|
|
1320
|
+
}
|
|
1321
|
+
/** Current session display name, if set */
|
|
1322
|
+
get sessionName() {
|
|
1323
|
+
return this.sessionManager.getSessionName();
|
|
1324
|
+
}
|
|
1325
|
+
/** Scoped models for cycling (from --models flag) */
|
|
1326
|
+
get scopedModels() {
|
|
1327
|
+
return this._scopedModels;
|
|
1328
|
+
}
|
|
1329
|
+
/** Update scoped models for cycling */
|
|
1330
|
+
setScopedModels(scopedModels) {
|
|
1331
|
+
this._scopedModels = scopedModels;
|
|
1332
|
+
}
|
|
1333
|
+
/** File-based prompt templates */
|
|
1334
|
+
get promptTemplates() {
|
|
1335
|
+
return this._resourceLoader.getPrompts().prompts;
|
|
1336
|
+
}
|
|
1337
|
+
_normalizePromptSnippet(text) {
|
|
1338
|
+
if (!text)
|
|
1339
|
+
return;
|
|
1340
|
+
let oneLine = text.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1341
|
+
return oneLine.length > 0 ? oneLine : void 0;
|
|
1342
|
+
}
|
|
1343
|
+
_normalizePromptGuidelines(guidelines) {
|
|
1344
|
+
if (!guidelines || guidelines.length === 0)
|
|
1345
|
+
return [];
|
|
1346
|
+
let unique = /* @__PURE__ */ new Set();
|
|
1347
|
+
for (let guideline of guidelines) {
|
|
1348
|
+
let normalized = guideline.trim();
|
|
1349
|
+
normalized.length > 0 && unique.add(normalized);
|
|
1350
|
+
}
|
|
1351
|
+
return Array.from(unique);
|
|
1352
|
+
}
|
|
1353
|
+
_rebuildSystemPrompt(toolNames) {
|
|
1354
|
+
let validToolNames = toolNames.filter((name) => this._toolRegistry.has(name)), toolSnippets = {}, promptGuidelines = [];
|
|
1355
|
+
for (let name of validToolNames) {
|
|
1356
|
+
let snippet = this._toolPromptSnippets.get(name);
|
|
1357
|
+
snippet && (toolSnippets[name] = snippet);
|
|
1358
|
+
let toolGuidelines = this._toolPromptGuidelines.get(name);
|
|
1359
|
+
toolGuidelines && promptGuidelines.push(...toolGuidelines);
|
|
1360
|
+
}
|
|
1361
|
+
let loaderSystemPrompt = this._resourceLoader.getSystemPrompt(), loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt(), appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join(`
|
|
1362
|
+
|
|
1363
|
+
`) : void 0, loadedSkills = this._resourceLoader.getSkills().skills, loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles, contextFiles = this._resourceLoader.contextFilesEnabled?.() === !1 ? [] : [
|
|
1364
|
+
...loadSelectedUserInstructions({
|
|
1365
|
+
agentDir: this._agentDir,
|
|
1366
|
+
settingsManager: this.settingsManager,
|
|
1367
|
+
model: this.model
|
|
1368
|
+
}),
|
|
1369
|
+
...loadedContextFiles.filter((file) => !isUserInstructionsPath(file.path, this._agentDir))
|
|
1370
|
+
];
|
|
1371
|
+
return this._baseSystemPromptOptions = {
|
|
1372
|
+
cwd: this._cwd,
|
|
1373
|
+
modelSlug: this.model ? `${this.model.provider}/${this.model.id}` : void 0,
|
|
1374
|
+
skills: loadedSkills,
|
|
1375
|
+
contextFiles,
|
|
1376
|
+
customPrompt: loaderSystemPrompt,
|
|
1377
|
+
appendSystemPrompt,
|
|
1378
|
+
selectedTools: validToolNames,
|
|
1379
|
+
toolSnippets,
|
|
1380
|
+
promptGuidelines
|
|
1381
|
+
}, buildSystemPrompt(this._baseSystemPromptOptions);
|
|
1382
|
+
}
|
|
1383
|
+
_refreshSystemPromptForCurrentModel() {
|
|
1384
|
+
this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames()), this.agent.state.systemPrompt = this._withSystemPromptAppend(this._systemPromptOverride ?? this._baseSystemPrompt);
|
|
1385
|
+
}
|
|
1386
|
+
// =========================================================================
|
|
1387
|
+
// Prompting
|
|
1388
|
+
// =========================================================================
|
|
1389
|
+
_registerSubagentWaitInterruption() {
|
|
1390
|
+
let run = this._activePromptRun, ownerSignal = this.agent.signal, wait = run && ownerSignal ? { controller: new AbortController(), interrupted: !1, ownerSignal } : void 0;
|
|
1391
|
+
return run && wait && (run.waits.add(wait), run.gracefulStopRequested && (wait.interrupted = !0, wait.controller.abort())), {
|
|
1392
|
+
signal: wait?.controller.signal ?? new AbortController().signal,
|
|
1393
|
+
wasInterrupted: () => wait?.interrupted === !0,
|
|
1394
|
+
unregister: () => {
|
|
1395
|
+
wait && run?.waits.delete(wait);
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
_interruptSubagentWaits(run, ownerSignal) {
|
|
1400
|
+
for (let wait of run.waits)
|
|
1401
|
+
wait.ownerSignal === ownerSignal && (wait.interrupted = !0, wait.controller.abort());
|
|
1402
|
+
}
|
|
1403
|
+
_invalidateWaitPromptHandoffs(message) {
|
|
1404
|
+
this._waitPromptGeneration++;
|
|
1405
|
+
let handoff = this._waitPromptHandoff;
|
|
1406
|
+
if (this._waitPromptHandoff = void 0, !handoff)
|
|
1407
|
+
return;
|
|
1408
|
+
let error = new Error(message);
|
|
1409
|
+
for (let reservation of handoff.reservations.splice(0))
|
|
1410
|
+
reservation.reject(error);
|
|
1411
|
+
}
|
|
1412
|
+
async _runAgentPrompt(messages) {
|
|
1413
|
+
let run = { waits: /* @__PURE__ */ new Set(), gracefulStopRequested: !1 };
|
|
1414
|
+
this._activePromptRun = run, this._isAgentRunActive = !0;
|
|
1415
|
+
try {
|
|
1416
|
+
for (await this.agent.prompt(messages); await this._handlePostAgentRun(run); )
|
|
1417
|
+
await this.agent.continue();
|
|
1418
|
+
} finally {
|
|
1419
|
+
this._activePromptRun === run && (this._activePromptRun = void 0), this._systemPromptOverride = void 0, this._flushPendingBashMessages(), await this._emitAgentSettled();
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
async _handlePostAgentRun(run) {
|
|
1423
|
+
let msg = this._lastAssistantMessage;
|
|
1424
|
+
return this._lastAssistantMessage = void 0, !msg || run.gracefulStopRequested ? !1 : msg.stopReason === "error" && this._isRotationError(msg) && await this._tryRotateSubscriptionKey(msg) || this._isRetryableError(msg) && await this._prepareRetry(msg) || msg.stopReason === "error" && this._isRetryableError(msg) && await this._tryRotateSubscriptionKey(msg) || (msg.stopReason === "error" && this._retryAttempt > 0 && (this._emit({
|
|
1425
|
+
type: "auto_retry_end",
|
|
1426
|
+
success: !1,
|
|
1427
|
+
attempt: this._retryAttempt,
|
|
1428
|
+
finalError: msg.errorMessage
|
|
1429
|
+
}), this._retryAttempt = 0), await this._checkCompaction(msg)) ? !0 : this.agent.hasQueuedMessages();
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Send a prompt to the agent.
|
|
1433
|
+
* - Handles extension commands (registered via pi.registerCommand) immediately, even during streaming
|
|
1434
|
+
* - Expands file-based prompt templates by default
|
|
1435
|
+
* - During streaming, queues via steer() or followUp() based on streamingBehavior option
|
|
1436
|
+
* - Validates model and API key before sending (when not streaming)
|
|
1437
|
+
* @throws Error if streaming and no streamingBehavior specified
|
|
1438
|
+
* @throws Error if no model selected or no API key available (when not streaming)
|
|
1439
|
+
*/
|
|
1440
|
+
async prompt(text, options) {
|
|
1441
|
+
this.assertCanStartWork();
|
|
1442
|
+
let preflightResult = options?.preflightResult, prepared, messages;
|
|
1443
|
+
try {
|
|
1444
|
+
if (prepared = await this._preparePromptInput(text, options, this.isStreaming ? options?.streamingBehavior : void 0), !prepared) {
|
|
1445
|
+
preflightResult?.(!0);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (this.isStreaming) {
|
|
1449
|
+
if (!options?.streamingBehavior)
|
|
1450
|
+
throw new Error("Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.");
|
|
1451
|
+
options.streamingBehavior === "followUp" ? await this._queueFollowUp(prepared.text, prepared.images) : await this._queueSteer(prepared.text, prepared.images), preflightResult?.(!0);
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
messages = await this._createPromptMessages(prepared);
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
throw preflightResult?.(!1), error;
|
|
1457
|
+
}
|
|
1458
|
+
preflightResult?.(!0), await this._runAgentPrompt(messages);
|
|
1459
|
+
}
|
|
1460
|
+
async _preparePromptInput(text, options, streamingBehavior) {
|
|
1461
|
+
let expandPromptTemplates = options?.expandPromptTemplates ?? !0;
|
|
1462
|
+
if (expandPromptTemplates && text.startsWith("/") && await this._tryExecuteExtensionCommand(text))
|
|
1463
|
+
return;
|
|
1464
|
+
let currentText = text, currentImages = options?.images;
|
|
1465
|
+
if (this._extensionRunner.hasHandlers("input")) {
|
|
1466
|
+
let inputResult = await this._extensionRunner.emitInput(currentText, currentImages, options?.source ?? "interactive", streamingBehavior);
|
|
1467
|
+
if (inputResult.action === "handled")
|
|
1468
|
+
return;
|
|
1469
|
+
inputResult.action === "transform" && (currentText = inputResult.text, currentImages = inputResult.images ?? currentImages);
|
|
1470
|
+
}
|
|
1471
|
+
return expandPromptTemplates && (currentText = this._expandSkillCommand(currentText), currentText = expandPromptTemplate(currentText, [...this.promptTemplates])), { text: currentText, images: currentImages };
|
|
1472
|
+
}
|
|
1473
|
+
async _createPromptMessages(input) {
|
|
1474
|
+
if (this._flushPendingBashMessages(), !this.model)
|
|
1475
|
+
throw new Error(formatNoModelSelectedMessage());
|
|
1476
|
+
if (!(this._modelRuntime.hasConfiguredAuth(this.model.provider) || await this._modelRuntime.checkAuth(this.model.provider) !== void 0))
|
|
1477
|
+
throw this._modelRuntime.isUsingOAuth(this.model.provider) ? new Error(`Authentication failed for "${this.model.provider}". Credentials may have expired or network is unavailable. Run '/login ${this.model.provider}' to re-authenticate.`) : new Error(formatNoApiKeyFoundMessage(this.model.provider));
|
|
1478
|
+
let lastAssistant = this._findLastAssistantMessage();
|
|
1479
|
+
lastAssistant && await this._checkCompaction(lastAssistant, !1);
|
|
1480
|
+
let userContent = [{ type: "text", text: input.text }];
|
|
1481
|
+
input.images && userContent.push(...input.images);
|
|
1482
|
+
let messages = [
|
|
1483
|
+
{
|
|
1484
|
+
role: "user",
|
|
1485
|
+
content: userContent,
|
|
1486
|
+
timestamp: Date.now()
|
|
1487
|
+
},
|
|
1488
|
+
...this._pendingNextTurnMessages
|
|
1489
|
+
];
|
|
1490
|
+
this._pendingNextTurnMessages = [];
|
|
1491
|
+
let result = await this._extensionRunner.emitBeforeAgentStart(input.text, input.images, this._baseSystemPrompt, this._baseSystemPromptOptions);
|
|
1492
|
+
if (result?.messages)
|
|
1493
|
+
for (let msg of result.messages)
|
|
1494
|
+
messages.push({
|
|
1495
|
+
role: "custom",
|
|
1496
|
+
customType: msg.customType,
|
|
1497
|
+
content: msg.content ?? [],
|
|
1498
|
+
display: msg.display,
|
|
1499
|
+
details: msg.details,
|
|
1500
|
+
timestamp: Date.now()
|
|
1501
|
+
});
|
|
1502
|
+
return result?.systemPrompt !== void 0 ? (this._systemPromptOverride = result.systemPrompt, this.agent.state.systemPrompt = this._withSystemPromptAppend(result.systemPrompt)) : (this._systemPromptOverride = void 0, this.agent.state.systemPrompt = this._withSystemPromptAppend(this._baseSystemPrompt)), messages;
|
|
1503
|
+
}
|
|
1504
|
+
async interruptSubagentWaitWithPrompt(text, options) {
|
|
1505
|
+
let acceptance = this._waitPromptAcceptance.then(() => this._reserveWaitPrompt(text, options));
|
|
1506
|
+
return this._waitPromptAcceptance = acceptance.then(() => {
|
|
1507
|
+
}, () => {
|
|
1508
|
+
}), acceptance;
|
|
1509
|
+
}
|
|
1510
|
+
async _reserveWaitPrompt(text, options) {
|
|
1511
|
+
this.assertCanStartWork();
|
|
1512
|
+
let activeRun = this._activePromptRun, existing = this._waitPromptHandoff, activeWait = activeRun?.waits.values().next().value, run = existing?.accepting ? existing.run : activeWait ? activeRun : void 0, ownerSignal = existing?.accepting ? existing.ownerSignal : activeWait?.ownerSignal;
|
|
1513
|
+
if (!run || !ownerSignal)
|
|
1514
|
+
return;
|
|
1515
|
+
let handoff = existing?.run === run && existing.accepting ? existing : {
|
|
1516
|
+
run,
|
|
1517
|
+
ownerSignal,
|
|
1518
|
+
generation: this._waitPromptGeneration,
|
|
1519
|
+
reservations: [],
|
|
1520
|
+
draining: !1,
|
|
1521
|
+
accepting: !0
|
|
1522
|
+
};
|
|
1523
|
+
this._waitPromptHandoff = handoff;
|
|
1524
|
+
let prepared;
|
|
1525
|
+
try {
|
|
1526
|
+
prepared = await this._preparePromptInput(text, options, void 0);
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
throw handoff.reservations.length === 0 && this._waitPromptHandoff === handoff && (this._waitPromptHandoff = void 0), error;
|
|
1529
|
+
}
|
|
1530
|
+
if (!prepared)
|
|
1531
|
+
return handoff.reservations.length === 0 && this._waitPromptHandoff === handoff && (this._waitPromptHandoff = void 0), { completion: Promise.resolve() };
|
|
1532
|
+
let resolveCompletion = () => {
|
|
1533
|
+
}, rejectCompletion = (_error) => {
|
|
1534
|
+
}, completion = new Promise((resolve3, reject) => {
|
|
1535
|
+
resolveCompletion = resolve3, rejectCompletion = reject;
|
|
1536
|
+
});
|
|
1537
|
+
return handoff.reservations.push({ input: prepared, resolve: resolveCompletion, reject: rejectCompletion }), !run.gracefulStopRequested && this.agent.requestGracefulStop(handoff.ownerSignal) && (run.gracefulStopRequested = !0), this._interruptSubagentWaits(run, handoff.ownerSignal), handoff.draining || (handoff.draining = !0, this._drainWaitPromptHandoff(handoff)), { completion };
|
|
1538
|
+
}
|
|
1539
|
+
async _drainWaitPromptHandoff(handoff) {
|
|
1540
|
+
for (await this.waitForIdle(); this._waitPromptHandoff === handoff && handoff.generation === this._waitPromptGeneration && handoff.reservations.length > 0; ) {
|
|
1541
|
+
let reservation = handoff.reservations[0];
|
|
1542
|
+
try {
|
|
1543
|
+
this.assertCanStartWork();
|
|
1544
|
+
let messages = await this._createPromptMessages(reservation.input);
|
|
1545
|
+
handoff.accepting = !1, await this._runAgentPrompt(messages), reservation.resolve();
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
reservation.reject(error);
|
|
1548
|
+
} finally {
|
|
1549
|
+
handoff.reservations[0] === reservation && handoff.reservations.shift();
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
handoff.accepting = !1, this._waitPromptHandoff === handoff && (this._waitPromptHandoff = void 0);
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Try to execute an extension command. Returns true if command was found and executed.
|
|
1556
|
+
*/
|
|
1557
|
+
async _tryExecuteExtensionCommand(text) {
|
|
1558
|
+
let spaceIndex = text.indexOf(" "), commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex), args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1), command = this._extensionRunner.getCommand(commandName);
|
|
1559
|
+
if (!command)
|
|
1560
|
+
return !1;
|
|
1561
|
+
let ctx = this._extensionRunner.createCommandContext();
|
|
1562
|
+
try {
|
|
1563
|
+
return await command.handler(args, ctx), !0;
|
|
1564
|
+
} catch (err) {
|
|
1565
|
+
return this._extensionRunner.emitError({
|
|
1566
|
+
extensionPath: `command:${commandName}`,
|
|
1567
|
+
event: "command",
|
|
1568
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1569
|
+
}), !0;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Expand skill commands (/skill:name args) to their full content.
|
|
1574
|
+
* Returns the expanded text, or the original text if not a skill command or skill not found.
|
|
1575
|
+
* Emits errors via extension runner if file read fails.
|
|
1576
|
+
*/
|
|
1577
|
+
_expandSkillCommand(text) {
|
|
1578
|
+
if (!text.startsWith("/skill:"))
|
|
1579
|
+
return text;
|
|
1580
|
+
let spaceIndex = text.indexOf(" "), skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex), args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim(), skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);
|
|
1581
|
+
if (!skill)
|
|
1582
|
+
return text;
|
|
1583
|
+
try {
|
|
1584
|
+
let content = readFileSync2(skill.filePath, "utf-8"), body = stripFrontmatter(content).trim(), skillBlock = `<skill name="${skill.name}" location="${skill.filePath}">
|
|
1585
|
+
References are relative to ${skill.baseDir}.
|
|
1586
|
+
|
|
1587
|
+
${body}
|
|
1588
|
+
</skill>`;
|
|
1589
|
+
return args ? `${skillBlock}
|
|
1590
|
+
|
|
1591
|
+
${args}` : skillBlock;
|
|
1592
|
+
} catch (err) {
|
|
1593
|
+
return this._extensionRunner.emitError({
|
|
1594
|
+
extensionPath: skill.filePath,
|
|
1595
|
+
event: "skill_expansion",
|
|
1596
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1597
|
+
}), text;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Queue a steering message while the agent is running.
|
|
1602
|
+
* Delivered after the current assistant turn finishes executing its tool calls,
|
|
1603
|
+
* before the next LLM call.
|
|
1604
|
+
* Expands skill commands and prompt templates. Errors on extension commands.
|
|
1605
|
+
* @param images Optional image attachments to include with the message
|
|
1606
|
+
* @throws Error if text is an extension command
|
|
1607
|
+
*/
|
|
1608
|
+
async steer(text, images) {
|
|
1609
|
+
this.assertCanStartWork(), text.startsWith("/") && this._throwIfExtensionCommand(text);
|
|
1610
|
+
let expandedText = this._expandSkillCommand(text);
|
|
1611
|
+
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]), await this._queueSteer(expandedText, images);
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Queue a follow-up message to be processed after the agent finishes.
|
|
1615
|
+
* Delivered only when agent has no more tool calls or steering messages.
|
|
1616
|
+
* Expands skill commands and prompt templates. Errors on extension commands.
|
|
1617
|
+
* @param images Optional image attachments to include with the message
|
|
1618
|
+
* @throws Error if text is an extension command
|
|
1619
|
+
*/
|
|
1620
|
+
async followUp(text, images) {
|
|
1621
|
+
this.assertCanStartWork(), text.startsWith("/") && this._throwIfExtensionCommand(text);
|
|
1622
|
+
let expandedText = this._expandSkillCommand(text);
|
|
1623
|
+
expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]), await this._queueFollowUp(expandedText, images);
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Internal: Queue a steering message (already expanded, no extension command check).
|
|
1627
|
+
*/
|
|
1628
|
+
async _queueSteer(text, images) {
|
|
1629
|
+
this._steeringMessages.push(text), this._emitQueueUpdate();
|
|
1630
|
+
let content = [{ type: "text", text }];
|
|
1631
|
+
images && content.push(...images), this.agent.steer({
|
|
1632
|
+
role: "user",
|
|
1633
|
+
content,
|
|
1634
|
+
timestamp: Date.now()
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Internal: Queue a follow-up message (already expanded, no extension command check).
|
|
1639
|
+
*/
|
|
1640
|
+
async _queueFollowUp(text, images) {
|
|
1641
|
+
this._followUpMessages.push(text), this._emitQueueUpdate();
|
|
1642
|
+
let content = [{ type: "text", text }];
|
|
1643
|
+
images && content.push(...images), this.agent.followUp({
|
|
1644
|
+
role: "user",
|
|
1645
|
+
content,
|
|
1646
|
+
timestamp: Date.now()
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Throw an error if the text is an extension command.
|
|
1651
|
+
*/
|
|
1652
|
+
_throwIfExtensionCommand(text) {
|
|
1653
|
+
let spaceIndex = text.indexOf(" "), commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
1654
|
+
if (this._extensionRunner.getCommand(commandName))
|
|
1655
|
+
throw new Error(`Extension command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`);
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Send a custom message to the session. Creates a CustomMessageEntry.
|
|
1659
|
+
*
|
|
1660
|
+
* Handles three cases:
|
|
1661
|
+
* - Streaming: queues message, processed when loop pulls from queue
|
|
1662
|
+
* - Not streaming + triggerTurn: appends to state/session, starts new turn
|
|
1663
|
+
* - Not streaming + no trigger: appends to state/session, no turn
|
|
1664
|
+
*
|
|
1665
|
+
* @param message Custom message with customType, content, display, details
|
|
1666
|
+
* @param options.triggerTurn If true and not streaming, triggers a new LLM turn
|
|
1667
|
+
* @param options.deliverAs Delivery mode: "steer", "followUp", or "nextTurn"
|
|
1668
|
+
*/
|
|
1669
|
+
async sendCustomMessage(message, options) {
|
|
1670
|
+
this.assertCanStartWork();
|
|
1671
|
+
let appMessage = {
|
|
1672
|
+
role: "custom",
|
|
1673
|
+
customType: message.customType,
|
|
1674
|
+
// Untyped extensions can pass null/missing content; normalize at ingestion.
|
|
1675
|
+
content: message.content ?? [],
|
|
1676
|
+
display: message.display,
|
|
1677
|
+
details: message.details,
|
|
1678
|
+
timestamp: Date.now()
|
|
1679
|
+
};
|
|
1680
|
+
options?.deliverAs === "nextTurn" ? this._pendingNextTurnMessages.push(appMessage) : this.isStreaming ? options?.deliverAs === "followUp" ? this.agent.followUp(appMessage) : this.agent.steer(appMessage) : options?.triggerTurn ? await this._runAgentPrompt(appMessage) : (this.agent.state.messages.push(appMessage), this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details), this._emit({ type: "message_start", message: appMessage }), this._emit({ type: "message_end", message: appMessage }));
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Send a user message to the agent. Always triggers a turn.
|
|
1684
|
+
* When the agent is streaming, use deliverAs to specify how to queue the message.
|
|
1685
|
+
*
|
|
1686
|
+
* @param content User message content (string or content array)
|
|
1687
|
+
* @param options.deliverAs Delivery mode when streaming: "steer" or "followUp"
|
|
1688
|
+
*/
|
|
1689
|
+
async sendUserMessage(content, options) {
|
|
1690
|
+
let text, images;
|
|
1691
|
+
if (typeof content == "string")
|
|
1692
|
+
text = content;
|
|
1693
|
+
else {
|
|
1694
|
+
let textParts = [];
|
|
1695
|
+
images = [];
|
|
1696
|
+
for (let part of content)
|
|
1697
|
+
part.type === "text" ? textParts.push(part.text) : images.push(part);
|
|
1698
|
+
text = textParts.join(`
|
|
1699
|
+
`), images.length === 0 && (images = void 0);
|
|
1700
|
+
}
|
|
1701
|
+
await this.prompt(text, {
|
|
1702
|
+
expandPromptTemplates: !1,
|
|
1703
|
+
streamingBehavior: options?.deliverAs,
|
|
1704
|
+
images,
|
|
1705
|
+
source: "extension"
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Clear all queued messages and return them.
|
|
1710
|
+
* Useful for restoring to editor when user aborts.
|
|
1711
|
+
* @returns Object with steering and followUp arrays
|
|
1712
|
+
*/
|
|
1713
|
+
clearQueue() {
|
|
1714
|
+
let steering = [...this._steeringMessages], followUp = [...this._followUpMessages];
|
|
1715
|
+
return this._steeringMessages = [], this._followUpMessages = [], this.agent.clearAllQueues(), this._emitQueueUpdate(), { steering, followUp };
|
|
1716
|
+
}
|
|
1717
|
+
/** Number of pending messages (includes both steering and follow-up) */
|
|
1718
|
+
get pendingMessageCount() {
|
|
1719
|
+
return this._steeringMessages.length + this._followUpMessages.length;
|
|
1720
|
+
}
|
|
1721
|
+
/** Get pending steering messages (read-only) */
|
|
1722
|
+
getSteeringMessages() {
|
|
1723
|
+
return this._steeringMessages;
|
|
1724
|
+
}
|
|
1725
|
+
/** Get pending follow-up messages (read-only) */
|
|
1726
|
+
getFollowUpMessages() {
|
|
1727
|
+
return this._followUpMessages;
|
|
1728
|
+
}
|
|
1729
|
+
get resourceLoader() {
|
|
1730
|
+
return this._resourceLoader;
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Abort current operation and wait for agent to become idle.
|
|
1734
|
+
*/
|
|
1735
|
+
async abort() {
|
|
1736
|
+
this._invalidateWaitPromptHandoffs("The active run was aborted before the pending prompt could run."), this.abortRetry(), this.agent.abort(), await this.waitForIdle();
|
|
1737
|
+
}
|
|
1738
|
+
async waitForIdle() {
|
|
1739
|
+
this.isIdle || await this._getIdleWaitPromise();
|
|
1740
|
+
}
|
|
1741
|
+
// =========================================================================
|
|
1742
|
+
// Model Management
|
|
1743
|
+
// =========================================================================
|
|
1744
|
+
async _emitModelSelect(nextModel, previousModel, source) {
|
|
1745
|
+
modelsAreEqual(previousModel, nextModel) || await this._extensionRunner.emit({
|
|
1746
|
+
type: "model_select",
|
|
1747
|
+
model: nextModel,
|
|
1748
|
+
previousModel,
|
|
1749
|
+
source
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Set model directly.
|
|
1754
|
+
* Validates that auth is configured, saves to session and settings.
|
|
1755
|
+
* @throws Error if no auth is configured for the model
|
|
1756
|
+
*/
|
|
1757
|
+
async setModel(model) {
|
|
1758
|
+
if (!await this._modelRuntime.checkAuth(model.provider))
|
|
1759
|
+
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
1760
|
+
let previousModel = this.model, thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1761
|
+
this.agent.state.model = model, this._syncOpenAIServiceTier(), this._refreshSystemPromptForCurrentModel(), this.sessionManager.appendModelChange(model.provider, model.id), this.settingsManager.setDefaultModelAndProvider(model.provider, model.id), this.setThinkingLevel(thinkingLevel), await this._emitModelSelect(model, previousModel, "set");
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Cycle to next/previous model.
|
|
1765
|
+
* Uses scoped models (from --models flag) if available, otherwise all available models.
|
|
1766
|
+
* @param direction - "forward" (default) or "backward"
|
|
1767
|
+
* @returns The new model info, or undefined if only one model available
|
|
1768
|
+
*/
|
|
1769
|
+
async cycleModel(direction = "forward") {
|
|
1770
|
+
return this._scopedModels.length > 0 ? this._cycleScopedModel(direction) : this._cycleAvailableModel(direction);
|
|
1771
|
+
}
|
|
1772
|
+
async _cycleScopedModel(direction) {
|
|
1773
|
+
let scopedModels = (await Promise.all(this._scopedModels.map(async (scoped) => ({
|
|
1774
|
+
scoped,
|
|
1775
|
+
auth: await this._modelRuntime.checkAuth(scoped.model.provider)
|
|
1776
|
+
})))).filter(({ auth }) => auth !== void 0).map(({ scoped }) => scoped);
|
|
1777
|
+
if (scopedModels.length <= 1)
|
|
1778
|
+
return;
|
|
1779
|
+
let currentModel = this.model, currentIndex = scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel));
|
|
1780
|
+
currentIndex === -1 && (currentIndex = 0);
|
|
1781
|
+
let len = scopedModels.length, nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len, next = scopedModels[nextIndex], thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
|
|
1782
|
+
return this.agent.state.model = next.model, this._syncOpenAIServiceTier(), this._refreshSystemPromptForCurrentModel(), this.sessionManager.appendModelChange(next.model.provider, next.model.id), this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id), this.setThinkingLevel(thinkingLevel), await this._emitModelSelect(next.model, currentModel, "cycle"), { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: !0 };
|
|
1783
|
+
}
|
|
1784
|
+
async _cycleAvailableModel(direction) {
|
|
1785
|
+
let availableModels = await this._modelRuntime.getAvailable();
|
|
1786
|
+
if (availableModels.length <= 1)
|
|
1787
|
+
return;
|
|
1788
|
+
let currentModel = this.model, currentIndex = availableModels.findIndex((m) => modelsAreEqual(m, currentModel));
|
|
1789
|
+
currentIndex === -1 && (currentIndex = 0);
|
|
1790
|
+
let len = availableModels.length, nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len, nextModel = availableModels[nextIndex], thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1791
|
+
return this.agent.state.model = nextModel, this._syncOpenAIServiceTier(), this._refreshSystemPromptForCurrentModel(), this.sessionManager.appendModelChange(nextModel.provider, nextModel.id), this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id), this.setThinkingLevel(thinkingLevel), await this._emitModelSelect(nextModel, currentModel, "cycle"), { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: !1 };
|
|
1792
|
+
}
|
|
1793
|
+
// =========================================================================
|
|
1794
|
+
// Thinking Level Management
|
|
1795
|
+
// =========================================================================
|
|
1796
|
+
/**
|
|
1797
|
+
* Set thinking level.
|
|
1798
|
+
* Clamps to model capabilities based on available thinking levels.
|
|
1799
|
+
* Saves to session and settings only if the level actually changes.
|
|
1800
|
+
*/
|
|
1801
|
+
setThinkingLevel(level) {
|
|
1802
|
+
let availableLevels = this.getAvailableThinkingLevels(), effectiveLevel = availableLevels.includes(level) ? level : this._clampThinkingLevel(level, availableLevels), previousLevel = this.agent.state.thinkingLevel, isChanging = effectiveLevel !== previousLevel;
|
|
1803
|
+
this.agent.state.thinkingLevel = effectiveLevel, isChanging && (this.sessionManager.appendThinkingLevelChange(effectiveLevel), (this.supportsThinking() || effectiveLevel !== "off") && this.settingsManager.setDefaultThinkingLevel(effectiveLevel), this._emit({ type: "thinking_level_changed", level: effectiveLevel }), this._extensionRunner.emit({
|
|
1804
|
+
type: "thinking_level_select",
|
|
1805
|
+
level: effectiveLevel,
|
|
1806
|
+
previousLevel
|
|
1807
|
+
}));
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Cycle to next thinking level.
|
|
1811
|
+
* @returns New level, or undefined if model doesn't support thinking
|
|
1812
|
+
*/
|
|
1813
|
+
cycleThinkingLevel() {
|
|
1814
|
+
if (!this.supportsThinking())
|
|
1815
|
+
return;
|
|
1816
|
+
let levels = this.getAvailableThinkingLevels(), nextIndex = (levels.indexOf(this.thinkingLevel) + 1) % levels.length, nextLevel = levels[nextIndex];
|
|
1817
|
+
return this.setThinkingLevel(nextLevel), nextLevel;
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Get available thinking levels for current model.
|
|
1821
|
+
* Uses the registry row when /refresh has a newer copy than the session object.
|
|
1822
|
+
*/
|
|
1823
|
+
getAvailableThinkingLevels() {
|
|
1824
|
+
let model = this.resolvedCatalogModel();
|
|
1825
|
+
return model ? getSupportedThinkingLevels(model) : THINKING_LEVELS;
|
|
1826
|
+
}
|
|
1827
|
+
/** Replace the session model with the current registry copy after a catalog refresh. */
|
|
1828
|
+
refreshModelFromRegistry() {
|
|
1829
|
+
this._refreshCurrentModelFromRegistry();
|
|
1830
|
+
}
|
|
1831
|
+
resolvedCatalogModel() {
|
|
1832
|
+
let current = this.model;
|
|
1833
|
+
if (!current)
|
|
1834
|
+
return;
|
|
1835
|
+
let registry = this._modelRuntime.getModel(current.provider, current.id);
|
|
1836
|
+
if (!registry || registry === current)
|
|
1837
|
+
return current;
|
|
1838
|
+
let thinkingLevelMap = current.thinkingLevelMap || registry.thinkingLevelMap ? { ...current.thinkingLevelMap, ...registry.thinkingLevelMap } : current.thinkingLevelMap;
|
|
1839
|
+
return {
|
|
1840
|
+
...current,
|
|
1841
|
+
...registry,
|
|
1842
|
+
compat: { ...current.compat, ...registry.compat },
|
|
1843
|
+
thinkingLevelMap
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Check if current model supports thinking/reasoning.
|
|
1848
|
+
*/
|
|
1849
|
+
supportsThinking() {
|
|
1850
|
+
return !!this.model?.reasoning;
|
|
1851
|
+
}
|
|
1852
|
+
_getThinkingLevelForModelSwitch(explicitLevel) {
|
|
1853
|
+
return explicitLevel !== void 0 ? explicitLevel : this.supportsThinking() ? this.thinkingLevel : this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
|
|
1854
|
+
}
|
|
1855
|
+
_clampThinkingLevel(level, _availableLevels) {
|
|
1856
|
+
return this.model ? clampThinkingLevel(this.model, level) : "off";
|
|
1857
|
+
}
|
|
1858
|
+
// =========================================================================
|
|
1859
|
+
// Queue Mode Management
|
|
1860
|
+
// =========================================================================
|
|
1861
|
+
syncQueueModesFromSettings() {
|
|
1862
|
+
this.agent.steeringMode = this.settingsManager.getSteeringMode(), this.agent.followUpMode = this.settingsManager.getFollowUpMode();
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Set steering message mode.
|
|
1866
|
+
* Saves to settings.
|
|
1867
|
+
*/
|
|
1868
|
+
setSteeringMode(mode) {
|
|
1869
|
+
this.agent.steeringMode = mode, this.settingsManager.setSteeringMode(mode);
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Set follow-up message mode.
|
|
1873
|
+
* Saves to settings.
|
|
1874
|
+
*/
|
|
1875
|
+
setFollowUpMode(mode) {
|
|
1876
|
+
this.agent.followUpMode = mode, this.settingsManager.setFollowUpMode(mode);
|
|
1877
|
+
}
|
|
1878
|
+
// =========================================================================
|
|
1879
|
+
// Compaction
|
|
1880
|
+
// =========================================================================
|
|
1881
|
+
_latestCompactionResult() {
|
|
1882
|
+
let pathEntries = this.sessionManager.getBranch(), lastEntry = pathEntries[pathEntries.length - 1];
|
|
1883
|
+
if (lastEntry?.type === "compaction")
|
|
1884
|
+
return {
|
|
1885
|
+
summary: lastEntry.summary,
|
|
1886
|
+
firstKeptEntryId: lastEntry.firstKeptEntryId,
|
|
1887
|
+
tokensBefore: lastEntry.tokensBefore,
|
|
1888
|
+
estimatedTokensAfter: estimateMessagesTokens(this.sessionManager.buildSessionContext().messages),
|
|
1889
|
+
details: lastEntry.details
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Manually compact the session context.
|
|
1894
|
+
* Aborts current agent operation first.
|
|
1895
|
+
* @param customInstructions Optional instructions for the compaction summary
|
|
1896
|
+
*/
|
|
1897
|
+
async compact(customInstructions) {
|
|
1898
|
+
let existing = this._latestCompactionResult();
|
|
1899
|
+
if (existing)
|
|
1900
|
+
return existing;
|
|
1901
|
+
if (this._manualCompactionPromise)
|
|
1902
|
+
return this._manualCompactionPromise;
|
|
1903
|
+
let promise = this._compact(customInstructions);
|
|
1904
|
+
this._manualCompactionPromise = promise;
|
|
1905
|
+
try {
|
|
1906
|
+
return await promise;
|
|
1907
|
+
} finally {
|
|
1908
|
+
this._manualCompactionPromise === promise && (this._manualCompactionPromise = void 0);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
async _compact(customInstructions) {
|
|
1912
|
+
this._disconnectFromAgent(), await this.abort();
|
|
1913
|
+
let existing = this._latestCompactionResult();
|
|
1914
|
+
if (existing)
|
|
1915
|
+
return this._reconnectToAgent(), existing;
|
|
1916
|
+
this._compactionAbortController = new AbortController(), this._emit({ type: "compaction_start", reason: "manual" });
|
|
1917
|
+
try {
|
|
1918
|
+
if (!this.model)
|
|
1919
|
+
throw new Error(formatNoModelSelectedMessage());
|
|
1920
|
+
let { apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model), pathEntries = this.sessionManager.getBranch(), settings = this.settingsManager.getCompactionSettings(), preparation = prepareCompaction(pathEntries, settings);
|
|
1921
|
+
if (!preparation)
|
|
1922
|
+
throw pathEntries[pathEntries.length - 1]?.type === "compaction" ? new Error("Already compacted") : new Error("Nothing to compact (session too small)");
|
|
1923
|
+
let extensionCompaction, fromExtension = !1;
|
|
1924
|
+
if (this._extensionRunner.hasHandlers("session_before_compact")) {
|
|
1925
|
+
let result = await this._extensionRunner.emit({
|
|
1926
|
+
type: "session_before_compact",
|
|
1927
|
+
preparation,
|
|
1928
|
+
branchEntries: pathEntries,
|
|
1929
|
+
customInstructions,
|
|
1930
|
+
reason: "manual",
|
|
1931
|
+
willRetry: !1,
|
|
1932
|
+
signal: this._compactionAbortController.signal
|
|
1933
|
+
});
|
|
1934
|
+
if (result?.cancel)
|
|
1935
|
+
throw new Error("Compaction cancelled");
|
|
1936
|
+
result?.compaction && (extensionCompaction = result.compaction, fromExtension = !0);
|
|
1937
|
+
}
|
|
1938
|
+
let summary, firstKeptEntryId, tokensBefore, details;
|
|
1939
|
+
if (extensionCompaction)
|
|
1940
|
+
summary = extensionCompaction.summary, firstKeptEntryId = extensionCompaction.firstKeptEntryId, tokensBefore = extensionCompaction.tokensBefore, details = extensionCompaction.details;
|
|
1941
|
+
else {
|
|
1942
|
+
let result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, env);
|
|
1943
|
+
summary = result.summary, firstKeptEntryId = result.firstKeptEntryId, tokensBefore = result.tokensBefore, details = result.details;
|
|
1944
|
+
}
|
|
1945
|
+
if (this._compactionAbortController.signal.aborted)
|
|
1946
|
+
throw new Error("Compaction cancelled");
|
|
1947
|
+
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1948
|
+
let newEntries = this.sessionManager.getEntries(), sessionContext = this.sessionManager.buildSessionContext();
|
|
1949
|
+
this.agent.state.messages = sessionContext.messages;
|
|
1950
|
+
let estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages), savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
1951
|
+
this._extensionRunner && savedCompactionEntry && await this._extensionRunner.emit({
|
|
1952
|
+
type: "session_compact",
|
|
1953
|
+
compactionEntry: savedCompactionEntry,
|
|
1954
|
+
fromExtension,
|
|
1955
|
+
reason: "manual",
|
|
1956
|
+
willRetry: !1
|
|
1957
|
+
});
|
|
1958
|
+
let compactionResult = {
|
|
1959
|
+
summary,
|
|
1960
|
+
firstKeptEntryId,
|
|
1961
|
+
tokensBefore,
|
|
1962
|
+
estimatedTokensAfter,
|
|
1963
|
+
details
|
|
1964
|
+
};
|
|
1965
|
+
return this._emit({
|
|
1966
|
+
type: "compaction_end",
|
|
1967
|
+
reason: "manual",
|
|
1968
|
+
result: compactionResult,
|
|
1969
|
+
aborted: !1,
|
|
1970
|
+
willRetry: !1
|
|
1971
|
+
}), compactionResult;
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
let message = error instanceof Error ? error.message : String(error), aborted = message === "Compaction cancelled" || error instanceof Error && error.name === "AbortError";
|
|
1974
|
+
throw this._emit({
|
|
1975
|
+
type: "compaction_end",
|
|
1976
|
+
reason: "manual",
|
|
1977
|
+
result: void 0,
|
|
1978
|
+
aborted,
|
|
1979
|
+
willRetry: !1,
|
|
1980
|
+
errorMessage: aborted ? void 0 : `Compaction failed: ${message}`
|
|
1981
|
+
}), error;
|
|
1982
|
+
} finally {
|
|
1983
|
+
this._compactionAbortController = void 0, this._reconnectToAgent();
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Cancel in-progress compaction (manual or auto).
|
|
1988
|
+
*/
|
|
1989
|
+
abortCompaction() {
|
|
1990
|
+
this._compactionAbortController?.abort(), this._autoCompactionAbortController?.abort();
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Cancel in-progress branch summarization.
|
|
1994
|
+
*/
|
|
1995
|
+
abortBranchSummary() {
|
|
1996
|
+
this._branchSummaryAbortController?.abort();
|
|
1997
|
+
}
|
|
1998
|
+
/**
|
|
1999
|
+
* Check if compaction is needed and run it.
|
|
2000
|
+
* Called after agent_end and before prompt submission.
|
|
2001
|
+
*
|
|
2002
|
+
* Two cases:
|
|
2003
|
+
* 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry
|
|
2004
|
+
* 2. Threshold: Context over threshold, compact, NO auto-retry (user continues manually)
|
|
2005
|
+
*
|
|
2006
|
+
* @param assistantMessage The assistant message to check
|
|
2007
|
+
* @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
|
|
2008
|
+
*/
|
|
2009
|
+
async _checkCompaction(assistantMessage, skipAbortedCheck = !0) {
|
|
2010
|
+
let settings = this.settingsManager.getCompactionSettings();
|
|
2011
|
+
if (!settings.enabled || skipAbortedCheck && assistantMessage.stopReason === "aborted")
|
|
2012
|
+
return !1;
|
|
2013
|
+
let contextWindow = this.model?.contextWindow ?? 0, sameModel = this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id, compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
2014
|
+
if (compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime())
|
|
2015
|
+
return !1;
|
|
2016
|
+
if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
|
|
2017
|
+
let willRetry = assistantMessage.stopReason !== "stop";
|
|
2018
|
+
if (!willRetry)
|
|
2019
|
+
return await this._runAutoCompaction("overflow", !1);
|
|
2020
|
+
if (this._overflowRecoveryAttempted)
|
|
2021
|
+
return this._emit({
|
|
2022
|
+
type: "compaction_end",
|
|
2023
|
+
reason: "overflow",
|
|
2024
|
+
result: void 0,
|
|
2025
|
+
aborted: !1,
|
|
2026
|
+
willRetry: !1,
|
|
2027
|
+
errorMessage: "Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model."
|
|
2028
|
+
}), !1;
|
|
2029
|
+
this._overflowRecoveryAttempted = !0;
|
|
2030
|
+
let messages = this.agent.state.messages;
|
|
2031
|
+
return messages.length > 0 && messages[messages.length - 1].role === "assistant" && (this.agent.state.messages = messages.slice(0, -1)), await this._runAutoCompaction("overflow", willRetry);
|
|
2032
|
+
}
|
|
2033
|
+
let contextTokens, directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;
|
|
2034
|
+
if (assistantMessage.stopReason === "error" || directContextTokens === 0) {
|
|
2035
|
+
let messages = this.agent.state.messages, estimate = estimateContextTokens(messages);
|
|
2036
|
+
if (estimate.lastUsageIndex === null)
|
|
2037
|
+
return !1;
|
|
2038
|
+
let usageMsg = messages[estimate.lastUsageIndex];
|
|
2039
|
+
if (compactionEntry && usageMsg.role === "assistant" && usageMsg.timestamp <= new Date(compactionEntry.timestamp).getTime())
|
|
2040
|
+
return !1;
|
|
2041
|
+
contextTokens = estimate.tokens;
|
|
2042
|
+
} else
|
|
2043
|
+
contextTokens = directContextTokens;
|
|
2044
|
+
return shouldCompact(contextTokens, contextWindow, settings) ? await this._runAutoCompaction("threshold", !1) : !1;
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Internal: Run auto-compaction with events.
|
|
2048
|
+
*/
|
|
2049
|
+
async _runAutoCompaction(reason, willRetry) {
|
|
2050
|
+
if (!this.model || this._compactionAbortController || this._autoCompactionAbortController)
|
|
2051
|
+
return !1;
|
|
2052
|
+
let settings = this.settingsManager.getCompactionSettings(), started = !1;
|
|
2053
|
+
try {
|
|
2054
|
+
let apiKey, headers, env;
|
|
2055
|
+
if (this.agent.streamFn === streamSimple) {
|
|
2056
|
+
let authResult = await this._modelRuntime.getAuth(this.model);
|
|
2057
|
+
if (!authResult?.auth.apiKey)
|
|
2058
|
+
return !1;
|
|
2059
|
+
apiKey = authResult.auth.apiKey, headers = withoutDeletedHeaders(authResult.auth.headers), env = authResult.env;
|
|
2060
|
+
} else
|
|
2061
|
+
({ apiKey, headers, env } = await this._getSummarizationRequestAuth(this.model));
|
|
2062
|
+
let pathEntries = this.sessionManager.getBranch(), preparation = prepareCompaction(pathEntries, settings);
|
|
2063
|
+
if (!preparation)
|
|
2064
|
+
return !1;
|
|
2065
|
+
this._emit({ type: "compaction_start", reason }), this._autoCompactionAbortController = new AbortController(), started = !0;
|
|
2066
|
+
let extensionCompaction, fromExtension = !1;
|
|
2067
|
+
if (this._extensionRunner.hasHandlers("session_before_compact")) {
|
|
2068
|
+
let extensionResult = await this._extensionRunner.emit({
|
|
2069
|
+
type: "session_before_compact",
|
|
2070
|
+
preparation,
|
|
2071
|
+
branchEntries: pathEntries,
|
|
2072
|
+
customInstructions: void 0,
|
|
2073
|
+
reason,
|
|
2074
|
+
willRetry,
|
|
2075
|
+
signal: this._autoCompactionAbortController.signal
|
|
2076
|
+
});
|
|
2077
|
+
if (extensionResult?.cancel)
|
|
2078
|
+
return this._emit({
|
|
2079
|
+
type: "compaction_end",
|
|
2080
|
+
reason,
|
|
2081
|
+
result: void 0,
|
|
2082
|
+
aborted: !0,
|
|
2083
|
+
willRetry: !1
|
|
2084
|
+
}), !1;
|
|
2085
|
+
extensionResult?.compaction && (extensionCompaction = extensionResult.compaction, fromExtension = !0);
|
|
2086
|
+
}
|
|
2087
|
+
let summary, firstKeptEntryId, tokensBefore, details;
|
|
2088
|
+
if (extensionCompaction)
|
|
2089
|
+
summary = extensionCompaction.summary, firstKeptEntryId = extensionCompaction.firstKeptEntryId, tokensBefore = extensionCompaction.tokensBefore, details = extensionCompaction.details;
|
|
2090
|
+
else {
|
|
2091
|
+
let compactResult = await compact(preparation, this.model, apiKey, headers, void 0, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, env);
|
|
2092
|
+
summary = compactResult.summary, firstKeptEntryId = compactResult.firstKeptEntryId, tokensBefore = compactResult.tokensBefore, details = compactResult.details;
|
|
2093
|
+
}
|
|
2094
|
+
if (this._autoCompactionAbortController.signal.aborted)
|
|
2095
|
+
return this._emit({
|
|
2096
|
+
type: "compaction_end",
|
|
2097
|
+
reason,
|
|
2098
|
+
result: void 0,
|
|
2099
|
+
aborted: !0,
|
|
2100
|
+
willRetry: !1
|
|
2101
|
+
}), !1;
|
|
2102
|
+
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
2103
|
+
let newEntries = this.sessionManager.getEntries(), sessionContext = this.sessionManager.buildSessionContext();
|
|
2104
|
+
this.agent.state.messages = sessionContext.messages;
|
|
2105
|
+
let estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages), savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary);
|
|
2106
|
+
this._extensionRunner && savedCompactionEntry && await this._extensionRunner.emit({
|
|
2107
|
+
type: "session_compact",
|
|
2108
|
+
compactionEntry: savedCompactionEntry,
|
|
2109
|
+
fromExtension,
|
|
2110
|
+
reason,
|
|
2111
|
+
willRetry
|
|
2112
|
+
});
|
|
2113
|
+
let result = {
|
|
2114
|
+
summary,
|
|
2115
|
+
firstKeptEntryId,
|
|
2116
|
+
tokensBefore,
|
|
2117
|
+
estimatedTokensAfter,
|
|
2118
|
+
details
|
|
2119
|
+
};
|
|
2120
|
+
if (this._emit({ type: "compaction_end", reason, result, aborted: !1, willRetry }), willRetry) {
|
|
2121
|
+
let messages = this.agent.state.messages, lastMsg = messages[messages.length - 1];
|
|
2122
|
+
return lastMsg?.role === "assistant" && lastMsg.stopReason === "error" && (this.agent.state.messages = messages.slice(0, -1)), !0;
|
|
2123
|
+
}
|
|
2124
|
+
return this.agent.hasQueuedMessages();
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
let errorMessage = error instanceof Error ? error.message : "compaction failed";
|
|
2127
|
+
return started && this._emit({
|
|
2128
|
+
type: "compaction_end",
|
|
2129
|
+
reason,
|
|
2130
|
+
result: void 0,
|
|
2131
|
+
aborted: !1,
|
|
2132
|
+
willRetry: !1,
|
|
2133
|
+
errorMessage: reason === "overflow" ? `Context overflow recovery failed: ${errorMessage}` : `Auto-compaction failed: ${errorMessage}`
|
|
2134
|
+
}), !1;
|
|
2135
|
+
} finally {
|
|
2136
|
+
this._autoCompactionAbortController = void 0;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
2140
|
+
* Toggle auto-compaction setting.
|
|
2141
|
+
*/
|
|
2142
|
+
setAutoCompactionEnabled(enabled) {
|
|
2143
|
+
this.settingsManager.setCompactionEnabled(enabled);
|
|
2144
|
+
}
|
|
2145
|
+
/** Whether auto-compaction is enabled */
|
|
2146
|
+
get autoCompactionEnabled() {
|
|
2147
|
+
return this.settingsManager.getCompactionEnabled();
|
|
2148
|
+
}
|
|
2149
|
+
async bindExtensions(bindings) {
|
|
2150
|
+
bindings.uiContext !== void 0 && (this._extensionUIContext = bindings.uiContext), bindings.mode !== void 0 && (this._extensionMode = bindings.mode), bindings.commandContextActions !== void 0 && (this._extensionCommandContextActions = bindings.commandContextActions), bindings.abortHandler !== void 0 && (this._extensionAbortHandler = bindings.abortHandler), bindings.shutdownHandler !== void 0 && (this._extensionShutdownHandler = bindings.shutdownHandler), bindings.onError !== void 0 && (this._extensionErrorListener = bindings.onError), this._applyExtensionBindings(this._extensionRunner), await this._extensionRunner.emit(this._sessionStartEvent), await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup");
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Load extra inline factories into the current runner after first paint.
|
|
2154
|
+
* New factories receive the current session_start event so late tools/commands exist
|
|
2155
|
+
* before the first prompt. Already-bound extensions are not restarted.
|
|
2156
|
+
*/
|
|
2157
|
+
async attachInlineExtensions(factories) {
|
|
2158
|
+
if (factories.length === 0)
|
|
2159
|
+
return;
|
|
2160
|
+
let attach = this._resourceLoader.attachInlineExtensions;
|
|
2161
|
+
if (!attach)
|
|
2162
|
+
throw new Error("Resource loader does not support attaching inline extensions");
|
|
2163
|
+
let alreadyBound = new Set(this._extensionRunner.getExtensionPaths()), fresh = (await attach.call(this._resourceLoader, factories)).extensions.filter((extension) => !alreadyBound.has(extension.path));
|
|
2164
|
+
fresh.length !== 0 && (this._extensionRunner.appendExtensions(fresh), this._applyExtensionBindings(this._extensionRunner), this.applyDeferredExtensionFlags(fresh), await this.emitSessionStartForExtensions(fresh), await this.extendResourcesFromExtensions(this._sessionStartEvent.reason === "reload" ? "reload" : "startup"), this._refreshToolRegistry({
|
|
2165
|
+
activeToolNames: this.getActiveToolNames(),
|
|
2166
|
+
includeAllExtensionTools: !0
|
|
2167
|
+
}));
|
|
2168
|
+
}
|
|
2169
|
+
applyDeferredExtensionFlags(extensions) {
|
|
2170
|
+
let runtime = this._resourceLoader.getExtensions().runtime, argv = process.argv;
|
|
2171
|
+
for (let ext of extensions)
|
|
2172
|
+
for (let [name, flag] of ext.flags) {
|
|
2173
|
+
let prefix = `--${name}`, eqArg = argv.find((arg) => arg.startsWith(`${prefix}=`));
|
|
2174
|
+
if (eqArg) {
|
|
2175
|
+
runtime.flagValues.set(name, eqArg.slice(prefix.length + 1));
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
let idx = argv.indexOf(prefix);
|
|
2179
|
+
if (idx < 0)
|
|
2180
|
+
continue;
|
|
2181
|
+
let next = argv[idx + 1];
|
|
2182
|
+
flag.type === "string" && next && !next.startsWith("-") ? runtime.flagValues.set(name, next) : runtime.flagValues.set(name, !0);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
async emitSessionStartForExtensions(extensions) {
|
|
2186
|
+
let ctx = this._extensionRunner.createContext();
|
|
2187
|
+
for (let ext of extensions) {
|
|
2188
|
+
let handlers = ext.handlers.get("session_start");
|
|
2189
|
+
if (handlers)
|
|
2190
|
+
for (let handler of handlers)
|
|
2191
|
+
try {
|
|
2192
|
+
await measureStartup(`${ext.path} session_start`, () => handler(this._sessionStartEvent, ctx), "lifecycle");
|
|
2193
|
+
} catch (err) {
|
|
2194
|
+
let message = err instanceof Error ? err.message : String(err), stack = err instanceof Error ? err.stack : void 0;
|
|
2195
|
+
this._extensionRunner.emitError({
|
|
2196
|
+
extensionPath: ext.path,
|
|
2197
|
+
event: "session_start",
|
|
2198
|
+
error: message,
|
|
2199
|
+
stack
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
async extendResourcesFromExtensions(reason) {
|
|
2205
|
+
if (!this._extensionRunner.hasHandlers("resources_discover"))
|
|
2206
|
+
return;
|
|
2207
|
+
let { skillPaths, promptPaths, themePaths } = await this._extensionRunner.emitResourcesDiscover(this._cwd, reason);
|
|
2208
|
+
if (skillPaths.length === 0 && promptPaths.length === 0 && themePaths.length === 0)
|
|
2209
|
+
return;
|
|
2210
|
+
let extensionPaths = {
|
|
2211
|
+
skillPaths: this.buildExtensionResourcePaths(skillPaths),
|
|
2212
|
+
promptPaths: this.buildExtensionResourcePaths(promptPaths),
|
|
2213
|
+
themePaths: this.buildExtensionResourcePaths(themePaths)
|
|
2214
|
+
};
|
|
2215
|
+
this._resourceLoader.extendResources(extensionPaths), this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames()), this.agent.state.systemPrompt = this._withSystemPromptAppend(this._baseSystemPrompt);
|
|
2216
|
+
}
|
|
2217
|
+
buildExtensionResourcePaths(entries) {
|
|
2218
|
+
return entries.map((entry) => {
|
|
2219
|
+
let source = this.getExtensionSourceLabel(entry.extensionPath), baseDir = entry.extensionPath.startsWith("<") ? void 0 : dirname2(entry.extensionPath);
|
|
2220
|
+
return {
|
|
2221
|
+
path: entry.path,
|
|
2222
|
+
metadata: {
|
|
2223
|
+
source,
|
|
2224
|
+
scope: "temporary",
|
|
2225
|
+
origin: "top-level",
|
|
2226
|
+
baseDir
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
getExtensionSourceLabel(extensionPath) {
|
|
2232
|
+
return extensionPath.startsWith("<") ? `extension:${extensionPath.replace(/[<>]/g, "")}` : `extension:${basename(extensionPath).replace(/\.(ts|js)$/, "")}`;
|
|
2233
|
+
}
|
|
2234
|
+
_applyExtensionBindings(runner) {
|
|
2235
|
+
runner.setUIContext(this._extensionUIContext, this._extensionMode), runner.bindCommandContext(this._extensionCommandContextActions), this._extensionErrorUnsubscriber?.(), this._extensionErrorUnsubscriber = this._extensionErrorListener ? runner.onError(this._extensionErrorListener) : void 0;
|
|
2236
|
+
}
|
|
2237
|
+
_refreshCurrentModelFromRegistry() {
|
|
2238
|
+
let currentModel = this.model;
|
|
2239
|
+
if (!currentModel)
|
|
2240
|
+
return;
|
|
2241
|
+
let refreshedModel = this._modelRuntime.getModel(currentModel.provider, currentModel.id);
|
|
2242
|
+
!refreshedModel || refreshedModel === currentModel || (this.agent.state.model = refreshedModel, this._syncOpenAIServiceTier());
|
|
2243
|
+
}
|
|
2244
|
+
_bindExtensionCore(runner) {
|
|
2245
|
+
let getCommands = () => {
|
|
2246
|
+
let extensionCommands = runner.getRegisteredCommands().map((command) => ({
|
|
2247
|
+
name: command.invocationName,
|
|
2248
|
+
description: command.description,
|
|
2249
|
+
source: "extension",
|
|
2250
|
+
sourceInfo: command.sourceInfo
|
|
2251
|
+
})), templates = this.promptTemplates.map((template) => ({
|
|
2252
|
+
name: template.name,
|
|
2253
|
+
description: template.description,
|
|
2254
|
+
source: "prompt",
|
|
2255
|
+
sourceInfo: template.sourceInfo
|
|
2256
|
+
})), skills = this._resourceLoader.getSkills().skills.map((skill) => ({
|
|
2257
|
+
name: `skill:${skill.name}`,
|
|
2258
|
+
description: skill.description,
|
|
2259
|
+
source: "skill",
|
|
2260
|
+
sourceInfo: skill.sourceInfo
|
|
2261
|
+
}));
|
|
2262
|
+
return [...extensionCommands, ...templates, ...skills];
|
|
2263
|
+
};
|
|
2264
|
+
runner.bindCore({
|
|
2265
|
+
sendMessage: (message, options) => {
|
|
2266
|
+
this.sendCustomMessage(message, options).catch((err) => {
|
|
2267
|
+
runner.emitError({
|
|
2268
|
+
extensionPath: "<runtime>",
|
|
2269
|
+
event: "send_message",
|
|
2270
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2271
|
+
});
|
|
2272
|
+
});
|
|
2273
|
+
},
|
|
2274
|
+
sendUserMessage: (content, options) => {
|
|
2275
|
+
this.sendUserMessage(content, options).catch((err) => {
|
|
2276
|
+
runner.emitError({
|
|
2277
|
+
extensionPath: "<runtime>",
|
|
2278
|
+
event: "send_user_message",
|
|
2279
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2280
|
+
});
|
|
2281
|
+
});
|
|
2282
|
+
},
|
|
2283
|
+
appendEntry: (customType, data) => {
|
|
2284
|
+
let entryId = this.sessionManager.appendCustomEntry(customType, data), entry = this.sessionManager.getEntry(entryId);
|
|
2285
|
+
entry && this._emit({ type: "entry_appended", entry });
|
|
2286
|
+
},
|
|
2287
|
+
setSessionName: (name) => {
|
|
2288
|
+
this.setSessionName(name);
|
|
2289
|
+
},
|
|
2290
|
+
getSessionName: () => this.sessionManager.getSessionName(),
|
|
2291
|
+
setLabel: (entryId, label) => {
|
|
2292
|
+
this.sessionManager.appendLabelChange(entryId, label);
|
|
2293
|
+
},
|
|
2294
|
+
getActiveTools: () => this.getActiveToolNames(),
|
|
2295
|
+
getAllTools: () => this.getAllTools(),
|
|
2296
|
+
setActiveTools: (toolNames) => this.setActiveToolsByName(toolNames),
|
|
2297
|
+
refreshTools: () => this._refreshToolRegistry(),
|
|
2298
|
+
getCommands,
|
|
2299
|
+
setModel: async (model) => this._modelRuntime.hasConfiguredAuth(model.provider) ? (await this.setModel(model), !0) : !1,
|
|
2300
|
+
getThinkingLevel: () => this.thinkingLevel,
|
|
2301
|
+
setThinkingLevel: (level) => this.setThinkingLevel(level)
|
|
2302
|
+
}, {
|
|
2303
|
+
getModel: () => this.resolvedCatalogModel(),
|
|
2304
|
+
isIdle: () => this.isIdle,
|
|
2305
|
+
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
2306
|
+
getSignal: () => this.agent.signal,
|
|
2307
|
+
abort: () => {
|
|
2308
|
+
if (this._extensionAbortHandler) {
|
|
2309
|
+
this._extensionAbortHandler();
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
this.abort();
|
|
2313
|
+
},
|
|
2314
|
+
hasPendingMessages: () => this.pendingMessageCount > 0 || this.agent.hasQueuedMessages(),
|
|
2315
|
+
shutdown: () => {
|
|
2316
|
+
this._extensionShutdownHandler?.();
|
|
2317
|
+
},
|
|
2318
|
+
getContextUsage: () => this.getContextUsage(),
|
|
2319
|
+
compact: (options) => {
|
|
2320
|
+
(async () => {
|
|
2321
|
+
try {
|
|
2322
|
+
let result = await this.compact(options?.customInstructions);
|
|
2323
|
+
options?.onComplete?.(result);
|
|
2324
|
+
} catch (error) {
|
|
2325
|
+
let err = error instanceof Error ? error : new Error(String(error));
|
|
2326
|
+
options?.onError?.(err);
|
|
2327
|
+
}
|
|
2328
|
+
})();
|
|
2329
|
+
},
|
|
2330
|
+
getSystemPrompt: () => this.systemPrompt,
|
|
2331
|
+
getSystemPromptOptions: () => this._baseSystemPromptOptions
|
|
2332
|
+
}, {
|
|
2333
|
+
registerProvider: (name, config) => {
|
|
2334
|
+
this._modelRuntime.registerProvider(name, config), this._refreshCurrentModelFromRegistry();
|
|
2335
|
+
},
|
|
2336
|
+
unregisterProvider: (name) => {
|
|
2337
|
+
this._modelRuntime.unregisterProvider(name), this._refreshCurrentModelFromRegistry();
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
_refreshToolRegistry(options) {
|
|
2342
|
+
let previousRegistryNames = new Set(this._toolRegistry.keys()), previousActiveToolNames = this.getActiveToolNames(), allowedToolNames = this._allowedToolNames, excludedToolNames = this._excludedToolNames, isAllowedTool = (name) => (!allowedToolNames || allowedToolNames.has(name)) && !excludedToolNames?.has(name) && (this.settingsManager.getMemoryEnabled() || !MEMORY_TOOL_NAMES.has(name)) && (this.settingsManager.getTodosEnabled() || !TODO_TOOL_NAMES.has(name)) && (name !== "browser" || this.settingsManager.getBrowserEnabled()), allCustomTools = [
|
|
2343
|
+
...this._extensionRunner.getAllRegisteredTools(),
|
|
2344
|
+
...this._customTools.map((definition) => ({
|
|
2345
|
+
definition,
|
|
2346
|
+
sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: "sdk" })
|
|
2347
|
+
}))
|
|
2348
|
+
].filter((tool) => isAllowedTool(tool.definition.name)), definitionRegistry = new Map(Array.from(this._baseToolDefinitions.entries()).filter(([name]) => isAllowedTool(name)).map(([name, definition]) => [
|
|
2349
|
+
name,
|
|
2350
|
+
{
|
|
2351
|
+
definition,
|
|
2352
|
+
sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: "builtin" })
|
|
2353
|
+
}
|
|
2354
|
+
]));
|
|
2355
|
+
for (let tool of allCustomTools)
|
|
2356
|
+
definitionRegistry.set(tool.definition.name, {
|
|
2357
|
+
definition: tool.definition,
|
|
2358
|
+
sourceInfo: tool.sourceInfo
|
|
2359
|
+
});
|
|
2360
|
+
this._toolDefinitions = definitionRegistry, this._toolPromptSnippets = new Map(Array.from(definitionRegistry.values()).map(({ definition }) => {
|
|
2361
|
+
let snippet = this._normalizePromptSnippet(definition.promptSnippet);
|
|
2362
|
+
return snippet ? [definition.name, snippet] : void 0;
|
|
2363
|
+
}).filter((entry) => entry !== void 0)), this._toolPromptGuidelines = new Map(Array.from(definitionRegistry.values()).map(({ definition }) => {
|
|
2364
|
+
let guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);
|
|
2365
|
+
return guidelines.length > 0 ? [definition.name, guidelines] : void 0;
|
|
2366
|
+
}).filter((entry) => entry !== void 0));
|
|
2367
|
+
let runner = this._extensionRunner, wrappedExtensionTools = wrapRegisteredTools(allCustomTools, runner), wrappedBuiltInTools = wrapRegisteredTools(Array.from(this._baseToolDefinitions.values()).filter((definition) => isAllowedTool(definition.name)).map((definition) => ({
|
|
2368
|
+
definition,
|
|
2369
|
+
sourceInfo: createSyntheticSourceInfo(`<builtin:${definition.name}>`, { source: "builtin" })
|
|
2370
|
+
})), runner), toolRegistry = new Map(wrappedBuiltInTools.map((tool) => [tool.name, tool]));
|
|
2371
|
+
for (let tool of wrappedExtensionTools)
|
|
2372
|
+
toolRegistry.set(tool.name, tool);
|
|
2373
|
+
this._toolRegistry = toolRegistry;
|
|
2374
|
+
let nextActiveToolNames = (options?.activeToolNames ? [...options.activeToolNames] : [...previousActiveToolNames]).filter((name) => isAllowedTool(name));
|
|
2375
|
+
if (allowedToolNames)
|
|
2376
|
+
for (let toolName of this._toolRegistry.keys())
|
|
2377
|
+
allowedToolNames.has(toolName) && nextActiveToolNames.push(toolName);
|
|
2378
|
+
else if (options?.includeAllExtensionTools)
|
|
2379
|
+
for (let tool of wrappedExtensionTools)
|
|
2380
|
+
nextActiveToolNames.push(tool.name);
|
|
2381
|
+
else if (!options?.activeToolNames)
|
|
2382
|
+
for (let toolName of this._toolRegistry.keys())
|
|
2383
|
+
previousRegistryNames.has(toolName) || nextActiveToolNames.push(toolName);
|
|
2384
|
+
this.setActiveToolsByName([...new Set(nextActiveToolNames)]);
|
|
2385
|
+
}
|
|
2386
|
+
_buildRuntime(options) {
|
|
2387
|
+
let autoResizeImages = this.settingsManager.getImageAutoResize(), shellCommandPrefix = this.settingsManager.getShellCommandPrefix(), shellPath = this.settingsManager.getShellPath(), baseToolDefinitions = this._baseToolsOverride ? Object.fromEntries(Object.entries(this._baseToolsOverride).map(([name, tool]) => [
|
|
2388
|
+
name,
|
|
2389
|
+
createToolDefinitionFromAgentTool(tool)
|
|
2390
|
+
])) : createAllToolDefinitions(this._cwd, {
|
|
2391
|
+
read: { autoResizeImages },
|
|
2392
|
+
bash: { commandPrefix: shellCommandPrefix, shellPath }
|
|
2393
|
+
});
|
|
2394
|
+
this._baseToolDefinitions = new Map(Object.entries(baseToolDefinitions).map(([name, tool]) => [name, tool]));
|
|
2395
|
+
let extensionsResult = this._resourceLoader.getExtensions();
|
|
2396
|
+
if (options.flagValues)
|
|
2397
|
+
for (let [name, value] of options.flagValues)
|
|
2398
|
+
extensionsResult.runtime.flagValues.set(name, value);
|
|
2399
|
+
this._extensionRunner = new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, new ModelRegistry(this._modelRuntime)), this._extensionRunnerRef && (this._extensionRunnerRef.current = this._extensionRunner), this._bindExtensionCore(this._extensionRunner), this._applyExtensionBindings(this._extensionRunner);
|
|
2400
|
+
let defaultActiveToolNames = this._baseToolsOverride ? Object.keys(this._baseToolsOverride) : ["read", "bash", "edit", "write"], baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
|
|
2401
|
+
this._refreshToolRegistry({
|
|
2402
|
+
activeToolNames: baseActiveToolNames,
|
|
2403
|
+
includeAllExtensionTools: options.includeAllExtensionTools
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
async reload(options) {
|
|
2407
|
+
this._invalidateWaitPromptHandoffs("Extensions reloaded before the pending prompt could run.");
|
|
2408
|
+
let previousFlagValues = this._extensionRunner.getFlagValues();
|
|
2409
|
+
await emitSessionShutdownEvent(this._extensionRunner, { type: "session_shutdown", reason: "reload" }), await this.settingsManager.reload(), this.syncQueueModesFromSettings(), resetApiProviders(), await this._resourceLoader.reload(), this._buildRuntime({
|
|
2410
|
+
activeToolNames: this.getActiveToolNames(),
|
|
2411
|
+
flagValues: previousFlagValues,
|
|
2412
|
+
includeAllExtensionTools: !0
|
|
2413
|
+
}), (this._extensionUIContext || this._extensionCommandContextActions || this._extensionShutdownHandler || this._extensionErrorListener) && (await options?.beforeSessionStart?.(), await this._extensionRunner.emit({ type: "session_start", reason: "reload" }), await this.extendResourcesFromExtensions("reload"));
|
|
2414
|
+
}
|
|
2415
|
+
// =========================================================================
|
|
2416
|
+
// Auto-Retry
|
|
2417
|
+
// =========================================================================
|
|
2418
|
+
/**
|
|
2419
|
+
* Check if an error is retryable (overloaded, rate limit, server errors).
|
|
2420
|
+
* Context overflow errors are NOT retryable (handled by compaction instead).
|
|
2421
|
+
*/
|
|
2422
|
+
_isRetryableError(message) {
|
|
2423
|
+
return isContextOverflow(message, this.model?.contextWindow ?? 0) ? !1 : isRetryableAssistantError(message);
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Prepare a retryable error for continuation with exponential backoff.
|
|
2427
|
+
* @returns true if the caller should continue the agent, false otherwise
|
|
2428
|
+
*/
|
|
2429
|
+
async _prepareRetry(message) {
|
|
2430
|
+
let settings = this.settingsManager.getRetrySettings();
|
|
2431
|
+
if (!settings.enabled)
|
|
2432
|
+
return !1;
|
|
2433
|
+
if (this._retryAttempt++, this._retryAttempt > settings.maxRetries)
|
|
2434
|
+
return this._retryAttempt--, !1;
|
|
2435
|
+
let delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
|
|
2436
|
+
this._emit({
|
|
2437
|
+
type: "auto_retry_start",
|
|
2438
|
+
attempt: this._retryAttempt,
|
|
2439
|
+
maxAttempts: settings.maxRetries,
|
|
2440
|
+
delayMs,
|
|
2441
|
+
errorMessage: message.errorMessage || "Unknown error"
|
|
2442
|
+
});
|
|
2443
|
+
let messages = this.agent.state.messages;
|
|
2444
|
+
messages.length > 0 && messages[messages.length - 1].role === "assistant" && (this.agent.state.messages = messages.slice(0, -1)), this._retryAbortController = new AbortController();
|
|
2445
|
+
try {
|
|
2446
|
+
await sleep(delayMs, this._retryAbortController.signal);
|
|
2447
|
+
} catch {
|
|
2448
|
+
let attempt = this._retryAttempt;
|
|
2449
|
+
return this._retryAttempt = 0, this._emit({
|
|
2450
|
+
type: "auto_retry_end",
|
|
2451
|
+
success: !1,
|
|
2452
|
+
attempt,
|
|
2453
|
+
finalError: "Retry cancelled"
|
|
2454
|
+
}), !1;
|
|
2455
|
+
} finally {
|
|
2456
|
+
this._retryAbortController = void 0;
|
|
2457
|
+
}
|
|
2458
|
+
return !0;
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* Cancel in-progress retry.
|
|
2462
|
+
*/
|
|
2463
|
+
abortRetry() {
|
|
2464
|
+
this._retryAbortController?.abort();
|
|
2465
|
+
}
|
|
2466
|
+
/** lunr: errors that mark a subscription key dead — rotate without same-key backoff. */
|
|
2467
|
+
_isRotationError(message) {
|
|
2468
|
+
let errorMessage = message.errorMessage ?? "";
|
|
2469
|
+
return isUsageLimitError(errorMessage) || isAuthInvalidError(errorMessage);
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* lunr: multi-subscription rotation. When a run is about to fail final, switch
|
|
2473
|
+
* the provider to the next pooled key (mirrored into the credential store by
|
|
2474
|
+
* SubscriptionManager) and continue immediately — no backoff sleep. Returns
|
|
2475
|
+
* false when rotation is impossible, leaving the error as the final message.
|
|
2476
|
+
*/
|
|
2477
|
+
async _tryRotateSubscriptionKey(message) {
|
|
2478
|
+
let model = this.model;
|
|
2479
|
+
if (!model)
|
|
2480
|
+
return !1;
|
|
2481
|
+
let providerId = model.provider;
|
|
2482
|
+
if (this._modelRuntime.hasRuntimeApiKey(providerId))
|
|
2483
|
+
return !1;
|
|
2484
|
+
let manager = this._modelRuntime.subscriptionManager;
|
|
2485
|
+
if ((await manager.list(providerId)).length < 2)
|
|
2486
|
+
return !1;
|
|
2487
|
+
let errorMessage = message.errorMessage ?? "Unknown error", entry = await manager.rotateOnFailure(providerId, errorMessage);
|
|
2488
|
+
if (!entry)
|
|
2489
|
+
return !1;
|
|
2490
|
+
this._retryAttempt > 0 && this._emit({ type: "auto_retry_end", success: !0, attempt: this._retryAttempt }), this._retryAttempt = 0;
|
|
2491
|
+
let messages = this.agent.state.messages;
|
|
2492
|
+
return messages.length > 0 && messages[messages.length - 1].role === "assistant" && (this.agent.state.messages = messages.slice(0, -1)), this._emit({
|
|
2493
|
+
type: "subscription_rotation",
|
|
2494
|
+
providerId,
|
|
2495
|
+
keyName: entry.name,
|
|
2496
|
+
errorMessage
|
|
2497
|
+
}), !0;
|
|
2498
|
+
}
|
|
2499
|
+
/** Whether auto-retry is currently in progress */
|
|
2500
|
+
get isRetrying() {
|
|
2501
|
+
return this._retryAbortController !== void 0;
|
|
2502
|
+
}
|
|
2503
|
+
/** Whether auto-retry is enabled */
|
|
2504
|
+
get autoRetryEnabled() {
|
|
2505
|
+
return this.settingsManager.getRetryEnabled();
|
|
2506
|
+
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Toggle auto-retry setting.
|
|
2509
|
+
*/
|
|
2510
|
+
setAutoRetryEnabled(enabled) {
|
|
2511
|
+
this.settingsManager.setRetryEnabled(enabled);
|
|
2512
|
+
}
|
|
2513
|
+
// =========================================================================
|
|
2514
|
+
// Bash Execution
|
|
2515
|
+
// =========================================================================
|
|
2516
|
+
/**
|
|
2517
|
+
* Execute a bash command.
|
|
2518
|
+
* Adds result to agent context and session.
|
|
2519
|
+
* @param command The bash command to execute
|
|
2520
|
+
* @param onChunk Optional streaming callback for output
|
|
2521
|
+
* @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
|
|
2522
|
+
* @param options.operations Custom BashOperations for remote execution
|
|
2523
|
+
*/
|
|
2524
|
+
async executeBash(command, onChunk, options) {
|
|
2525
|
+
this.assertCanStartWork(), this._bashAbortController = new AbortController();
|
|
2526
|
+
let prefix = this.settingsManager.getShellCommandPrefix(), shellPath = this.settingsManager.getShellPath(), resolvedCommand = prefix ? `${prefix}
|
|
2527
|
+
${command}` : command;
|
|
2528
|
+
try {
|
|
2529
|
+
let result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ?? createLocalBashOperations({ shellPath, sessionId: this.sessionId }), {
|
|
2530
|
+
onChunk,
|
|
2531
|
+
signal: this._bashAbortController.signal
|
|
2532
|
+
});
|
|
2533
|
+
return this.recordBashResult(command, result, options), result;
|
|
2534
|
+
} finally {
|
|
2535
|
+
this._bashAbortController = void 0;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Record a bash execution result in session history.
|
|
2540
|
+
* Used by executeBash and by extensions that handle bash execution themselves.
|
|
2541
|
+
*/
|
|
2542
|
+
recordBashResult(command, result, options) {
|
|
2543
|
+
let bashMessage = {
|
|
2544
|
+
role: "bashExecution",
|
|
2545
|
+
command,
|
|
2546
|
+
output: result.output,
|
|
2547
|
+
exitCode: result.exitCode,
|
|
2548
|
+
cancelled: result.cancelled,
|
|
2549
|
+
truncated: result.truncated,
|
|
2550
|
+
fullOutputPath: result.fullOutputPath,
|
|
2551
|
+
timestamp: Date.now(),
|
|
2552
|
+
excludeFromContext: options?.excludeFromContext
|
|
2553
|
+
};
|
|
2554
|
+
this.isStreaming ? this._pendingBashMessages.push(bashMessage) : (this.agent.state.messages.push(bashMessage), this.sessionManager.appendMessage(bashMessage));
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* Cancel running bash command.
|
|
2558
|
+
*/
|
|
2559
|
+
abortBash() {
|
|
2560
|
+
this._bashAbortController?.abort();
|
|
2561
|
+
}
|
|
2562
|
+
/** Whether a bash command is currently running */
|
|
2563
|
+
get isBashRunning() {
|
|
2564
|
+
return this._bashAbortController !== void 0;
|
|
2565
|
+
}
|
|
2566
|
+
/** Whether there are pending bash messages waiting to be flushed */
|
|
2567
|
+
get hasPendingBashMessages() {
|
|
2568
|
+
return this._pendingBashMessages.length > 0;
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Flush pending bash messages to agent state and session.
|
|
2572
|
+
* Called after agent turn completes to maintain proper message ordering.
|
|
2573
|
+
*/
|
|
2574
|
+
_flushPendingBashMessages() {
|
|
2575
|
+
if (this._pendingBashMessages.length !== 0) {
|
|
2576
|
+
for (let bashMessage of this._pendingBashMessages)
|
|
2577
|
+
this.agent.state.messages.push(bashMessage), this.sessionManager.appendMessage(bashMessage);
|
|
2578
|
+
this._pendingBashMessages = [];
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
// =========================================================================
|
|
2582
|
+
// Session Management
|
|
2583
|
+
// =========================================================================
|
|
2584
|
+
/**
|
|
2585
|
+
* Set a display name for the current session.
|
|
2586
|
+
*/
|
|
2587
|
+
setSessionName(name) {
|
|
2588
|
+
this.sessionManager.appendSessionInfo(name);
|
|
2589
|
+
let event = { type: "session_info_changed", name: this.sessionManager.getSessionName() };
|
|
2590
|
+
this._emit(event), this._extensionRunner.emit(event);
|
|
2591
|
+
}
|
|
2592
|
+
// =========================================================================
|
|
2593
|
+
// Tree Navigation
|
|
2594
|
+
// =========================================================================
|
|
2595
|
+
/**
|
|
2596
|
+
* Navigate to a different node in the session tree.
|
|
2597
|
+
* Unlike fork() which creates a new session file, this stays in the same file.
|
|
2598
|
+
*
|
|
2599
|
+
* @param targetId The entry ID to navigate to
|
|
2600
|
+
* @param options.summarize Whether user wants to summarize abandoned branch
|
|
2601
|
+
* @param options.customInstructions Custom instructions for summarizer
|
|
2602
|
+
* @param options.replaceInstructions If true, customInstructions replaces the default prompt
|
|
2603
|
+
* @param options.label Label to attach to the branch summary entry
|
|
2604
|
+
* @returns Result with editorText (if user message) and cancelled status
|
|
2605
|
+
*/
|
|
2606
|
+
async navigateTree(targetId, options = {}) {
|
|
2607
|
+
let oldLeafId = this.sessionManager.getLeafId();
|
|
2608
|
+
if (targetId === oldLeafId)
|
|
2609
|
+
return { cancelled: !1 };
|
|
2610
|
+
if (options.summarize && !this.model)
|
|
2611
|
+
throw new Error("No model available for summarization");
|
|
2612
|
+
let targetEntry = this.sessionManager.getEntry(targetId);
|
|
2613
|
+
if (!targetEntry)
|
|
2614
|
+
throw new Error(`Entry ${targetId} not found`);
|
|
2615
|
+
let { entries: entriesToSummarize, commonAncestorId } = collectEntriesForBranchSummary(this.sessionManager, oldLeafId, targetId), customInstructions = options.customInstructions, replaceInstructions = options.replaceInstructions, label = options.label, preparation = {
|
|
2616
|
+
targetId,
|
|
2617
|
+
oldLeafId,
|
|
2618
|
+
commonAncestorId,
|
|
2619
|
+
entriesToSummarize,
|
|
2620
|
+
userWantsSummary: options.summarize ?? !1,
|
|
2621
|
+
customInstructions,
|
|
2622
|
+
replaceInstructions,
|
|
2623
|
+
label
|
|
2624
|
+
};
|
|
2625
|
+
this._branchSummaryAbortController = new AbortController();
|
|
2626
|
+
try {
|
|
2627
|
+
let extensionSummary, fromExtension = !1;
|
|
2628
|
+
if (this._extensionRunner.hasHandlers("session_before_tree")) {
|
|
2629
|
+
let result = await this._extensionRunner.emit({
|
|
2630
|
+
type: "session_before_tree",
|
|
2631
|
+
preparation,
|
|
2632
|
+
signal: this._branchSummaryAbortController.signal
|
|
2633
|
+
});
|
|
2634
|
+
if (result?.cancel)
|
|
2635
|
+
return { cancelled: !0 };
|
|
2636
|
+
result?.summary && options.summarize && (extensionSummary = result.summary, fromExtension = !0), result?.customInstructions !== void 0 && (customInstructions = result.customInstructions), result?.replaceInstructions !== void 0 && (replaceInstructions = result.replaceInstructions), result?.label !== void 0 && (label = result.label);
|
|
2637
|
+
}
|
|
2638
|
+
let summaryText, summaryDetails;
|
|
2639
|
+
if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
|
|
2640
|
+
let model = this.model, { apiKey, headers, env } = await this._getSummarizationRequestAuth(model), branchSummarySettings = this.settingsManager.getBranchSummarySettings(), result = await generateBranchSummary(entriesToSummarize, {
|
|
2641
|
+
model,
|
|
2642
|
+
apiKey,
|
|
2643
|
+
headers,
|
|
2644
|
+
env,
|
|
2645
|
+
signal: this._branchSummaryAbortController.signal,
|
|
2646
|
+
customInstructions,
|
|
2647
|
+
replaceInstructions,
|
|
2648
|
+
reserveTokens: branchSummarySettings.reserveTokens,
|
|
2649
|
+
streamFn: this.agent.streamFn
|
|
2650
|
+
});
|
|
2651
|
+
if (result.aborted)
|
|
2652
|
+
return { cancelled: !0, aborted: !0 };
|
|
2653
|
+
if (result.error)
|
|
2654
|
+
throw new Error(result.error);
|
|
2655
|
+
summaryText = result.summary, summaryDetails = {
|
|
2656
|
+
readFiles: result.readFiles || [],
|
|
2657
|
+
modifiedFiles: result.modifiedFiles || []
|
|
2658
|
+
};
|
|
2659
|
+
} else extensionSummary && (summaryText = extensionSummary.summary, summaryDetails = extensionSummary.details);
|
|
2660
|
+
let newLeafId, editorText, editorImages;
|
|
2661
|
+
targetEntry.type === "message" && targetEntry.message.role === "user" ? (newLeafId = targetEntry.parentId, editorText = this._extractUserMessageText(targetEntry.message.content), editorImages = this._extractUserMessageImages(targetEntry.message.content)) : targetEntry.type === "custom_message" ? (newLeafId = targetEntry.parentId, editorText = typeof targetEntry.content == "string" ? targetEntry.content : targetEntry.content.filter((c) => c.type === "text").map((c) => c.text).join("")) : newLeafId = targetId;
|
|
2662
|
+
let summaryEntry;
|
|
2663
|
+
if (summaryText) {
|
|
2664
|
+
let summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
|
|
2665
|
+
summaryEntry = this.sessionManager.getEntry(summaryId), label && this.sessionManager.appendLabelChange(summaryId, label);
|
|
2666
|
+
} else newLeafId === null ? this.sessionManager.resetLeaf() : this.sessionManager.branch(newLeafId);
|
|
2667
|
+
label && !summaryText && this.sessionManager.appendLabelChange(targetId, label);
|
|
2668
|
+
let sessionContext = this.sessionManager.buildSessionContext();
|
|
2669
|
+
return this.agent.state.messages = sessionContext.messages, await this._extensionRunner.emit({
|
|
2670
|
+
type: "session_tree",
|
|
2671
|
+
newLeafId: this.sessionManager.getLeafId(),
|
|
2672
|
+
oldLeafId,
|
|
2673
|
+
summaryEntry,
|
|
2674
|
+
fromExtension: summaryText ? fromExtension : void 0
|
|
2675
|
+
}), { editorText, editorImages, cancelled: !1, summaryEntry };
|
|
2676
|
+
} finally {
|
|
2677
|
+
this._branchSummaryAbortController = void 0;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Get all user messages from session for fork selector.
|
|
2682
|
+
*/
|
|
2683
|
+
getUserMessagesForForking() {
|
|
2684
|
+
let entries = this.sessionManager.getEntries(), result = [];
|
|
2685
|
+
for (let entry of entries) {
|
|
2686
|
+
if (entry.type !== "message" || entry.message.role !== "user")
|
|
2687
|
+
continue;
|
|
2688
|
+
let text = this._extractUserMessageText(entry.message.content);
|
|
2689
|
+
text && result.push({ entryId: entry.id, text });
|
|
2690
|
+
}
|
|
2691
|
+
return result;
|
|
2692
|
+
}
|
|
2693
|
+
_extractUserMessageText(content) {
|
|
2694
|
+
return typeof content == "string" ? content : Array.isArray(content) ? content.filter((c) => c.type === "text").map((c) => c.text).join("") : "";
|
|
2695
|
+
}
|
|
2696
|
+
_extractUserMessageImages(content) {
|
|
2697
|
+
if (typeof content == "string" || !Array.isArray(content))
|
|
2698
|
+
return;
|
|
2699
|
+
let images = content.filter((part) => part.type === "image" && typeof part.data == "string" && typeof part.mimeType == "string");
|
|
2700
|
+
return images.length > 0 ? images : void 0;
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* Get session statistics. Aggregates over ALL session entries (including
|
|
2704
|
+
* history that was compacted away), so token/cost totals reflect what was
|
|
2705
|
+
* actually billed across the session.
|
|
2706
|
+
*/
|
|
2707
|
+
getSessionStats() {
|
|
2708
|
+
let userMessages = 0, assistantMessages = 0, toolResults = 0, totalMessages = 0, toolCalls = 0, totalInput = 0, totalOutput = 0, totalCacheRead = 0, totalCacheWrite = 0, totalCost = 0;
|
|
2709
|
+
for (let entry of this.sessionManager.getEntries()) {
|
|
2710
|
+
if (entry.type !== "message")
|
|
2711
|
+
continue;
|
|
2712
|
+
totalMessages++;
|
|
2713
|
+
let message = entry.message;
|
|
2714
|
+
if (message.role === "user")
|
|
2715
|
+
userMessages++;
|
|
2716
|
+
else if (message.role === "toolResult")
|
|
2717
|
+
toolResults++;
|
|
2718
|
+
else if (message.role === "assistant") {
|
|
2719
|
+
assistantMessages++;
|
|
2720
|
+
let assistantMsg = message;
|
|
2721
|
+
Array.isArray(assistantMsg.content) && (toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length);
|
|
2722
|
+
let usage = assistantMsg.usage;
|
|
2723
|
+
totalInput += usage.input, totalOutput += usage.output, totalCacheRead += usage.cacheRead, totalCacheWrite += usage.cacheWrite, totalCost += usage.cost.total;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
return {
|
|
2727
|
+
sessionFile: this.sessionFile,
|
|
2728
|
+
sessionId: this.sessionId,
|
|
2729
|
+
userMessages,
|
|
2730
|
+
assistantMessages,
|
|
2731
|
+
toolCalls,
|
|
2732
|
+
toolResults,
|
|
2733
|
+
totalMessages,
|
|
2734
|
+
tokens: {
|
|
2735
|
+
input: totalInput,
|
|
2736
|
+
output: totalOutput,
|
|
2737
|
+
cacheRead: totalCacheRead,
|
|
2738
|
+
cacheWrite: totalCacheWrite,
|
|
2739
|
+
total: totalInput + totalOutput + totalCacheRead + totalCacheWrite
|
|
2740
|
+
},
|
|
2741
|
+
cost: totalCost,
|
|
2742
|
+
contextUsage: this.getContextUsage()
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
getContextUsage() {
|
|
2746
|
+
let model = this.model;
|
|
2747
|
+
if (!model)
|
|
2748
|
+
return;
|
|
2749
|
+
let contextWindow = model.contextWindow ?? 0;
|
|
2750
|
+
if (contextWindow <= 0)
|
|
2751
|
+
return;
|
|
2752
|
+
let branchEntries = this.sessionManager.getBranch(), latestCompaction = getLatestCompactionEntry(branchEntries);
|
|
2753
|
+
if (latestCompaction) {
|
|
2754
|
+
let compactionIndex = branchEntries.lastIndexOf(latestCompaction), hasPostCompactionUsage = !1;
|
|
2755
|
+
for (let i = branchEntries.length - 1; i > compactionIndex; i--) {
|
|
2756
|
+
let entry = branchEntries[i];
|
|
2757
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
2758
|
+
let assistant = entry.message;
|
|
2759
|
+
if (assistant.stopReason !== "aborted" && assistant.stopReason !== "error" && calculateContextTokens(assistant.usage) > 0) {
|
|
2760
|
+
hasPostCompactionUsage = !0;
|
|
2761
|
+
break;
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
if (!hasPostCompactionUsage)
|
|
2766
|
+
return { tokens: null, contextWindow, percent: null };
|
|
2767
|
+
}
|
|
2768
|
+
let estimate = estimateContextTokens(this.messages), percent = estimate.tokens / contextWindow * 100;
|
|
2769
|
+
return {
|
|
2770
|
+
tokens: estimate.tokens,
|
|
2771
|
+
contextWindow,
|
|
2772
|
+
percent
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
/**
|
|
2776
|
+
* Export session to HTML.
|
|
2777
|
+
* @param outputPath Optional output path (defaults to session directory)
|
|
2778
|
+
* @returns Path to exported file
|
|
2779
|
+
*/
|
|
2780
|
+
async exportToHtml(outputPath) {
|
|
2781
|
+
let [{ exportSessionToHtml }, { createToolHtmlRenderer }] = await Promise.all([
|
|
2782
|
+
import("./export-html-KSOIMVPI.js"),
|
|
2783
|
+
import("./tool-renderer-MV5O452K.js")
|
|
2784
|
+
]), configuredThemeName = this.settingsManager.getTheme(), themeName = configuredThemeName && getThemeByName(configuredThemeName) ? configuredThemeName : void 0, toolRenderer = createToolHtmlRenderer({
|
|
2785
|
+
getToolDefinition: (name) => this.getToolDefinition(name),
|
|
2786
|
+
theme,
|
|
2787
|
+
cwd: this.sessionManager.getCwd()
|
|
2788
|
+
});
|
|
2789
|
+
return await exportSessionToHtml(this.sessionManager, this.state, {
|
|
2790
|
+
outputPath,
|
|
2791
|
+
themeName,
|
|
2792
|
+
toolRenderer
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Export the current session branch to a JSONL file.
|
|
2797
|
+
* Writes the session header followed by all entries on the current branch path.
|
|
2798
|
+
* @param outputPath Target file path. If omitted, generates a timestamped file in cwd.
|
|
2799
|
+
* @returns The resolved output file path.
|
|
2800
|
+
*/
|
|
2801
|
+
exportToJsonl(outputPath) {
|
|
2802
|
+
let filePath = resolvePath(outputPath ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.jsonl`, process.cwd()), dir = dirname2(filePath);
|
|
2803
|
+
existsSync2(dir) || mkdirSync2(dir, { recursive: !0 });
|
|
2804
|
+
let header = {
|
|
2805
|
+
type: "session",
|
|
2806
|
+
version: CURRENT_SESSION_VERSION,
|
|
2807
|
+
id: this.sessionManager.getSessionId(),
|
|
2808
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2809
|
+
cwd: this.sessionManager.getCwd()
|
|
2810
|
+
}, branchEntries = this.sessionManager.getBranch(), lines = [JSON.stringify(header)], prevId = null;
|
|
2811
|
+
for (let entry of branchEntries) {
|
|
2812
|
+
let linear = { ...entry, parentId: prevId };
|
|
2813
|
+
lines.push(JSON.stringify(linear)), prevId = entry.id;
|
|
2814
|
+
}
|
|
2815
|
+
return writeFileSync2(filePath, `${lines.join(`
|
|
2816
|
+
`)}
|
|
2817
|
+
`), filePath;
|
|
2818
|
+
}
|
|
2819
|
+
// =========================================================================
|
|
2820
|
+
// Utilities
|
|
2821
|
+
// =========================================================================
|
|
2822
|
+
/**
|
|
2823
|
+
* Get text content of last assistant message.
|
|
2824
|
+
* Useful for /copy command.
|
|
2825
|
+
* @returns Text content, or undefined if no assistant message exists
|
|
2826
|
+
*/
|
|
2827
|
+
getLastAssistantText() {
|
|
2828
|
+
let lastAssistant = this.messages.slice().reverse().find((m) => {
|
|
2829
|
+
if (m.role !== "assistant")
|
|
2830
|
+
return !1;
|
|
2831
|
+
let msg = m;
|
|
2832
|
+
return !(msg.stopReason === "aborted" && msg.content.length === 0);
|
|
2833
|
+
});
|
|
2834
|
+
if (!lastAssistant)
|
|
2835
|
+
return;
|
|
2836
|
+
let text = "";
|
|
2837
|
+
for (let content of lastAssistant.content)
|
|
2838
|
+
content.type === "text" && (text += content.text);
|
|
2839
|
+
return text.trim() || void 0;
|
|
2840
|
+
}
|
|
2841
|
+
// =========================================================================
|
|
2842
|
+
// Extension System
|
|
2843
|
+
// =========================================================================
|
|
2844
|
+
createReplacedSessionContext() {
|
|
2845
|
+
let context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this._extensionRunner.createCommandContext()));
|
|
2846
|
+
return context.sendMessage = (message, options) => this.sendCustomMessage(message, options), context.sendUserMessage = (content, options) => this.sendUserMessage(content, options), context;
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Check if extensions have handlers for a specific event type.
|
|
2850
|
+
*/
|
|
2851
|
+
hasExtensionHandlers(eventType) {
|
|
2852
|
+
return this._extensionRunner.hasHandlers(eventType);
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Get the extension runner (for setting UI context and error handlers).
|
|
2856
|
+
*/
|
|
2857
|
+
get extensionRunner() {
|
|
2858
|
+
return this._extensionRunner;
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
|
|
2862
|
+
// packages/coding-agent/dist/core/provider-attribution.js
|
|
2863
|
+
var OPENCODE_HOST = "opencode.ai";
|
|
2864
|
+
function matchesHost(baseUrl, expectedHost) {
|
|
2865
|
+
try {
|
|
2866
|
+
return new URL(baseUrl).hostname === expectedHost;
|
|
2867
|
+
} catch {
|
|
2868
|
+
return !1;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
function getSessionHeaders(model, sessionId) {
|
|
2872
|
+
if (sessionId && !(model.provider !== "opencode" && model.provider !== "opencode-go" && !matchesHost(model.baseUrl, OPENCODE_HOST)))
|
|
2873
|
+
return { "x-opencode-session": sessionId, "x-opencode-client": "pi" };
|
|
2874
|
+
}
|
|
2875
|
+
function mergeProviderAttributionHeaders(model, _settingsManager, sessionId, ...headerSources) {
|
|
2876
|
+
let merged = {
|
|
2877
|
+
...getSessionHeaders(model, sessionId)
|
|
2878
|
+
};
|
|
2879
|
+
for (let headers of headerSources)
|
|
2880
|
+
headers && Object.assign(merged, headers);
|
|
2881
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// packages/coding-agent/dist/core/agent-session-runtime.js
|
|
2885
|
+
import { copyFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "node:fs";
|
|
2886
|
+
import { basename as basename2, join as join5 } from "node:path";
|
|
2887
|
+
|
|
2888
|
+
// packages/coding-agent/dist/core/session-cwd.js
|
|
2889
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
2890
|
+
function getMissingSessionCwdIssue(sessionManager, fallbackCwd) {
|
|
2891
|
+
let sessionFile = sessionManager.getSessionFile();
|
|
2892
|
+
if (!sessionFile)
|
|
2893
|
+
return;
|
|
2894
|
+
let sessionCwd = sessionManager.getCwd();
|
|
2895
|
+
if (!(!sessionCwd || existsSync3(sessionCwd)))
|
|
2896
|
+
return {
|
|
2897
|
+
sessionFile,
|
|
2898
|
+
sessionCwd,
|
|
2899
|
+
fallbackCwd
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2902
|
+
function formatMissingSessionCwdError(issue) {
|
|
2903
|
+
let sessionFile = issue.sessionFile ? `
|
|
2904
|
+
Session file: ${issue.sessionFile}` : "";
|
|
2905
|
+
return `Stored session working directory does not exist: ${issue.sessionCwd}${sessionFile}
|
|
2906
|
+
Current working directory: ${issue.fallbackCwd}`;
|
|
2907
|
+
}
|
|
2908
|
+
function formatMissingSessionCwdPrompt(issue) {
|
|
2909
|
+
return `cwd from session file does not exist
|
|
2910
|
+
${issue.sessionCwd}
|
|
2911
|
+
|
|
2912
|
+
continue in current cwd
|
|
2913
|
+
${issue.fallbackCwd}`;
|
|
2914
|
+
}
|
|
2915
|
+
var MissingSessionCwdError = class extends Error {
|
|
2916
|
+
issue;
|
|
2917
|
+
constructor(issue) {
|
|
2918
|
+
super(formatMissingSessionCwdError(issue)), this.name = "MissingSessionCwdError", this.issue = issue;
|
|
2919
|
+
}
|
|
2920
|
+
};
|
|
2921
|
+
function assertSessionCwdExists(sessionManager, fallbackCwd) {
|
|
2922
|
+
let issue = getMissingSessionCwdIssue(sessionManager, fallbackCwd);
|
|
2923
|
+
if (issue)
|
|
2924
|
+
throw new MissingSessionCwdError(issue);
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
// packages/coding-agent/dist/core/agent-session-runtime.js
|
|
2928
|
+
var SessionImportFileNotFoundError = class extends Error {
|
|
2929
|
+
filePath;
|
|
2930
|
+
constructor(filePath) {
|
|
2931
|
+
super(`File not found: ${filePath}`), this.name = "SessionImportFileNotFoundError", this.filePath = filePath;
|
|
2932
|
+
}
|
|
2933
|
+
};
|
|
2934
|
+
function extractUserMessageText(content) {
|
|
2935
|
+
return typeof content == "string" ? content : content.filter((part) => part.type === "text" && typeof part.text == "string").map((part) => part.text).join("");
|
|
2936
|
+
}
|
|
2937
|
+
var AgentSessionRuntime = class {
|
|
2938
|
+
rebindSession;
|
|
2939
|
+
beforeSessionInvalidate;
|
|
2940
|
+
_session;
|
|
2941
|
+
_services;
|
|
2942
|
+
createRuntime;
|
|
2943
|
+
onRuntimeApplied;
|
|
2944
|
+
_diagnostics;
|
|
2945
|
+
_modelFallbackMessage;
|
|
2946
|
+
detached = !1;
|
|
2947
|
+
transferring = !1;
|
|
2948
|
+
get isDetached() {
|
|
2949
|
+
return this.detached;
|
|
2950
|
+
}
|
|
2951
|
+
async releaseForTransfer(options = {}) {
|
|
2952
|
+
if (options.signal?.throwIfAborted(), this.detached)
|
|
2953
|
+
return;
|
|
2954
|
+
if (this.transferring)
|
|
2955
|
+
throw new SessionTransferError("Session transfer is already in progress.", "busy");
|
|
2956
|
+
this.transferring = !0, this.session.setTransferring(!0);
|
|
2957
|
+
let hasAttachedWork = () => getSubagentCancellation(this.session.sessionId)?.hasActiveRuns() || getSubagentCancellation(this.session.sessionFile ?? "")?.hasActiveRuns() || list().some((entry) => entry.status !== "exited" && (!entry.sessionId || entry.sessionId === this.session.sessionId)), busy = () => this.session.isStreaming || this.session.isCompacting || this.session.isBashRunning || this.session.isRetrying || this.session.pendingMessageCount > 0;
|
|
2958
|
+
try {
|
|
2959
|
+
if (hasAttachedWork())
|
|
2960
|
+
throw new SessionTransferError("Live children or processes cannot move between runtimes. Wait for them to finish or stop them and retry.", "busy");
|
|
2961
|
+
if (busy() && !options.stop)
|
|
2962
|
+
throw new SessionTransferError("Session is busy. Wait and retry, explicitly request stop, or cancel.", "busy");
|
|
2963
|
+
if (busy() && (this.session.abortCompaction(), this.session.abortBranchSummary(), this.session.abortBash(), this.session.clearQueue(), await this.session.abort()), busy() || hasAttachedWork())
|
|
2964
|
+
throw new SessionTransferError("Work has not stopped yet. Wait and retry.", "busy");
|
|
2965
|
+
options.signal?.throwIfAborted(), this.session.sessionManager.flush(), await this.teardownCurrent("resume");
|
|
2966
|
+
} finally {
|
|
2967
|
+
this.transferring = !1, this.detached || this.session.setTransferring(!1);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
constructor(_session, _services, createRuntime, _diagnostics = [], _modelFallbackMessage, onRuntimeApplied) {
|
|
2971
|
+
this._session = _session, this._services = _services, this.createRuntime = createRuntime, this._diagnostics = _diagnostics, this._modelFallbackMessage = _modelFallbackMessage, this.onRuntimeApplied = onRuntimeApplied;
|
|
2972
|
+
}
|
|
2973
|
+
async createOwnedRuntime(options) {
|
|
2974
|
+
try {
|
|
2975
|
+
return await this.createRuntime(options);
|
|
2976
|
+
} catch (error) {
|
|
2977
|
+
throw options.sessionManager.dispose(), error;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
get services() {
|
|
2981
|
+
return this._services;
|
|
2982
|
+
}
|
|
2983
|
+
get session() {
|
|
2984
|
+
return this._session;
|
|
2985
|
+
}
|
|
2986
|
+
get cwd() {
|
|
2987
|
+
return this._services.cwd;
|
|
2988
|
+
}
|
|
2989
|
+
get diagnostics() {
|
|
2990
|
+
return this._diagnostics;
|
|
2991
|
+
}
|
|
2992
|
+
get modelFallbackMessage() {
|
|
2993
|
+
return this._modelFallbackMessage;
|
|
2994
|
+
}
|
|
2995
|
+
setRebindSession(rebindSession) {
|
|
2996
|
+
this.rebindSession = rebindSession;
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Set a synchronous callback that runs after `session_shutdown` handlers finish
|
|
3000
|
+
* but before the current session is invalidated.
|
|
3001
|
+
*
|
|
3002
|
+
* This is for host-owned UI teardown that must not yield to the event loop,
|
|
3003
|
+
* such as detaching extension-provided TUI components before the old extension
|
|
3004
|
+
* context becomes stale.
|
|
3005
|
+
*/
|
|
3006
|
+
setBeforeSessionInvalidate(beforeSessionInvalidate) {
|
|
3007
|
+
this.beforeSessionInvalidate = beforeSessionInvalidate;
|
|
3008
|
+
}
|
|
3009
|
+
async emitBeforeSwitch(reason, targetSessionFile) {
|
|
3010
|
+
let runner = this.session.extensionRunner;
|
|
3011
|
+
return runner.hasHandlers("session_before_switch") ? { cancelled: (await runner.emit({
|
|
3012
|
+
type: "session_before_switch",
|
|
3013
|
+
reason,
|
|
3014
|
+
targetSessionFile
|
|
3015
|
+
}))?.cancel === !0 } : { cancelled: !1 };
|
|
3016
|
+
}
|
|
3017
|
+
async emitBeforeFork(entryId, options) {
|
|
3018
|
+
let runner = this.session.extensionRunner;
|
|
3019
|
+
return runner.hasHandlers("session_before_fork") ? { cancelled: (await runner.emit({
|
|
3020
|
+
type: "session_before_fork",
|
|
3021
|
+
entryId,
|
|
3022
|
+
...options
|
|
3023
|
+
}))?.cancel === !0 } : { cancelled: !1 };
|
|
3024
|
+
}
|
|
3025
|
+
async teardownCurrent(reason, targetSessionFile) {
|
|
3026
|
+
this.detached || (await emitSessionShutdownEvent(this.session.extensionRunner, {
|
|
3027
|
+
type: "session_shutdown",
|
|
3028
|
+
reason,
|
|
3029
|
+
targetSessionFile
|
|
3030
|
+
}), this.beforeSessionInvalidate?.(reason), this.session.dispose(), this.detached = !0);
|
|
3031
|
+
}
|
|
3032
|
+
async teardownForReplacement(reason, sessionManager) {
|
|
3033
|
+
try {
|
|
3034
|
+
await this.teardownCurrent(reason, sessionManager.getSessionFile());
|
|
3035
|
+
} catch (error) {
|
|
3036
|
+
throw sessionManager.dispose(), error;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
apply(result) {
|
|
3040
|
+
this.detached = !1, this._session = result.session, this._services = result.services, this._diagnostics = result.diagnostics, this._modelFallbackMessage = result.modelFallbackMessage, this.onRuntimeApplied?.(result);
|
|
3041
|
+
}
|
|
3042
|
+
async finishSessionReplacement(withSession) {
|
|
3043
|
+
this.rebindSession && await this.rebindSession(this.session), withSession && await withSession(this.session.createReplacedSessionContext());
|
|
3044
|
+
}
|
|
3045
|
+
async switchSession(sessionPath, options) {
|
|
3046
|
+
if (!this.detached && this.session.sessionFile && canonicalSessionPath(sessionPath) === canonicalSessionPath(this.session.sessionFile))
|
|
3047
|
+
return { cancelled: !1 };
|
|
3048
|
+
let beforeResult = this.detached ? { cancelled: !1 } : await this.emitBeforeSwitch("resume", sessionPath);
|
|
3049
|
+
if (beforeResult.cancelled)
|
|
3050
|
+
return beforeResult;
|
|
3051
|
+
let previousSessionFile = this.session.sessionFile, sessionManager = SessionManager.open(sessionPath, void 0, options?.cwdOverride);
|
|
3052
|
+
try {
|
|
3053
|
+
assertSessionCwdExists(sessionManager, this.cwd);
|
|
3054
|
+
} catch (error) {
|
|
3055
|
+
throw sessionManager.dispose(), error;
|
|
3056
|
+
}
|
|
3057
|
+
return await this.teardownForReplacement("resume", sessionManager), this.apply(await this.createOwnedRuntime({
|
|
3058
|
+
cwd: sessionManager.getCwd(),
|
|
3059
|
+
agentDir: this.services.agentDir,
|
|
3060
|
+
sessionManager,
|
|
3061
|
+
sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
|
|
3062
|
+
projectTrustContext: options?.projectTrustContextFactory?.(sessionManager.getCwd())
|
|
3063
|
+
})), await this.finishSessionReplacement(options?.withSession), { cancelled: !1 };
|
|
3064
|
+
}
|
|
3065
|
+
async newSession(options) {
|
|
3066
|
+
let beforeResult = await this.emitBeforeSwitch("new");
|
|
3067
|
+
if (beforeResult.cancelled)
|
|
3068
|
+
return beforeResult;
|
|
3069
|
+
let previousSessionFile = this.session.sessionFile, sessionDir = this.session.sessionManager.getSessionDir(), sessionManager = this.session.sessionManager.isPersisted() ? SessionManager.create(this.cwd, sessionDir) : SessionManager.inMemory(this.cwd);
|
|
3070
|
+
return options?.parentSession && sessionManager.newSession({ parentSession: options.parentSession }), await this.teardownForReplacement("new", sessionManager), this.apply(await this.createOwnedRuntime({
|
|
3071
|
+
cwd: this.cwd,
|
|
3072
|
+
agentDir: this.services.agentDir,
|
|
3073
|
+
sessionManager,
|
|
3074
|
+
sessionStartEvent: { type: "session_start", reason: "new", previousSessionFile }
|
|
3075
|
+
})), options?.setup && (await options.setup(this.session.sessionManager), this.session.agent.state.messages = this.session.sessionManager.buildSessionContext().messages), await this.finishSessionReplacement(options?.withSession), { cancelled: !1 };
|
|
3076
|
+
}
|
|
3077
|
+
async fork(entryId, options) {
|
|
3078
|
+
let position = options?.position ?? "before";
|
|
3079
|
+
if ((await this.emitBeforeFork(entryId, { position })).cancelled)
|
|
3080
|
+
return { cancelled: !0 };
|
|
3081
|
+
let targetLeafId, selectedText, selectedEntry = this.session.sessionManager.getEntry(entryId);
|
|
3082
|
+
if (!selectedEntry)
|
|
3083
|
+
throw new Error("Invalid entry ID for forking");
|
|
3084
|
+
if (position === "at")
|
|
3085
|
+
targetLeafId = selectedEntry.id;
|
|
3086
|
+
else {
|
|
3087
|
+
if (selectedEntry.type !== "message" || selectedEntry.message.role !== "user")
|
|
3088
|
+
throw new Error("Invalid entry ID for forking");
|
|
3089
|
+
targetLeafId = selectedEntry.parentId, selectedText = extractUserMessageText(selectedEntry.message.content);
|
|
3090
|
+
}
|
|
3091
|
+
let previousSessionFile = this.session.sessionFile;
|
|
3092
|
+
if (this.session.sessionManager.isPersisted()) {
|
|
3093
|
+
let currentSessionFile = this.session.sessionFile;
|
|
3094
|
+
if (!currentSessionFile)
|
|
3095
|
+
throw new Error("Persisted session is missing a session file");
|
|
3096
|
+
let sessionDir = this.session.sessionManager.getSessionDir();
|
|
3097
|
+
if (!targetLeafId) {
|
|
3098
|
+
let sessionManager3 = SessionManager.create(this.cwd, sessionDir);
|
|
3099
|
+
return sessionManager3.newSession({ parentSession: currentSessionFile }), await this.teardownForReplacement("fork", sessionManager3), this.apply(await this.createOwnedRuntime({
|
|
3100
|
+
cwd: this.cwd,
|
|
3101
|
+
agentDir: this.services.agentDir,
|
|
3102
|
+
sessionManager: sessionManager3,
|
|
3103
|
+
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile }
|
|
3104
|
+
})), await this.finishSessionReplacement(options?.withSession), { cancelled: !1, selectedText };
|
|
3105
|
+
}
|
|
3106
|
+
if (!existsSync4(currentSessionFile))
|
|
3107
|
+
throw new Error("This session has not been saved yet. Wait for the first assistant response before cloning or forking it.");
|
|
3108
|
+
let sessionManager2 = SessionManager.forkBranch(currentSessionFile, targetLeafId, sessionDir);
|
|
3109
|
+
if (!sessionManager2.getSessionFile())
|
|
3110
|
+
throw new Error("Failed to create forked session");
|
|
3111
|
+
return await this.teardownForReplacement("fork", sessionManager2), this.apply(await this.createOwnedRuntime({
|
|
3112
|
+
cwd: sessionManager2.getCwd(),
|
|
3113
|
+
agentDir: this.services.agentDir,
|
|
3114
|
+
sessionManager: sessionManager2,
|
|
3115
|
+
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile }
|
|
3116
|
+
})), await this.finishSessionReplacement(options?.withSession), { cancelled: !1, selectedText };
|
|
3117
|
+
}
|
|
3118
|
+
let sessionManager = this.session.sessionManager.copyInMemory();
|
|
3119
|
+
return targetLeafId ? sessionManager.createBranchedSession(targetLeafId) : sessionManager.newSession({ parentSession: this.session.sessionFile }), await this.teardownForReplacement("fork", sessionManager), this.apply(await this.createOwnedRuntime({
|
|
3120
|
+
cwd: this.cwd,
|
|
3121
|
+
agentDir: this.services.agentDir,
|
|
3122
|
+
sessionManager,
|
|
3123
|
+
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile }
|
|
3124
|
+
})), await this.finishSessionReplacement(options?.withSession), { cancelled: !1, selectedText };
|
|
3125
|
+
}
|
|
3126
|
+
/**
|
|
3127
|
+
* Import a session JSONL file and switch runtime state to the imported session.
|
|
3128
|
+
*
|
|
3129
|
+
* @returns `{ cancelled: true }` when cancelled by `session_before_switch`, otherwise `{ cancelled: false }`.
|
|
3130
|
+
* @throws {SessionImportFileNotFoundError} When the input path does not exist.
|
|
3131
|
+
* @throws {MissingSessionCwdError} When the imported session cwd cannot be resolved and no override is provided.
|
|
3132
|
+
*/
|
|
3133
|
+
async importFromJsonl(inputPath, cwdOverride) {
|
|
3134
|
+
let resolvedPath = resolvePath(inputPath);
|
|
3135
|
+
if (!existsSync4(resolvedPath))
|
|
3136
|
+
throw new SessionImportFileNotFoundError(resolvedPath);
|
|
3137
|
+
let sessionDir = this.session.sessionManager.getSessionDir();
|
|
3138
|
+
existsSync4(sessionDir) || mkdirSync3(sessionDir, { recursive: !0 });
|
|
3139
|
+
let destinationPath = join5(sessionDir, basename2(resolvedPath)), beforeResult = await this.emitBeforeSwitch("resume", destinationPath);
|
|
3140
|
+
if (beforeResult.cancelled)
|
|
3141
|
+
return beforeResult;
|
|
3142
|
+
let previousSessionFile = this.session.sessionFile;
|
|
3143
|
+
if (canonicalSessionPath(destinationPath) !== canonicalSessionPath(resolvedPath)) {
|
|
3144
|
+
let ownership = new SessionOwnership(destinationPath);
|
|
3145
|
+
try {
|
|
3146
|
+
copyFileSync(resolvedPath, destinationPath);
|
|
3147
|
+
} finally {
|
|
3148
|
+
ownership.release();
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
let sessionManager = SessionManager.open(destinationPath, sessionDir, cwdOverride);
|
|
3152
|
+
try {
|
|
3153
|
+
assertSessionCwdExists(sessionManager, this.cwd);
|
|
3154
|
+
} catch (error) {
|
|
3155
|
+
throw sessionManager.dispose(), error;
|
|
3156
|
+
}
|
|
3157
|
+
return await this.teardownForReplacement("resume", sessionManager), this.apply(await this.createOwnedRuntime({
|
|
3158
|
+
cwd: sessionManager.getCwd(),
|
|
3159
|
+
agentDir: this.services.agentDir,
|
|
3160
|
+
sessionManager,
|
|
3161
|
+
sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile }
|
|
3162
|
+
})), await this.finishSessionReplacement(), { cancelled: !1 };
|
|
3163
|
+
}
|
|
3164
|
+
async dispose() {
|
|
3165
|
+
await this.teardownCurrent("quit");
|
|
3166
|
+
}
|
|
3167
|
+
};
|
|
3168
|
+
async function createAgentSessionRuntime(createRuntime, options) {
|
|
3169
|
+
let result;
|
|
3170
|
+
try {
|
|
3171
|
+
assertSessionCwdExists(options.sessionManager, options.cwd), result = await createRuntime(options);
|
|
3172
|
+
} catch (error) {
|
|
3173
|
+
throw options.sessionManager.dispose(), error;
|
|
3174
|
+
}
|
|
3175
|
+
let runtime = new AgentSessionRuntime(result.session, result.services, createRuntime, result.diagnostics, result.modelFallbackMessage, options.onRuntimeApplied);
|
|
3176
|
+
return options.onRuntimeApplied?.(result), runtime;
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
// packages/coding-agent/dist/core/sdk.js
|
|
3180
|
+
function getDefaultAgentDir() {
|
|
3181
|
+
return getAgentDir();
|
|
3182
|
+
}
|
|
3183
|
+
async function createAgentSession(options = {}) {
|
|
3184
|
+
let cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd()), agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir(), sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));
|
|
3185
|
+
try {
|
|
3186
|
+
return sessionManager.assertWritable(), await createOwnedAgentSession({ ...options, sessionManager });
|
|
3187
|
+
} catch (error) {
|
|
3188
|
+
throw sessionManager.dispose(), error;
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
async function createOwnedAgentSession(options) {
|
|
3192
|
+
let cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd()), agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir(), resourceLoader = options.resourceLoader, authPath = options.agentDir ? join6(agentDir, "auth.json") : void 0, modelsPath = options.agentDir ? join6(agentDir, "models.json") : void 0, modelRuntime = options.modelRuntime ?? await ModelRuntime.create({ authPath, modelsPath }), settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir), sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));
|
|
3193
|
+
resourceLoader || (resourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager }), await resourceLoader.reload(), time("resourceLoader.reload"));
|
|
3194
|
+
let existingSession = sessionManager.buildSessionContext(), hasExistingSession = existingSession.messages.length > 0, hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === "thinking_level_change"), model = options.model, modelFallbackMessage;
|
|
3195
|
+
if (!model && hasExistingSession && existingSession.model) {
|
|
3196
|
+
let restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);
|
|
3197
|
+
restoredModel && modelRuntime.hasConfiguredAuth(restoredModel.provider) && (model = restoredModel), model || (modelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`);
|
|
3198
|
+
}
|
|
3199
|
+
model || (model = (await findInitialModel({
|
|
3200
|
+
scopedModels: [],
|
|
3201
|
+
isContinuing: hasExistingSession,
|
|
3202
|
+
defaultProvider: settingsManager.getDefaultProvider(),
|
|
3203
|
+
defaultModelId: settingsManager.getDefaultModel(),
|
|
3204
|
+
defaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),
|
|
3205
|
+
modelRuntime
|
|
3206
|
+
})).model, model ? modelFallbackMessage && (modelFallbackMessage += `. Using ${model.provider}/${model.id}`) : modelFallbackMessage = formatNoModelsAvailableMessage());
|
|
3207
|
+
let thinkingLevel = options.thinkingLevel;
|
|
3208
|
+
thinkingLevel === void 0 && hasExistingSession && (thinkingLevel = hasThinkingEntry ? existingSession.thinkingLevel : settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL), thinkingLevel === void 0 && (thinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL), model ? thinkingLevel = clampThinkingLevel2(model, thinkingLevel) : thinkingLevel = "off";
|
|
3209
|
+
let defaultActiveToolNames = ["read", "bash", "edit", "write"], allowedToolNames = options.tools ?? (options.noTools === "all" ? [] : void 0), excludedToolNames = options.excludeTools, excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : void 0, initialActiveToolNames = (options.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames).filter((name) => !excludedToolNameSet?.has(name)), agent, convertToLlmWithBlockImages = (messages) => {
|
|
3210
|
+
let converted = convertToLlm(messages);
|
|
3211
|
+
return settingsManager.getBlockImages() ? converted.map((msg) => {
|
|
3212
|
+
if (msg.role === "user" || msg.role === "toolResult") {
|
|
3213
|
+
let content = msg.content;
|
|
3214
|
+
if (Array.isArray(content) && content.some((c) => c.type === "image")) {
|
|
3215
|
+
let filteredContent = content.map((c) => c.type === "image" ? { type: "text", text: "Image reading is disabled." } : c).filter((c, i, arr) => (
|
|
3216
|
+
// Dedupe consecutive "Image reading is disabled." texts
|
|
3217
|
+
!(c.type === "text" && c.text === "Image reading is disabled." && i > 0 && arr[i - 1].type === "text" && arr[i - 1].text === "Image reading is disabled.")
|
|
3218
|
+
));
|
|
3219
|
+
return { ...msg, content: filteredContent };
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
return msg;
|
|
3223
|
+
}) : converted;
|
|
3224
|
+
}, extensionRunnerRef = {};
|
|
3225
|
+
agent = new Agent({
|
|
3226
|
+
initialState: {
|
|
3227
|
+
systemPrompt: "",
|
|
3228
|
+
model,
|
|
3229
|
+
thinkingLevel,
|
|
3230
|
+
tools: []
|
|
3231
|
+
},
|
|
3232
|
+
convertToLlm: convertToLlmWithBlockImages,
|
|
3233
|
+
streamFn: async (model2, context, options2) => {
|
|
3234
|
+
let providerRetrySettings = settingsManager.getProviderRetrySettings(), httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs(), effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs, timeoutMs = options2?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs, websocketConnectTimeoutMs = options2?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs(), headerRunner = extensionRunnerRef.current;
|
|
3235
|
+
return modelRuntime.streamSimple(model2, context, {
|
|
3236
|
+
...options2,
|
|
3237
|
+
timeoutMs,
|
|
3238
|
+
websocketConnectTimeoutMs,
|
|
3239
|
+
cacheRetention: settingsManager.getCacheRetention(),
|
|
3240
|
+
maxRetries: options2?.maxRetries ?? providerRetrySettings.maxRetries,
|
|
3241
|
+
maxRetryDelayMs: options2?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,
|
|
3242
|
+
transformHeaders: async (requestHeaders) => {
|
|
3243
|
+
let headers = mergeProviderAttributionHeaders(model2, settingsManager, options2?.sessionId, requestHeaders);
|
|
3244
|
+
return headerRunner?.hasHandlers("before_provider_headers") ? headerRunner.emitBeforeProviderHeaders(headers ?? {}) : headers ?? {};
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
},
|
|
3248
|
+
onPayload: async (payload, _model) => {
|
|
3249
|
+
let runner = extensionRunnerRef.current;
|
|
3250
|
+
return runner?.hasHandlers("before_provider_request") ? runner.emitBeforeProviderRequest(payload) : payload;
|
|
3251
|
+
},
|
|
3252
|
+
onResponse: async (response, _model) => {
|
|
3253
|
+
let runner = extensionRunnerRef.current;
|
|
3254
|
+
runner?.hasHandlers("after_provider_response") && await runner.emit({
|
|
3255
|
+
type: "after_provider_response",
|
|
3256
|
+
status: response.status,
|
|
3257
|
+
headers: response.headers
|
|
3258
|
+
});
|
|
3259
|
+
},
|
|
3260
|
+
sessionId: sessionManager.getSessionId(),
|
|
3261
|
+
transformContext: async (messages) => {
|
|
3262
|
+
let runner = extensionRunnerRef.current;
|
|
3263
|
+
return runner ? runner.emitContext(messages) : messages;
|
|
3264
|
+
},
|
|
3265
|
+
steeringMode: settingsManager.getSteeringMode(),
|
|
3266
|
+
followUpMode: settingsManager.getFollowUpMode(),
|
|
3267
|
+
transport: settingsManager.getTransport(),
|
|
3268
|
+
thinkingBudgets: settingsManager.getThinkingBudgets(),
|
|
3269
|
+
maxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs
|
|
3270
|
+
}), hasExistingSession ? (agent.state.messages = existingSession.messages, hasThinkingEntry || sessionManager.appendThinkingLevelChange(thinkingLevel)) : (model && sessionManager.appendModelChange(model.provider, model.id), sessionManager.appendThinkingLevelChange(thinkingLevel));
|
|
3271
|
+
let session = new AgentSession({
|
|
3272
|
+
agent,
|
|
3273
|
+
sessionManager,
|
|
3274
|
+
settingsManager,
|
|
3275
|
+
cwd,
|
|
3276
|
+
agentDir,
|
|
3277
|
+
scopedModels: options.scopedModels,
|
|
3278
|
+
resourceLoader,
|
|
3279
|
+
customTools: options.customTools,
|
|
3280
|
+
modelRuntime,
|
|
3281
|
+
initialActiveToolNames,
|
|
3282
|
+
allowedToolNames,
|
|
3283
|
+
excludedToolNames,
|
|
3284
|
+
extensionRunnerRef,
|
|
3285
|
+
sessionStartEvent: options.sessionStartEvent
|
|
3286
|
+
}), extensionsResult = resourceLoader.getExtensions();
|
|
3287
|
+
return {
|
|
3288
|
+
session,
|
|
3289
|
+
extensionsResult,
|
|
3290
|
+
modelFallbackMessage
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
// packages/coding-agent/dist/core/agent-session-services.js
|
|
3295
|
+
function applyExtensionFlagValues(resourceLoader, extensionFlagValues) {
|
|
3296
|
+
if (!extensionFlagValues)
|
|
3297
|
+
return [];
|
|
3298
|
+
let diagnostics = [], extensionsResult = resourceLoader.getExtensions(), registeredFlags = /* @__PURE__ */ new Map();
|
|
3299
|
+
for (let extension of extensionsResult.extensions)
|
|
3300
|
+
for (let [name, flag] of extension.flags)
|
|
3301
|
+
registeredFlags.set(name, { type: flag.type });
|
|
3302
|
+
let unknownFlags = [], deferredFlags = /* @__PURE__ */ new Set(["mcp-config"]);
|
|
3303
|
+
for (let [name, value] of extensionFlagValues) {
|
|
3304
|
+
let flag = registeredFlags.get(name);
|
|
3305
|
+
if (!flag) {
|
|
3306
|
+
if (deferredFlags.has(name))
|
|
3307
|
+
continue;
|
|
3308
|
+
unknownFlags.push(name);
|
|
3309
|
+
continue;
|
|
3310
|
+
}
|
|
3311
|
+
if (flag.type === "boolean") {
|
|
3312
|
+
extensionsResult.runtime.flagValues.set(name, !0);
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
if (typeof value == "string") {
|
|
3316
|
+
extensionsResult.runtime.flagValues.set(name, value);
|
|
3317
|
+
continue;
|
|
3318
|
+
}
|
|
3319
|
+
diagnostics.push({
|
|
3320
|
+
type: "error",
|
|
3321
|
+
message: `Extension flag "--${name}" requires a value`
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
return unknownFlags.length > 0 && diagnostics.push({
|
|
3325
|
+
type: "error",
|
|
3326
|
+
message: `Unknown option${unknownFlags.length === 1 ? "" : "s"}: ${unknownFlags.map((name) => `--${name}`).join(", ")}`
|
|
3327
|
+
}), diagnostics;
|
|
3328
|
+
}
|
|
3329
|
+
async function createAgentSessionServices(options) {
|
|
3330
|
+
let cwd = resolvePath(options.cwd), agentDir = options.agentDir ? resolvePath(options.agentDir) : getAgentDir(), modelRuntime = options.modelRuntime ?? await ModelRuntime.create({
|
|
3331
|
+
authPath: join7(agentDir, "auth.json"),
|
|
3332
|
+
modelsPath: join7(agentDir, "models.json")
|
|
3333
|
+
});
|
|
3334
|
+
time("ModelRuntime.create");
|
|
3335
|
+
let settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir), resourceLoader = new DefaultResourceLoader({
|
|
3336
|
+
...options.resourceLoaderOptions ?? {},
|
|
3337
|
+
cwd,
|
|
3338
|
+
agentDir,
|
|
3339
|
+
settingsManager
|
|
3340
|
+
});
|
|
3341
|
+
await resourceLoader.reload(options.resourceLoaderReloadOptions), time("resourceLoader.reload");
|
|
3342
|
+
let diagnostics = [], extensionsResult = resourceLoader.getExtensions();
|
|
3343
|
+
for (let { name, config, extensionPath } of extensionsResult.runtime.pendingProviderRegistrations)
|
|
3344
|
+
try {
|
|
3345
|
+
modelRuntime.registerProvider(name, config);
|
|
3346
|
+
} catch (error) {
|
|
3347
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
3348
|
+
diagnostics.push({
|
|
3349
|
+
type: "error",
|
|
3350
|
+
message: `Extension "${extensionPath}" error: ${message}`
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
return extensionsResult.runtime.pendingProviderRegistrations = [], await modelRuntime.refresh({ allowNetwork: !1 }), time("modelRuntime.refresh(cache-only)"), diagnostics.push(...applyExtensionFlagValues(resourceLoader, options.extensionFlagValues)), {
|
|
3354
|
+
cwd,
|
|
3355
|
+
agentDir,
|
|
3356
|
+
modelRuntime,
|
|
3357
|
+
settingsManager,
|
|
3358
|
+
resourceLoader,
|
|
3359
|
+
diagnostics
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
async function createAgentSessionFromServices(options) {
|
|
3363
|
+
return createAgentSession({
|
|
3364
|
+
cwd: options.services.cwd,
|
|
3365
|
+
agentDir: options.services.agentDir,
|
|
3366
|
+
modelRuntime: options.services.modelRuntime,
|
|
3367
|
+
settingsManager: options.services.settingsManager,
|
|
3368
|
+
resourceLoader: options.services.resourceLoader,
|
|
3369
|
+
sessionManager: options.sessionManager,
|
|
3370
|
+
model: options.model,
|
|
3371
|
+
thinkingLevel: options.thinkingLevel,
|
|
3372
|
+
scopedModels: options.scopedModels,
|
|
3373
|
+
tools: options.tools,
|
|
3374
|
+
excludeTools: options.excludeTools,
|
|
3375
|
+
noTools: options.noTools,
|
|
3376
|
+
customTools: options.customTools,
|
|
3377
|
+
sessionStartEvent: options.sessionStartEvent
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
export {
|
|
3382
|
+
formatNoModelsAvailableMessage,
|
|
3383
|
+
wrapRegisteredTool,
|
|
3384
|
+
wrapRegisteredTools,
|
|
3385
|
+
ModelRegistry,
|
|
3386
|
+
setRollbackWarningHandler,
|
|
3387
|
+
initRollback,
|
|
3388
|
+
isRollbackEnabled,
|
|
3389
|
+
isRollbackSessionForceEnabled,
|
|
3390
|
+
enableRollbackForSession,
|
|
3391
|
+
disableRollbackForSession,
|
|
3392
|
+
beginTurn,
|
|
3393
|
+
captureTreeChanges,
|
|
3394
|
+
peekRollbackTurnsConsumed,
|
|
3395
|
+
rollbackLastTurn,
|
|
3396
|
+
migrateRollbackSession,
|
|
3397
|
+
clearRollback,
|
|
3398
|
+
parseSkillBlock,
|
|
3399
|
+
AgentSession,
|
|
3400
|
+
getMissingSessionCwdIssue,
|
|
3401
|
+
formatMissingSessionCwdPrompt,
|
|
3402
|
+
MissingSessionCwdError,
|
|
3403
|
+
SessionImportFileNotFoundError,
|
|
3404
|
+
AgentSessionRuntime,
|
|
3405
|
+
createAgentSessionRuntime,
|
|
3406
|
+
createAgentSession,
|
|
3407
|
+
createAgentSessionServices,
|
|
3408
|
+
createAgentSessionFromServices
|
|
3409
|
+
};
|