gsd-pi 2.34.0-dev.ed0bfbf → 2.35.0-dev.30eec3f
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/dist/cli.js +7 -2
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +13 -1
- package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
- package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
- package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
- package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
- package/dist/resources/extensions/bg-shell/types.js +0 -2
- package/dist/resources/extensions/context7/index.js +5 -0
- package/dist/resources/extensions/get-secrets-from-user.js +2 -30
- package/dist/resources/extensions/google-search/index.js +5 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
- package/dist/resources/extensions/gsd/auto-loop.js +10 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
- package/dist/resources/extensions/gsd/auto-start.js +35 -2
- package/dist/resources/extensions/gsd/auto.js +59 -4
- package/dist/resources/extensions/gsd/changelog.js +162 -0
- package/dist/resources/extensions/gsd/commands-bootstrap.js +1 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
- package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +5 -1
- package/dist/resources/extensions/gsd/commands.js +8 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
- package/dist/resources/extensions/gsd/doctor-checks.js +113 -5
- package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
- package/dist/resources/extensions/gsd/doctor-proactive.js +22 -0
- package/dist/resources/extensions/gsd/doctor.js +36 -0
- package/dist/resources/extensions/gsd/files.js +11 -2
- package/dist/resources/extensions/gsd/gitignore.js +54 -7
- package/dist/resources/extensions/gsd/guided-flow.js +5 -3
- package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
- package/dist/resources/extensions/gsd/health-widget.js +97 -46
- package/dist/resources/extensions/gsd/index.js +10 -1
- package/dist/resources/extensions/gsd/migrate-external.js +55 -2
- package/dist/resources/extensions/gsd/paths.js +74 -7
- package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +54 -1
- package/dist/resources/extensions/gsd/preferences.js +2 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
- package/dist/resources/extensions/gsd/session-lock.js +26 -2
- package/dist/resources/extensions/gsd/templates/plan.md +8 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
- package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
- package/dist/resources/extensions/shared/mod.js +1 -1
- package/dist/resources/extensions/shared/sanitize.js +30 -0
- package/dist/resources/extensions/subagent/index.js +6 -14
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
- package/package.json +2 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts +14 -0
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +24 -27
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +1 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +11 -22
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/proxy.js +2 -8
- package/packages/pi-agent-core/dist/proxy.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +30 -27
- package/packages/pi-agent-core/src/agent.ts +12 -23
- package/packages/pi-agent-core/src/proxy.ts +2 -8
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +5 -41
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +10 -73
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +9 -80
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-shared.d.ts +65 -0
- package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-shared.js +146 -0
- package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +7 -135
- package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +7 -135
- package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts +46 -0
- package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js +160 -0
- package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js.map +1 -0
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +11 -45
- package/packages/pi-ai/src/providers/openai-completions.ts +16 -86
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -96
- package/packages/pi-ai/src/providers/openai-shared.ts +193 -0
- package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +14 -162
- package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +13 -161
- package/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts +201 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +16 -63
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +104 -641
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +0 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +4 -35
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js +5 -43
- package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +11 -69
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +40 -0
- package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/utils.js +78 -0
- package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +77 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +331 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +129 -243
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +49 -42
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js +2 -21
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lock-utils.d.ts +39 -0
- package/packages/pi-coding-agent/dist/core/lock-utils.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lock-utils.js +89 -0
- package/packages/pi-coding-agent/dist/core/lock-utils.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js +4 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +52 -107
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +2 -21
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +0 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +0 -28
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +2 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +2 -4
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +46 -60
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +87 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.js +295 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +0 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +3 -28
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +76 -166
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +1 -3
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +9 -26
- package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts +44 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js +61 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js +6 -9
- package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +65 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +6 -16
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +175 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js +15 -0
- package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/print-mode.js +2 -30
- package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +2 -28
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts +19 -0
- package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js +45 -0
- package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/error.d.ts +5 -0
- package/packages/pi-coding-agent/dist/utils/error.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/error.js +7 -0
- package/packages/pi-coding-agent/dist/utils/error.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +117 -745
- package/packages/pi-coding-agent/src/core/auth-storage.ts +4 -38
- package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +7 -53
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +14 -74
- package/packages/pi-coding-agent/src/core/compaction/utils.ts +100 -0
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +424 -0
- package/packages/pi-coding-agent/src/core/extensions/index.ts +1 -21
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +119 -243
- package/packages/pi-coding-agent/src/core/extensions/types.ts +50 -69
- package/packages/pi-coding-agent/src/core/lock-utils.ts +113 -0
- package/packages/pi-coding-agent/src/core/lsp/config.ts +4 -1
- package/packages/pi-coding-agent/src/core/lsp/index.ts +83 -152
- package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +2 -22
- package/packages/pi-coding-agent/src/core/lsp/types.ts +0 -29
- package/packages/pi-coding-agent/src/core/package-manager.ts +1 -4
- package/packages/pi-coding-agent/src/core/resource-loader.ts +56 -69
- package/packages/pi-coding-agent/src/core/retry-handler.ts +359 -0
- package/packages/pi-coding-agent/src/core/session-manager.ts +3 -30
- package/packages/pi-coding-agent/src/core/settings-manager.ts +85 -164
- package/packages/pi-coding-agent/src/core/skills.ts +1 -4
- package/packages/pi-coding-agent/src/index.ts +1 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +17 -29
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -13
- package/packages/pi-coding-agent/src/modes/interactive/components/tree-render-utils.ts +81 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tree-selector.ts +14 -19
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +7 -18
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +196 -0
- package/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts +14 -0
- package/packages/pi-coding-agent/src/modes/print-mode.ts +2 -30
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -28
- package/packages/pi-coding-agent/src/modes/shared/command-context-actions.ts +53 -0
- package/packages/pi-coding-agent/src/utils/error.ts +6 -0
- package/packages/pi-tui/dist/components/markdown.d.ts +5 -0
- package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/markdown.js +25 -31
- package/packages/pi-tui/dist/components/markdown.js.map +1 -1
- package/packages/pi-tui/dist/keys.d.ts +0 -4
- package/packages/pi-tui/dist/keys.d.ts.map +1 -1
- package/packages/pi-tui/dist/keys.js +94 -162
- package/packages/pi-tui/dist/keys.js.map +1 -1
- package/packages/pi-tui/src/components/markdown.ts +25 -29
- package/packages/pi-tui/src/keys.ts +94 -173
- package/pkg/dist/modes/interactive/theme/theme.d.ts +65 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +6 -16
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.d.ts +12 -0
- package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -0
- package/pkg/dist/modes/interactive/theme/themes.js +175 -0
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
- package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
- package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
- package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
- package/src/resources/extensions/bg-shell/types.ts +0 -12
- package/src/resources/extensions/context7/index.ts +7 -0
- package/src/resources/extensions/get-secrets-from-user.ts +2 -35
- package/src/resources/extensions/google-search/index.ts +7 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
- package/src/resources/extensions/gsd/auto-loop.ts +11 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
- package/src/resources/extensions/gsd/auto-start.ts +42 -2
- package/src/resources/extensions/gsd/auto.ts +61 -3
- package/src/resources/extensions/gsd/changelog.ts +213 -0
- package/src/resources/extensions/gsd/commands-bootstrap.ts +1 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
- package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +5 -1
- package/src/resources/extensions/gsd/commands.ts +9 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
- package/src/resources/extensions/gsd/doctor-checks.ts +107 -5
- package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
- package/src/resources/extensions/gsd/doctor-proactive.ts +24 -0
- package/src/resources/extensions/gsd/doctor-types.ts +9 -1
- package/src/resources/extensions/gsd/doctor.ts +35 -0
- package/src/resources/extensions/gsd/files.ts +12 -2
- package/src/resources/extensions/gsd/gitignore.ts +54 -7
- package/src/resources/extensions/gsd/guided-flow.ts +5 -3
- package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
- package/src/resources/extensions/gsd/health-widget.ts +103 -59
- package/src/resources/extensions/gsd/index.ts +10 -1
- package/src/resources/extensions/gsd/migrate-external.ts +47 -2
- package/src/resources/extensions/gsd/paths.ts +73 -7
- package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +54 -1
- package/src/resources/extensions/gsd/preferences.ts +2 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
- package/src/resources/extensions/gsd/session-lock.ts +29 -2
- package/src/resources/extensions/gsd/templates/plan.md +8 -0
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +98 -2
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +59 -3
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +40 -2
- package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
- package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
- package/src/resources/extensions/shared/mod.ts +1 -1
- package/src/resources/extensions/shared/sanitize.ts +36 -0
- package/src/resources/extensions/subagent/index.ts +6 -12
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
- package/dist/resources/extensions/shared/wizard-ui.js +0 -478
- package/packages/pi-coding-agent/dist/modes/interactive/theme/dark.json +0 -85
- package/packages/pi-coding-agent/dist/modes/interactive/theme/light.json +0 -84
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.json +0 -335
- package/packages/pi-coding-agent/src/modes/interactive/theme/dark.json +0 -85
- package/packages/pi-coding-agent/src/modes/interactive/theme/light.json +0 -84
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.json +0 -335
- package/pkg/dist/modes/interactive/theme/dark.json +0 -85
- package/pkg/dist/modes/interactive/theme/light.json +0 -84
- package/pkg/dist/modes/interactive/theme/theme-schema.json +0 -335
- package/src/resources/extensions/shared/wizard-ui.ts +0 -551
|
@@ -24,22 +24,20 @@ import type {
|
|
|
24
24
|
ThinkingLevel,
|
|
25
25
|
} from "@gsd/pi-agent-core";
|
|
26
26
|
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@gsd/pi-ai";
|
|
27
|
-
import {
|
|
27
|
+
import { modelsAreEqual, resetApiProviders, supportsXhigh } from "@gsd/pi-ai";
|
|
28
28
|
import { getDocsPath } from "../config.js";
|
|
29
|
+
import { getErrorMessage } from "../utils/error.js";
|
|
29
30
|
import { theme } from "../modes/interactive/theme/theme.js";
|
|
30
31
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
31
|
-
import { sleep } from "../utils/sleep.js";
|
|
32
32
|
import { type BashResult, executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
|
|
33
33
|
import {
|
|
34
34
|
type CompactionResult,
|
|
35
35
|
calculateContextTokens,
|
|
36
36
|
collectEntriesForBranchSummary,
|
|
37
|
-
compact,
|
|
38
37
|
estimateContextTokens,
|
|
39
38
|
generateBranchSummary,
|
|
40
|
-
prepareCompaction,
|
|
41
|
-
shouldCompact,
|
|
42
39
|
} from "./compaction/index.js";
|
|
40
|
+
import { CompactionOrchestrator } from "./compaction-orchestrator.js";
|
|
43
41
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
44
42
|
import { exportSessionToHtml, type ToolHtmlRenderer } from "./export-html/index.js";
|
|
45
43
|
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
@@ -53,7 +51,6 @@ import {
|
|
|
53
51
|
type MessageEndEvent,
|
|
54
52
|
type MessageStartEvent,
|
|
55
53
|
type MessageUpdateEvent,
|
|
56
|
-
type SessionBeforeCompactResult,
|
|
57
54
|
type SessionBeforeForkResult,
|
|
58
55
|
type SessionBeforeSwitchResult,
|
|
59
56
|
type SessionBeforeTreeResult,
|
|
@@ -73,7 +70,8 @@ import { FallbackResolver } from "./fallback-resolver.js";
|
|
|
73
70
|
import type { ModelRegistry } from "./model-registry.js";
|
|
74
71
|
import { expandPromptTemplate, type PromptTemplate } from "./prompt-templates.js";
|
|
75
72
|
import type { ResourceExtensionPaths, ResourceLoader } from "./resource-loader.js";
|
|
76
|
-
import
|
|
73
|
+
import { RetryHandler } from "./retry-handler.js";
|
|
74
|
+
import type { BranchSummaryEntry, SessionManager } from "./session-manager.js";
|
|
77
75
|
import { getLatestCompactionEntry } from "./session-manager.js";
|
|
78
76
|
import type { SettingsManager } from "./settings-manager.js";
|
|
79
77
|
import { BUILTIN_SLASH_COMMANDS, type SlashCommandInfo, type SlashCommandLocation } from "./slash-commands.js";
|
|
@@ -232,19 +230,15 @@ export class AgentSession {
|
|
|
232
230
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
233
231
|
private _pendingNextTurnMessages: CustomMessage[] = [];
|
|
234
232
|
|
|
235
|
-
//
|
|
236
|
-
private
|
|
237
|
-
private
|
|
238
|
-
private _overflowRecoveryAttempted = false;
|
|
233
|
+
// Delegated subsystems
|
|
234
|
+
private _retryHandler: RetryHandler;
|
|
235
|
+
private _compactionOrchestrator: CompactionOrchestrator;
|
|
239
236
|
|
|
240
|
-
//
|
|
241
|
-
private
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
private
|
|
245
|
-
private _retryAttempt = 0;
|
|
246
|
-
private _retryPromise: Promise<void> | undefined = undefined;
|
|
247
|
-
private _retryResolve: (() => void) | undefined = undefined;
|
|
237
|
+
// Cumulative session stats — survives compaction (#1423)
|
|
238
|
+
private _cumulativeCost = 0;
|
|
239
|
+
private _cumulativeInputTokens = 0;
|
|
240
|
+
private _cumulativeOutputTokens = 0;
|
|
241
|
+
private _cumulativeToolCalls = 0;
|
|
248
242
|
|
|
249
243
|
// Bash execution state
|
|
250
244
|
private _bashAbortController: AbortController | undefined = undefined;
|
|
@@ -299,6 +293,32 @@ export class AgentSession {
|
|
|
299
293
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
|
300
294
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
301
295
|
|
|
296
|
+
// Initialize delegated subsystems
|
|
297
|
+
this._retryHandler = new RetryHandler({
|
|
298
|
+
agent: this.agent,
|
|
299
|
+
settingsManager: this.settingsManager,
|
|
300
|
+
modelRegistry: this._modelRegistry,
|
|
301
|
+
fallbackResolver: this._fallbackResolver,
|
|
302
|
+
getModel: () => this.model,
|
|
303
|
+
getSessionId: () => this.sessionId,
|
|
304
|
+
emit: (event) => this._emit(event),
|
|
305
|
+
onModelChange: (model) => this.sessionManager.appendModelChange(model.provider, model.id),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
this._compactionOrchestrator = new CompactionOrchestrator({
|
|
309
|
+
agent: this.agent,
|
|
310
|
+
sessionManager: this.sessionManager,
|
|
311
|
+
settingsManager: this.settingsManager,
|
|
312
|
+
modelRegistry: this._modelRegistry,
|
|
313
|
+
getModel: () => this.model,
|
|
314
|
+
getSessionId: () => this.sessionId,
|
|
315
|
+
getExtensionRunner: () => this._extensionRunner,
|
|
316
|
+
emit: (event) => this._emit(event),
|
|
317
|
+
disconnectFromAgent: () => this._disconnectFromAgent(),
|
|
318
|
+
reconnectToAgent: () => this._reconnectToAgent(),
|
|
319
|
+
abort: () => this.abort(),
|
|
320
|
+
});
|
|
321
|
+
|
|
302
322
|
// Always subscribe to agent events for internal handling
|
|
303
323
|
// (session persistence, extensions, auto-compaction, retry logic)
|
|
304
324
|
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
@@ -342,7 +362,7 @@ export class AgentSession {
|
|
|
342
362
|
private _handleAgentEvent = (event: AgentEvent): void => {
|
|
343
363
|
// Create retry promise synchronously before queueing async processing.
|
|
344
364
|
// Agent.emit() calls this handler synchronously, and prompt() calls waitForRetry()
|
|
345
|
-
// as soon as agent.prompt() resolves. If
|
|
365
|
+
// as soon as agent.prompt() resolves. If the retry promise is created only inside
|
|
346
366
|
// _processAgentEvent, slow earlier queued events can delay agent_end processing
|
|
347
367
|
// and waitForRetry() can miss the in-flight retry.
|
|
348
368
|
this._createRetryPromiseForAgentEnd(event);
|
|
@@ -357,40 +377,15 @@ export class AgentSession {
|
|
|
357
377
|
};
|
|
358
378
|
|
|
359
379
|
private _createRetryPromiseForAgentEnd(event: AgentEvent): void {
|
|
360
|
-
if (event.type !== "agent_end"
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const settings = this.settingsManager.getRetrySettings();
|
|
365
|
-
if (!settings.enabled) {
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const lastAssistant = this._findLastAssistantInMessages(event.messages);
|
|
370
|
-
if (!lastAssistant || !this._isRetryableError(lastAssistant)) {
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
this._retryPromise = new Promise((resolve) => {
|
|
375
|
-
this._retryResolve = resolve;
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
private _findLastAssistantInMessages(messages: AgentMessage[]): AssistantMessage | undefined {
|
|
380
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
381
|
-
const message = messages[i];
|
|
382
|
-
if (message.role === "assistant") {
|
|
383
|
-
return message as AssistantMessage;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return undefined;
|
|
380
|
+
if (event.type !== "agent_end") return;
|
|
381
|
+
this._retryHandler.createRetryPromiseForAgentEnd(event.messages);
|
|
387
382
|
}
|
|
388
383
|
|
|
389
384
|
private async _processAgentEvent(event: AgentEvent): Promise<void> {
|
|
390
385
|
// When a user message starts, check if it's from either queue and remove it BEFORE emitting
|
|
391
386
|
// This ensures the UI sees the updated queue state
|
|
392
387
|
if (event.type === "message_start" && event.message.role === "user") {
|
|
393
|
-
this.
|
|
388
|
+
this._compactionOrchestrator.resetOverflowRecovery();
|
|
394
389
|
const messageText = this._getUserMessageText(event.message);
|
|
395
390
|
if (messageText) {
|
|
396
391
|
// Check steering queue first
|
|
@@ -438,21 +433,21 @@ export class AgentSession {
|
|
|
438
433
|
if (event.message.role === "assistant") {
|
|
439
434
|
this._lastAssistantMessage = event.message;
|
|
440
435
|
|
|
436
|
+
// Accumulate session stats that survive compaction (#1423)
|
|
441
437
|
const assistantMsg = event.message as AssistantMessage;
|
|
438
|
+
this._cumulativeCost += assistantMsg.usage?.cost?.total ?? 0;
|
|
439
|
+
this._cumulativeInputTokens += assistantMsg.usage?.input ?? 0;
|
|
440
|
+
this._cumulativeOutputTokens += assistantMsg.usage?.output ?? 0;
|
|
441
|
+
this._cumulativeToolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
|
|
442
|
+
|
|
442
443
|
if (assistantMsg.stopReason !== "error") {
|
|
443
|
-
this.
|
|
444
|
+
this._compactionOrchestrator.clearOverflowRecovery();
|
|
444
445
|
}
|
|
445
446
|
|
|
446
447
|
// Reset retry counter immediately on successful assistant response
|
|
447
448
|
// This prevents accumulation across multiple LLM calls within a turn
|
|
448
|
-
if (assistantMsg.stopReason !== "error"
|
|
449
|
-
this.
|
|
450
|
-
type: "auto_retry_end",
|
|
451
|
-
success: true,
|
|
452
|
-
attempt: this._retryAttempt,
|
|
453
|
-
});
|
|
454
|
-
this._retryAttempt = 0;
|
|
455
|
-
this._resolveRetry();
|
|
449
|
+
if (assistantMsg.stopReason !== "error") {
|
|
450
|
+
this._retryHandler.handleSuccessfulResponse();
|
|
456
451
|
}
|
|
457
452
|
}
|
|
458
453
|
}
|
|
@@ -463,21 +458,12 @@ export class AgentSession {
|
|
|
463
458
|
this._lastAssistantMessage = undefined;
|
|
464
459
|
|
|
465
460
|
// Check for retryable errors first (overloaded, rate limit, server errors)
|
|
466
|
-
if (this.
|
|
467
|
-
const didRetry = await this.
|
|
461
|
+
if (this._retryHandler.isRetryableError(msg)) {
|
|
462
|
+
const didRetry = await this._retryHandler.handleRetryableError(msg);
|
|
468
463
|
if (didRetry) return; // Retry was initiated, don't proceed to compaction
|
|
469
464
|
}
|
|
470
465
|
|
|
471
|
-
await this.
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/** Resolve the pending retry promise */
|
|
476
|
-
private _resolveRetry(): void {
|
|
477
|
-
if (this._retryResolve) {
|
|
478
|
-
this._retryResolve();
|
|
479
|
-
this._retryResolve = undefined;
|
|
480
|
-
this._retryPromise = undefined;
|
|
466
|
+
await this._compactionOrchestrator.checkCompaction(msg);
|
|
481
467
|
}
|
|
482
468
|
}
|
|
483
469
|
|
|
@@ -512,10 +498,7 @@ export class AgentSession {
|
|
|
512
498
|
};
|
|
513
499
|
}
|
|
514
500
|
} catch (err) {
|
|
515
|
-
|
|
516
|
-
return { block: true, reason: err.message };
|
|
517
|
-
}
|
|
518
|
-
return { block: true, reason: `Extension failed, blocking execution: ${String(err)}` };
|
|
501
|
+
return { block: true, reason: err instanceof Error ? err.message : `Extension failed, blocking execution: ${String(err)}` };
|
|
519
502
|
}
|
|
520
503
|
|
|
521
504
|
return undefined;
|
|
@@ -720,7 +703,7 @@ export class AgentSession {
|
|
|
720
703
|
|
|
721
704
|
/** Current retry attempt (0 if not retrying) */
|
|
722
705
|
get retryAttempt(): number {
|
|
723
|
-
return this.
|
|
706
|
+
return this._retryHandler.retryAttempt;
|
|
724
707
|
}
|
|
725
708
|
|
|
726
709
|
/**
|
|
@@ -767,11 +750,7 @@ export class AgentSession {
|
|
|
767
750
|
|
|
768
751
|
/** Whether compaction or branch summarization is currently running */
|
|
769
752
|
get isCompacting(): boolean {
|
|
770
|
-
return
|
|
771
|
-
this._autoCompactionAbortController !== undefined ||
|
|
772
|
-
this._compactionAbortController !== undefined ||
|
|
773
|
-
this._branchSummaryAbortController !== undefined
|
|
774
|
-
);
|
|
753
|
+
return this._compactionOrchestrator.isCompacting;
|
|
775
754
|
}
|
|
776
755
|
|
|
777
756
|
/**
|
|
@@ -1025,7 +1004,7 @@ export class AgentSession {
|
|
|
1025
1004
|
// Check if we need to compact before sending (catches aborted responses)
|
|
1026
1005
|
const lastAssistant = this._findLastAssistantMessage();
|
|
1027
1006
|
if (lastAssistant) {
|
|
1028
|
-
await this.
|
|
1007
|
+
await this._compactionOrchestrator.checkCompaction(lastAssistant, false);
|
|
1029
1008
|
}
|
|
1030
1009
|
|
|
1031
1010
|
// Build messages array (custom message if any, then user message)
|
|
@@ -1078,7 +1057,7 @@ export class AgentSession {
|
|
|
1078
1057
|
}
|
|
1079
1058
|
|
|
1080
1059
|
await this.agent.prompt(messages);
|
|
1081
|
-
await this.waitForRetry();
|
|
1060
|
+
await this._retryHandler.waitForRetry();
|
|
1082
1061
|
}
|
|
1083
1062
|
|
|
1084
1063
|
/**
|
|
@@ -1106,7 +1085,7 @@ export class AgentSession {
|
|
|
1106
1085
|
this._extensionRunner.emitError({
|
|
1107
1086
|
extensionPath: `command:${commandName}`,
|
|
1108
1087
|
event: "command",
|
|
1109
|
-
error:
|
|
1088
|
+
error: getErrorMessage(err),
|
|
1110
1089
|
});
|
|
1111
1090
|
return true;
|
|
1112
1091
|
}
|
|
@@ -1137,7 +1116,7 @@ export class AgentSession {
|
|
|
1137
1116
|
this._extensionRunner?.emitError({
|
|
1138
1117
|
extensionPath: skill.filePath,
|
|
1139
1118
|
event: "skill_expansion",
|
|
1140
|
-
error:
|
|
1119
|
+
error: getErrorMessage(err),
|
|
1141
1120
|
});
|
|
1142
1121
|
return text; // Return original on error
|
|
1143
1122
|
}
|
|
@@ -1356,7 +1335,7 @@ export class AgentSession {
|
|
|
1356
1335
|
* Abort current operation and wait for agent to become idle.
|
|
1357
1336
|
*/
|
|
1358
1337
|
async abort(): Promise<void> {
|
|
1359
|
-
this.abortRetry();
|
|
1338
|
+
this._retryHandler.abortRetry();
|
|
1360
1339
|
this.agent.abort();
|
|
1361
1340
|
await this.agent.waitForIdle();
|
|
1362
1341
|
// Ensure agent_end is emitted even when abort interrupts a tool call (#1414).
|
|
@@ -1464,6 +1443,26 @@ export class AgentSession {
|
|
|
1464
1443
|
});
|
|
1465
1444
|
}
|
|
1466
1445
|
|
|
1446
|
+
/**
|
|
1447
|
+
* Apply a model change: set the model on the agent, persist to session/settings,
|
|
1448
|
+
* re-clamp thinking level, and emit the model_select event.
|
|
1449
|
+
*/
|
|
1450
|
+
private async _applyModelChange(
|
|
1451
|
+
model: Model<any>,
|
|
1452
|
+
thinkingLevel: ThinkingLevel,
|
|
1453
|
+
source: "set" | "cycle" | "restore",
|
|
1454
|
+
options?: { persist?: boolean },
|
|
1455
|
+
): Promise<void> {
|
|
1456
|
+
const previousModel = this.model;
|
|
1457
|
+
this.agent.setModel(model);
|
|
1458
|
+
this.sessionManager.appendModelChange(model.provider, model.id);
|
|
1459
|
+
if (options?.persist !== false) {
|
|
1460
|
+
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
|
1461
|
+
}
|
|
1462
|
+
this.setThinkingLevel(thinkingLevel);
|
|
1463
|
+
await this._emitModelSelect(model, previousModel, source);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1467
1466
|
/**
|
|
1468
1467
|
* Set model directly.
|
|
1469
1468
|
* Validates API key, saves to session and settings.
|
|
@@ -1475,18 +1474,8 @@ export class AgentSession {
|
|
|
1475
1474
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
1476
1475
|
}
|
|
1477
1476
|
|
|
1478
|
-
const previousModel = this.model;
|
|
1479
1477
|
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1480
|
-
this.
|
|
1481
|
-
this.sessionManager.appendModelChange(model.provider, model.id);
|
|
1482
|
-
if (options?.persist !== false) {
|
|
1483
|
-
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
// Re-clamp thinking level for new model's capabilities
|
|
1487
|
-
this.setThinkingLevel(thinkingLevel);
|
|
1488
|
-
|
|
1489
|
-
await this._emitModelSelect(model, previousModel, "set");
|
|
1478
|
+
await this._applyModelChange(model, thinkingLevel, "set", options);
|
|
1490
1479
|
}
|
|
1491
1480
|
|
|
1492
1481
|
/**
|
|
@@ -1535,22 +1524,11 @@ export class AgentSession {
|
|
|
1535
1524
|
const len = scopedModels.length;
|
|
1536
1525
|
const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
|
|
1537
1526
|
const next = scopedModels[nextIndex];
|
|
1538
|
-
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
|
|
1539
|
-
|
|
1540
|
-
// Apply model
|
|
1541
|
-
this.agent.setModel(next.model);
|
|
1542
|
-
this.sessionManager.appendModelChange(next.model.provider, next.model.id);
|
|
1543
|
-
if (options?.persist !== false) {
|
|
1544
|
-
this.settingsManager.setDefaultModelAndProvider(next.model.provider, next.model.id);
|
|
1545
|
-
}
|
|
1546
1527
|
|
|
1547
|
-
//
|
|
1548
|
-
//
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
this.setThinkingLevel(thinkingLevel);
|
|
1552
|
-
|
|
1553
|
-
await this._emitModelSelect(next.model, currentModel, "cycle");
|
|
1528
|
+
// Explicit scoped model thinking level overrides current session level;
|
|
1529
|
+
// undefined scoped model thinking level inherits the current session preference.
|
|
1530
|
+
const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
|
|
1531
|
+
await this._applyModelChange(next.model, thinkingLevel, "cycle", options);
|
|
1554
1532
|
|
|
1555
1533
|
return { model: next.model, thinkingLevel: this.thinkingLevel, isScoped: true };
|
|
1556
1534
|
}
|
|
@@ -1573,16 +1551,7 @@ export class AgentSession {
|
|
|
1573
1551
|
}
|
|
1574
1552
|
|
|
1575
1553
|
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
1576
|
-
this.
|
|
1577
|
-
this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
|
|
1578
|
-
if (options?.persist !== false) {
|
|
1579
|
-
this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// Re-clamp thinking level for new model's capabilities
|
|
1583
|
-
this.setThinkingLevel(thinkingLevel);
|
|
1584
|
-
|
|
1585
|
-
await this._emitModelSelect(nextModel, currentModel, "cycle");
|
|
1554
|
+
await this._applyModelChange(nextModel, thinkingLevel, "cycle", options);
|
|
1586
1555
|
|
|
1587
1556
|
return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
|
|
1588
1557
|
}
|
|
@@ -1712,373 +1681,27 @@ export class AgentSession {
|
|
|
1712
1681
|
* @param customInstructions Optional instructions for the compaction summary
|
|
1713
1682
|
*/
|
|
1714
1683
|
async compact(customInstructions?: string): Promise<CompactionResult> {
|
|
1715
|
-
this.
|
|
1716
|
-
await this.abort();
|
|
1717
|
-
this._compactionAbortController = new AbortController();
|
|
1718
|
-
|
|
1719
|
-
try {
|
|
1720
|
-
if (!this.model) {
|
|
1721
|
-
throw new Error("No model selected");
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
const apiKey = await this._modelRegistry.getApiKey(this.model, this.sessionId);
|
|
1725
|
-
if (!apiKey) {
|
|
1726
|
-
throw new Error(`No API key for ${this.model.provider}`);
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
const pathEntries = this.sessionManager.getBranch();
|
|
1730
|
-
const settings = this.settingsManager.getCompactionSettings();
|
|
1731
|
-
|
|
1732
|
-
const preparation = prepareCompaction(pathEntries, settings);
|
|
1733
|
-
if (!preparation) {
|
|
1734
|
-
// Check why we can't compact
|
|
1735
|
-
const lastEntry = pathEntries[pathEntries.length - 1];
|
|
1736
|
-
if (lastEntry?.type === "compaction") {
|
|
1737
|
-
throw new Error("Already compacted");
|
|
1738
|
-
}
|
|
1739
|
-
throw new Error("Nothing to compact (session too small)");
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
let extensionCompaction: CompactionResult | undefined;
|
|
1743
|
-
let fromExtension = false;
|
|
1744
|
-
|
|
1745
|
-
if (this._extensionRunner?.hasHandlers("session_before_compact")) {
|
|
1746
|
-
const result = (await this._extensionRunner.emit({
|
|
1747
|
-
type: "session_before_compact",
|
|
1748
|
-
preparation,
|
|
1749
|
-
branchEntries: pathEntries,
|
|
1750
|
-
customInstructions,
|
|
1751
|
-
signal: this._compactionAbortController.signal,
|
|
1752
|
-
})) as SessionBeforeCompactResult | undefined;
|
|
1753
|
-
|
|
1754
|
-
if (result?.cancel) {
|
|
1755
|
-
throw new Error("Compaction cancelled");
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
if (result?.compaction) {
|
|
1759
|
-
extensionCompaction = result.compaction;
|
|
1760
|
-
fromExtension = true;
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
let summary: string;
|
|
1765
|
-
let firstKeptEntryId: string;
|
|
1766
|
-
let tokensBefore: number;
|
|
1767
|
-
let details: unknown;
|
|
1768
|
-
|
|
1769
|
-
if (extensionCompaction) {
|
|
1770
|
-
// Extension provided compaction content
|
|
1771
|
-
summary = extensionCompaction.summary;
|
|
1772
|
-
firstKeptEntryId = extensionCompaction.firstKeptEntryId;
|
|
1773
|
-
tokensBefore = extensionCompaction.tokensBefore;
|
|
1774
|
-
details = extensionCompaction.details;
|
|
1775
|
-
} else {
|
|
1776
|
-
// Generate compaction result
|
|
1777
|
-
const result = await compact(
|
|
1778
|
-
preparation,
|
|
1779
|
-
this.model,
|
|
1780
|
-
apiKey,
|
|
1781
|
-
customInstructions,
|
|
1782
|
-
this._compactionAbortController.signal,
|
|
1783
|
-
);
|
|
1784
|
-
summary = result.summary;
|
|
1785
|
-
firstKeptEntryId = result.firstKeptEntryId;
|
|
1786
|
-
tokensBefore = result.tokensBefore;
|
|
1787
|
-
details = result.details;
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
if (this._compactionAbortController.signal.aborted) {
|
|
1791
|
-
throw new Error("Compaction cancelled");
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1795
|
-
const newEntries = this.sessionManager.getEntries();
|
|
1796
|
-
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1797
|
-
this.agent.replaceMessages(sessionContext.messages);
|
|
1798
|
-
|
|
1799
|
-
// Get the saved compaction entry for the extension event
|
|
1800
|
-
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary) as
|
|
1801
|
-
| CompactionEntry
|
|
1802
|
-
| undefined;
|
|
1803
|
-
|
|
1804
|
-
if (this._extensionRunner && savedCompactionEntry) {
|
|
1805
|
-
await this._extensionRunner.emit({
|
|
1806
|
-
type: "session_compact",
|
|
1807
|
-
compactionEntry: savedCompactionEntry,
|
|
1808
|
-
fromExtension,
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
return {
|
|
1813
|
-
summary,
|
|
1814
|
-
firstKeptEntryId,
|
|
1815
|
-
tokensBefore,
|
|
1816
|
-
details,
|
|
1817
|
-
};
|
|
1818
|
-
} finally {
|
|
1819
|
-
this._compactionAbortController = undefined;
|
|
1820
|
-
this._reconnectToAgent();
|
|
1821
|
-
}
|
|
1684
|
+
return this._compactionOrchestrator.compact(customInstructions);
|
|
1822
1685
|
}
|
|
1823
1686
|
|
|
1824
|
-
/**
|
|
1825
|
-
* Cancel in-progress compaction (manual or auto).
|
|
1826
|
-
*/
|
|
1687
|
+
/** Cancel in-progress compaction (manual or auto) */
|
|
1827
1688
|
abortCompaction(): void {
|
|
1828
|
-
this.
|
|
1829
|
-
this._autoCompactionAbortController?.abort();
|
|
1689
|
+
this._compactionOrchestrator.abortCompaction();
|
|
1830
1690
|
}
|
|
1831
1691
|
|
|
1832
|
-
/**
|
|
1833
|
-
* Cancel in-progress branch summarization.
|
|
1834
|
-
*/
|
|
1692
|
+
/** Cancel in-progress branch summarization */
|
|
1835
1693
|
abortBranchSummary(): void {
|
|
1836
|
-
this.
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
/**
|
|
1840
|
-
* Check if compaction is needed and run it.
|
|
1841
|
-
* Called after agent_end and before prompt submission.
|
|
1842
|
-
*
|
|
1843
|
-
* Two cases:
|
|
1844
|
-
* 1. Overflow: LLM returned context overflow error, remove error message from agent state, compact, auto-retry
|
|
1845
|
-
* 2. Threshold: Context over threshold, compact, NO auto-retry (user continues manually)
|
|
1846
|
-
*
|
|
1847
|
-
* @param assistantMessage The assistant message to check
|
|
1848
|
-
* @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
|
|
1849
|
-
*/
|
|
1850
|
-
private async _checkCompaction(assistantMessage: AssistantMessage, skipAbortedCheck = true): Promise<void> {
|
|
1851
|
-
const settings = this.settingsManager.getCompactionSettings();
|
|
1852
|
-
if (!settings.enabled) return;
|
|
1853
|
-
|
|
1854
|
-
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
1855
|
-
if (skipAbortedCheck && assistantMessage.stopReason === "aborted") return;
|
|
1856
|
-
|
|
1857
|
-
const contextWindow = this.model?.contextWindow ?? 0;
|
|
1858
|
-
|
|
1859
|
-
// Skip overflow check if the message came from a different model.
|
|
1860
|
-
// This handles the case where user switched from a smaller-context model (e.g. opus)
|
|
1861
|
-
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
|
1862
|
-
// shouldn't trigger compaction for the new model.
|
|
1863
|
-
const sameModel =
|
|
1864
|
-
this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
|
|
1865
|
-
|
|
1866
|
-
// Skip compaction checks if this assistant message is older than the latest
|
|
1867
|
-
// compaction boundary. This prevents a stale pre-compaction usage/error
|
|
1868
|
-
// from retriggering compaction on the first prompt after compaction.
|
|
1869
|
-
const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
1870
|
-
const assistantIsFromBeforeCompaction =
|
|
1871
|
-
compactionEntry !== null && assistantMessage.timestamp <= new Date(compactionEntry.timestamp).getTime();
|
|
1872
|
-
if (assistantIsFromBeforeCompaction) {
|
|
1873
|
-
return;
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
|
-
// Case 1: Overflow - LLM returned context overflow error
|
|
1877
|
-
if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
|
|
1878
|
-
if (this._overflowRecoveryAttempted) {
|
|
1879
|
-
this._emit({
|
|
1880
|
-
type: "auto_compaction_end",
|
|
1881
|
-
result: undefined,
|
|
1882
|
-
aborted: false,
|
|
1883
|
-
willRetry: false,
|
|
1884
|
-
errorMessage:
|
|
1885
|
-
"Context overflow recovery failed after one compact-and-retry attempt. Try reducing context or switching to a larger-context model.",
|
|
1886
|
-
});
|
|
1887
|
-
return;
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
this._overflowRecoveryAttempted = true;
|
|
1891
|
-
// Remove the error message from agent state (it IS saved to session for history,
|
|
1892
|
-
// but we don't want it in context for the retry)
|
|
1893
|
-
const messages = this.agent.state.messages;
|
|
1894
|
-
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
1895
|
-
this.agent.replaceMessages(messages.slice(0, -1));
|
|
1896
|
-
}
|
|
1897
|
-
await this._runAutoCompaction("overflow", true);
|
|
1898
|
-
return;
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
// Case 2: Threshold - context is getting large
|
|
1902
|
-
// For error messages (no usage data), estimate from last successful response.
|
|
1903
|
-
// This ensures sessions that hit persistent API errors (e.g. 529) can still compact.
|
|
1904
|
-
let contextTokens: number;
|
|
1905
|
-
if (assistantMessage.stopReason === "error") {
|
|
1906
|
-
const messages = this.agent.state.messages;
|
|
1907
|
-
const estimate = estimateContextTokens(messages);
|
|
1908
|
-
if (estimate.lastUsageIndex === null) return; // No usage data at all
|
|
1909
|
-
// Verify the usage source is post-compaction. Kept pre-compaction messages
|
|
1910
|
-
// have stale usage reflecting the old (larger) context and would falsely
|
|
1911
|
-
// trigger compaction right after one just finished.
|
|
1912
|
-
const usageMsg = messages[estimate.lastUsageIndex];
|
|
1913
|
-
if (
|
|
1914
|
-
compactionEntry &&
|
|
1915
|
-
usageMsg.role === "assistant" &&
|
|
1916
|
-
(usageMsg as AssistantMessage).timestamp <= new Date(compactionEntry.timestamp).getTime()
|
|
1917
|
-
) {
|
|
1918
|
-
return;
|
|
1919
|
-
}
|
|
1920
|
-
contextTokens = estimate.tokens;
|
|
1921
|
-
} else {
|
|
1922
|
-
contextTokens = calculateContextTokens(assistantMessage.usage);
|
|
1923
|
-
}
|
|
1924
|
-
if (shouldCompact(contextTokens, contextWindow, settings)) {
|
|
1925
|
-
await this._runAutoCompaction("threshold", false);
|
|
1926
|
-
}
|
|
1694
|
+
this._compactionOrchestrator.abortBranchSummary();
|
|
1927
1695
|
}
|
|
1928
1696
|
|
|
1929
|
-
/**
|
|
1930
|
-
* Internal: Run auto-compaction with events.
|
|
1931
|
-
*/
|
|
1932
|
-
private async _runAutoCompaction(reason: "overflow" | "threshold", willRetry: boolean): Promise<void> {
|
|
1933
|
-
const settings = this.settingsManager.getCompactionSettings();
|
|
1934
|
-
|
|
1935
|
-
this._emit({ type: "auto_compaction_start", reason });
|
|
1936
|
-
this._autoCompactionAbortController = new AbortController();
|
|
1937
|
-
|
|
1938
|
-
try {
|
|
1939
|
-
if (!this.model) {
|
|
1940
|
-
this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
|
|
1941
|
-
return;
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
|
-
const apiKey = await this._modelRegistry.getApiKey(this.model, this.sessionId);
|
|
1945
|
-
if (!apiKey) {
|
|
1946
|
-
this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
|
|
1947
|
-
return;
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
const pathEntries = this.sessionManager.getBranch();
|
|
1951
|
-
|
|
1952
|
-
const preparation = prepareCompaction(pathEntries, settings);
|
|
1953
|
-
if (!preparation) {
|
|
1954
|
-
this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
|
|
1955
|
-
return;
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
let extensionCompaction: CompactionResult | undefined;
|
|
1959
|
-
let fromExtension = false;
|
|
1960
|
-
|
|
1961
|
-
if (this._extensionRunner?.hasHandlers("session_before_compact")) {
|
|
1962
|
-
const extensionResult = (await this._extensionRunner.emit({
|
|
1963
|
-
type: "session_before_compact",
|
|
1964
|
-
preparation,
|
|
1965
|
-
branchEntries: pathEntries,
|
|
1966
|
-
customInstructions: undefined,
|
|
1967
|
-
signal: this._autoCompactionAbortController.signal,
|
|
1968
|
-
})) as SessionBeforeCompactResult | undefined;
|
|
1969
|
-
|
|
1970
|
-
if (extensionResult?.cancel) {
|
|
1971
|
-
this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
|
|
1972
|
-
return;
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
if (extensionResult?.compaction) {
|
|
1976
|
-
extensionCompaction = extensionResult.compaction;
|
|
1977
|
-
fromExtension = true;
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
let summary: string;
|
|
1982
|
-
let firstKeptEntryId: string;
|
|
1983
|
-
let tokensBefore: number;
|
|
1984
|
-
let details: unknown;
|
|
1985
|
-
|
|
1986
|
-
if (extensionCompaction) {
|
|
1987
|
-
// Extension provided compaction content
|
|
1988
|
-
summary = extensionCompaction.summary;
|
|
1989
|
-
firstKeptEntryId = extensionCompaction.firstKeptEntryId;
|
|
1990
|
-
tokensBefore = extensionCompaction.tokensBefore;
|
|
1991
|
-
details = extensionCompaction.details;
|
|
1992
|
-
} else {
|
|
1993
|
-
// Generate compaction result
|
|
1994
|
-
const compactResult = await compact(
|
|
1995
|
-
preparation,
|
|
1996
|
-
this.model,
|
|
1997
|
-
apiKey,
|
|
1998
|
-
undefined,
|
|
1999
|
-
this._autoCompactionAbortController.signal,
|
|
2000
|
-
);
|
|
2001
|
-
summary = compactResult.summary;
|
|
2002
|
-
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
2003
|
-
tokensBefore = compactResult.tokensBefore;
|
|
2004
|
-
details = compactResult.details;
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
if (this._autoCompactionAbortController.signal.aborted) {
|
|
2008
|
-
this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
|
|
2009
|
-
return;
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
2013
|
-
const newEntries = this.sessionManager.getEntries();
|
|
2014
|
-
const sessionContext = this.sessionManager.buildSessionContext();
|
|
2015
|
-
this.agent.replaceMessages(sessionContext.messages);
|
|
2016
|
-
|
|
2017
|
-
// Get the saved compaction entry for the extension event
|
|
2018
|
-
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary) as
|
|
2019
|
-
| CompactionEntry
|
|
2020
|
-
| undefined;
|
|
2021
|
-
|
|
2022
|
-
if (this._extensionRunner && savedCompactionEntry) {
|
|
2023
|
-
await this._extensionRunner.emit({
|
|
2024
|
-
type: "session_compact",
|
|
2025
|
-
compactionEntry: savedCompactionEntry,
|
|
2026
|
-
fromExtension,
|
|
2027
|
-
});
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
const result: CompactionResult = {
|
|
2031
|
-
summary,
|
|
2032
|
-
firstKeptEntryId,
|
|
2033
|
-
tokensBefore,
|
|
2034
|
-
details,
|
|
2035
|
-
};
|
|
2036
|
-
this._emit({ type: "auto_compaction_end", result, aborted: false, willRetry });
|
|
2037
|
-
|
|
2038
|
-
if (willRetry) {
|
|
2039
|
-
const messages = this.agent.state.messages;
|
|
2040
|
-
const lastMsg = messages[messages.length - 1];
|
|
2041
|
-
if (lastMsg?.role === "assistant" && (lastMsg as AssistantMessage).stopReason === "error") {
|
|
2042
|
-
this.agent.replaceMessages(messages.slice(0, -1));
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
setTimeout(() => {
|
|
2046
|
-
this.agent.continue().catch(() => {});
|
|
2047
|
-
}, 100);
|
|
2048
|
-
} else if (this.agent.hasQueuedMessages()) {
|
|
2049
|
-
// Auto-compaction can complete while follow-up/steering/custom messages are waiting.
|
|
2050
|
-
// Kick the loop so queued messages are actually delivered.
|
|
2051
|
-
setTimeout(() => {
|
|
2052
|
-
this.agent.continue().catch(() => {});
|
|
2053
|
-
}, 100);
|
|
2054
|
-
}
|
|
2055
|
-
} catch (error) {
|
|
2056
|
-
const errorMessage = error instanceof Error ? error.message : "compaction failed";
|
|
2057
|
-
this._emit({
|
|
2058
|
-
type: "auto_compaction_end",
|
|
2059
|
-
result: undefined,
|
|
2060
|
-
aborted: false,
|
|
2061
|
-
willRetry: false,
|
|
2062
|
-
errorMessage:
|
|
2063
|
-
reason === "overflow"
|
|
2064
|
-
? `Context overflow recovery failed: ${errorMessage}`
|
|
2065
|
-
: `Auto-compaction failed: ${errorMessage}`,
|
|
2066
|
-
});
|
|
2067
|
-
} finally {
|
|
2068
|
-
this._autoCompactionAbortController = undefined;
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
/**
|
|
2073
|
-
* Toggle auto-compaction setting.
|
|
2074
|
-
*/
|
|
1697
|
+
/** Toggle auto-compaction setting */
|
|
2075
1698
|
setAutoCompactionEnabled(enabled: boolean): void {
|
|
2076
|
-
this.
|
|
1699
|
+
this._compactionOrchestrator.setAutoCompactionEnabled(enabled);
|
|
2077
1700
|
}
|
|
2078
1701
|
|
|
2079
1702
|
/** Whether auto-compaction is enabled */
|
|
2080
1703
|
get autoCompactionEnabled(): boolean {
|
|
2081
|
-
return this.
|
|
1704
|
+
return this._compactionOrchestrator.autoCompactionEnabled;
|
|
2082
1705
|
}
|
|
2083
1706
|
|
|
2084
1707
|
async bindExtensions(bindings: ExtensionBindings): Promise<void> {
|
|
@@ -2212,7 +1835,7 @@ export class AgentSession {
|
|
|
2212
1835
|
runner.emitError({
|
|
2213
1836
|
extensionPath: "<runtime>",
|
|
2214
1837
|
event: "send_message",
|
|
2215
|
-
error:
|
|
1838
|
+
error: getErrorMessage(err),
|
|
2216
1839
|
});
|
|
2217
1840
|
});
|
|
2218
1841
|
},
|
|
@@ -2221,7 +1844,7 @@ export class AgentSession {
|
|
|
2221
1844
|
runner.emitError({
|
|
2222
1845
|
extensionPath: "<runtime>",
|
|
2223
1846
|
event: "send_user_message",
|
|
2224
|
-
error:
|
|
1847
|
+
error: getErrorMessage(err),
|
|
2225
1848
|
});
|
|
2226
1849
|
});
|
|
2227
1850
|
},
|
|
@@ -2234,7 +1857,7 @@ export class AgentSession {
|
|
|
2234
1857
|
runner.emitError({
|
|
2235
1858
|
extensionPath: "<runtime>",
|
|
2236
1859
|
event: "retry_last_turn",
|
|
2237
|
-
error:
|
|
1860
|
+
error: getErrorMessage(err),
|
|
2238
1861
|
});
|
|
2239
1862
|
});
|
|
2240
1863
|
}
|
|
@@ -2434,278 +2057,27 @@ export class AgentSession {
|
|
|
2434
2057
|
}
|
|
2435
2058
|
|
|
2436
2059
|
// =========================================================================
|
|
2437
|
-
// Auto-Retry
|
|
2060
|
+
// Auto-Retry (delegated to RetryHandler)
|
|
2438
2061
|
// =========================================================================
|
|
2439
2062
|
|
|
2440
|
-
/**
|
|
2441
|
-
* Check if an error is retryable (overloaded, rate limit, server errors).
|
|
2442
|
-
* Context overflow errors are NOT retryable (handled by compaction instead).
|
|
2443
|
-
*/
|
|
2444
|
-
private _isRetryableError(message: AssistantMessage): boolean {
|
|
2445
|
-
if (message.stopReason !== "error" || !message.errorMessage) return false;
|
|
2446
|
-
|
|
2447
|
-
// Context overflow is handled by compaction, not retry
|
|
2448
|
-
const contextWindow = this.model?.contextWindow ?? 0;
|
|
2449
|
-
if (isContextOverflow(message, contextWindow)) return false;
|
|
2450
|
-
|
|
2451
|
-
const err = message.errorMessage;
|
|
2452
|
-
// Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection errors, fetch failed, terminated, retry delay exceeded, network unavailable / auth expired (transient network failures)
|
|
2453
|
-
return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|network.?(?:is\s+)?unavailable|credentials.*expired|temporarily backed off/i.test(
|
|
2454
|
-
err,
|
|
2455
|
-
);
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
/**
|
|
2459
|
-
* Classify an error message into a usage-limit error type for credential backoff.
|
|
2460
|
-
*/
|
|
2461
|
-
private _classifyErrorType(errorMessage: string): import("./auth-storage.js").UsageLimitErrorType {
|
|
2462
|
-
const err = errorMessage.toLowerCase();
|
|
2463
|
-
if (/quota|billing|exceeded.*limit|usage.*limit/i.test(err)) return "quota_exhausted";
|
|
2464
|
-
if (/rate.?limit|too many requests|429/i.test(err)) return "rate_limit";
|
|
2465
|
-
if (/500|502|503|504|server.?error|internal.?error|service.?unavailable/i.test(err)) return "server_error";
|
|
2466
|
-
return "unknown";
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
|
-
/**
|
|
2470
|
-
* Handle retryable errors with exponential backoff.
|
|
2471
|
-
* When multiple credentials are available, marks the failing credential
|
|
2472
|
-
* as backed off and retries immediately with the next one.
|
|
2473
|
-
* @returns true if retry was initiated, false if max retries exceeded or disabled
|
|
2474
|
-
*/
|
|
2475
|
-
private async _handleRetryableError(message: AssistantMessage): Promise<boolean> {
|
|
2476
|
-
const settings = this.settingsManager.getRetrySettings();
|
|
2477
|
-
if (!settings.enabled) {
|
|
2478
|
-
this._resolveRetry();
|
|
2479
|
-
return false;
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
// Retry promise is created synchronously in _handleAgentEvent for agent_end.
|
|
2483
|
-
// Keep a defensive fallback here in case a future refactor bypasses that path.
|
|
2484
|
-
if (!this._retryPromise) {
|
|
2485
|
-
this._retryPromise = new Promise((resolve) => {
|
|
2486
|
-
this._retryResolve = resolve;
|
|
2487
|
-
});
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
// Try credential fallback before counting against retry budget.
|
|
2491
|
-
// If another credential is available, switch to it and retry immediately.
|
|
2492
|
-
// Only attempt credential rotation for errors that indicate a credential-level
|
|
2493
|
-
// problem (rate limit, quota exhaustion, server error). Transport failures
|
|
2494
|
-
// ("unknown") like connection resets are not credential-specific — rotating
|
|
2495
|
-
// won't help and backing off the only credential causes "Authentication failed".
|
|
2496
|
-
if (this.model && message.errorMessage) {
|
|
2497
|
-
const errorType = this._classifyErrorType(message.errorMessage);
|
|
2498
|
-
const isCredentialError = errorType !== "unknown";
|
|
2499
|
-
const hasAlternate = isCredentialError && this._modelRegistry.authStorage.markUsageLimitReached(
|
|
2500
|
-
this.model.provider,
|
|
2501
|
-
this.sessionId,
|
|
2502
|
-
{ errorType },
|
|
2503
|
-
);
|
|
2504
|
-
|
|
2505
|
-
if (hasAlternate) {
|
|
2506
|
-
// Remove error message from agent state
|
|
2507
|
-
const messages = this.agent.state.messages;
|
|
2508
|
-
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
2509
|
-
this.agent.replaceMessages(messages.slice(0, -1));
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
this._emit({
|
|
2513
|
-
type: "auto_retry_start",
|
|
2514
|
-
attempt: this._retryAttempt + 1,
|
|
2515
|
-
maxAttempts: settings.maxRetries,
|
|
2516
|
-
delayMs: 0,
|
|
2517
|
-
errorMessage: `${message.errorMessage} (switching credential)`,
|
|
2518
|
-
});
|
|
2519
|
-
|
|
2520
|
-
// Retry immediately with the next credential - don't increment _retryAttempt
|
|
2521
|
-
setTimeout(() => {
|
|
2522
|
-
this.agent.continue().catch(() => {
|
|
2523
|
-
// Retry failed - will be caught by next agent_end
|
|
2524
|
-
});
|
|
2525
|
-
}, 0);
|
|
2526
|
-
|
|
2527
|
-
return true;
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
|
-
// All credentials are backed off. Try cross-provider fallback before giving up.
|
|
2531
|
-
if (isCredentialError) {
|
|
2532
|
-
const fallbackResult = await this._fallbackResolver.findFallback(
|
|
2533
|
-
this.model,
|
|
2534
|
-
errorType,
|
|
2535
|
-
);
|
|
2536
|
-
|
|
2537
|
-
if (fallbackResult) {
|
|
2538
|
-
// Swap to fallback model — don't persist to settings
|
|
2539
|
-
const previousProvider = this.model.provider;
|
|
2540
|
-
this.agent.setModel(fallbackResult.model);
|
|
2541
|
-
this.sessionManager.appendModelChange(fallbackResult.model.provider, fallbackResult.model.id);
|
|
2542
|
-
|
|
2543
|
-
// Remove error message from agent state
|
|
2544
|
-
const msgs = this.agent.state.messages;
|
|
2545
|
-
if (msgs.length > 0 && msgs[msgs.length - 1].role === "assistant") {
|
|
2546
|
-
this.agent.replaceMessages(msgs.slice(0, -1));
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
this._emit({
|
|
2550
|
-
type: "fallback_provider_switch",
|
|
2551
|
-
from: `${previousProvider}/${this.model?.id}`,
|
|
2552
|
-
to: `${fallbackResult.model.provider}/${fallbackResult.model.id}`,
|
|
2553
|
-
reason: fallbackResult.reason,
|
|
2554
|
-
});
|
|
2555
|
-
|
|
2556
|
-
this._emit({
|
|
2557
|
-
type: "auto_retry_start",
|
|
2558
|
-
attempt: this._retryAttempt + 1,
|
|
2559
|
-
maxAttempts: settings.maxRetries,
|
|
2560
|
-
delayMs: 0,
|
|
2561
|
-
errorMessage: `${message.errorMessage} (${fallbackResult.reason})`,
|
|
2562
|
-
});
|
|
2563
|
-
|
|
2564
|
-
// Retry immediately with fallback provider - don't increment _retryAttempt
|
|
2565
|
-
setTimeout(() => {
|
|
2566
|
-
this.agent.continue().catch(() => {
|
|
2567
|
-
// Retry failed - will be caught by next agent_end
|
|
2568
|
-
});
|
|
2569
|
-
}, 0);
|
|
2570
|
-
|
|
2571
|
-
return true;
|
|
2572
|
-
}
|
|
2573
|
-
|
|
2574
|
-
// No fallback available either
|
|
2575
|
-
if (errorType === "quota_exhausted") {
|
|
2576
|
-
this._emit({
|
|
2577
|
-
type: "fallback_chain_exhausted",
|
|
2578
|
-
reason: `All providers exhausted for ${this.model.provider}/${this.model.id}`,
|
|
2579
|
-
});
|
|
2580
|
-
this._emit({
|
|
2581
|
-
type: "auto_retry_end",
|
|
2582
|
-
success: false,
|
|
2583
|
-
attempt: this._retryAttempt,
|
|
2584
|
-
finalError: message.errorMessage,
|
|
2585
|
-
});
|
|
2586
|
-
this._retryAttempt = 0;
|
|
2587
|
-
this._resolveRetry();
|
|
2588
|
-
return false;
|
|
2589
|
-
}
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
this._retryAttempt++;
|
|
2594
|
-
|
|
2595
|
-
if (this._retryAttempt > settings.maxRetries) {
|
|
2596
|
-
// Max retries exceeded, emit final failure and reset
|
|
2597
|
-
this._emit({
|
|
2598
|
-
type: "auto_retry_end",
|
|
2599
|
-
success: false,
|
|
2600
|
-
attempt: this._retryAttempt - 1,
|
|
2601
|
-
finalError: message.errorMessage,
|
|
2602
|
-
});
|
|
2603
|
-
this._retryAttempt = 0;
|
|
2604
|
-
this._resolveRetry(); // Resolve so waitForRetry() completes
|
|
2605
|
-
return false;
|
|
2606
|
-
}
|
|
2607
|
-
|
|
2608
|
-
// Use server-requested delay when available (rate limit headers), capped by maxDelayMs.
|
|
2609
|
-
// Fall back to exponential backoff when no server hint is present.
|
|
2610
|
-
const exponentialDelayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
|
|
2611
|
-
let delayMs: number;
|
|
2612
|
-
if (message.retryAfterMs !== undefined) {
|
|
2613
|
-
const cap = settings.maxDelayMs > 0 ? settings.maxDelayMs : Infinity;
|
|
2614
|
-
if (message.retryAfterMs > cap) {
|
|
2615
|
-
// Server wants us to wait longer than maxDelayMs — give up to let auto-mode handle recovery
|
|
2616
|
-
this._emit({
|
|
2617
|
-
type: "auto_retry_end",
|
|
2618
|
-
success: false,
|
|
2619
|
-
attempt: this._retryAttempt - 1,
|
|
2620
|
-
finalError: `Rate limit reset in ${Math.ceil(message.retryAfterMs / 1000)}s (max: ${Math.ceil(cap / 1000)}s). ${message.errorMessage || ""}`.trim(),
|
|
2621
|
-
});
|
|
2622
|
-
this._retryAttempt = 0;
|
|
2623
|
-
this._resolveRetry();
|
|
2624
|
-
return false;
|
|
2625
|
-
}
|
|
2626
|
-
delayMs = message.retryAfterMs;
|
|
2627
|
-
} else {
|
|
2628
|
-
delayMs = exponentialDelayMs;
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
this._emit({
|
|
2632
|
-
type: "auto_retry_start",
|
|
2633
|
-
attempt: this._retryAttempt,
|
|
2634
|
-
maxAttempts: settings.maxRetries,
|
|
2635
|
-
delayMs,
|
|
2636
|
-
errorMessage: message.errorMessage || "Unknown error",
|
|
2637
|
-
});
|
|
2638
|
-
|
|
2639
|
-
// Remove error message from agent state (keep in session for history)
|
|
2640
|
-
const messages = this.agent.state.messages;
|
|
2641
|
-
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
|
|
2642
|
-
this.agent.replaceMessages(messages.slice(0, -1));
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
// Wait with exponential backoff (abortable)
|
|
2646
|
-
this._retryAbortController = new AbortController();
|
|
2647
|
-
try {
|
|
2648
|
-
await sleep(delayMs, this._retryAbortController.signal);
|
|
2649
|
-
} catch {
|
|
2650
|
-
// Aborted during sleep - emit end event so UI can clean up
|
|
2651
|
-
const attempt = this._retryAttempt;
|
|
2652
|
-
this._retryAttempt = 0;
|
|
2653
|
-
this._retryAbortController = undefined;
|
|
2654
|
-
this._emit({
|
|
2655
|
-
type: "auto_retry_end",
|
|
2656
|
-
success: false,
|
|
2657
|
-
attempt,
|
|
2658
|
-
finalError: "Retry cancelled",
|
|
2659
|
-
});
|
|
2660
|
-
this._resolveRetry();
|
|
2661
|
-
return false;
|
|
2662
|
-
}
|
|
2663
|
-
this._retryAbortController = undefined;
|
|
2664
|
-
|
|
2665
|
-
// Retry via continue() - use setTimeout to break out of event handler chain
|
|
2666
|
-
setTimeout(() => {
|
|
2667
|
-
this.agent.continue().catch(() => {
|
|
2668
|
-
// Retry failed - will be caught by next agent_end
|
|
2669
|
-
});
|
|
2670
|
-
}, 0);
|
|
2671
|
-
|
|
2672
|
-
return true;
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
/**
|
|
2676
|
-
* Cancel in-progress retry.
|
|
2677
|
-
*/
|
|
2063
|
+
/** Cancel in-progress retry */
|
|
2678
2064
|
abortRetry(): void {
|
|
2679
|
-
this.
|
|
2680
|
-
// Note: _retryAttempt is reset in the catch block of _autoRetry
|
|
2681
|
-
this._resolveRetry();
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
|
-
/**
|
|
2685
|
-
* Wait for any in-progress retry to complete.
|
|
2686
|
-
* Returns immediately if no retry is in progress.
|
|
2687
|
-
*/
|
|
2688
|
-
private async waitForRetry(): Promise<void> {
|
|
2689
|
-
if (this._retryPromise) {
|
|
2690
|
-
await this._retryPromise;
|
|
2691
|
-
}
|
|
2065
|
+
this._retryHandler.abortRetry();
|
|
2692
2066
|
}
|
|
2693
2067
|
|
|
2694
2068
|
/** Whether auto-retry is currently in progress */
|
|
2695
2069
|
get isRetrying(): boolean {
|
|
2696
|
-
return this.
|
|
2070
|
+
return this._retryHandler.isRetrying;
|
|
2697
2071
|
}
|
|
2698
2072
|
|
|
2699
2073
|
/** Whether auto-retry is enabled */
|
|
2700
2074
|
get autoRetryEnabled(): boolean {
|
|
2701
|
-
return this.
|
|
2075
|
+
return this._retryHandler.autoRetryEnabled;
|
|
2702
2076
|
}
|
|
2703
2077
|
|
|
2704
|
-
/**
|
|
2705
|
-
* Toggle auto-retry setting.
|
|
2706
|
-
*/
|
|
2078
|
+
/** Toggle auto-retry setting */
|
|
2707
2079
|
setAutoRetryEnabled(enabled: boolean): void {
|
|
2708
|
-
this.
|
|
2080
|
+
this._retryHandler.setAutoRetryEnabled(enabled);
|
|
2709
2081
|
}
|
|
2710
2082
|
|
|
2711
2083
|
// =========================================================================
|
|
@@ -3029,7 +2401,7 @@ export class AgentSession {
|
|
|
3029
2401
|
};
|
|
3030
2402
|
|
|
3031
2403
|
// Set up abort controller for summarization
|
|
3032
|
-
this.
|
|
2404
|
+
this._compactionOrchestrator.branchSummaryAbortController = new AbortController();
|
|
3033
2405
|
let extensionSummary: { summary: string; details?: unknown } | undefined;
|
|
3034
2406
|
let fromExtension = false;
|
|
3035
2407
|
|
|
@@ -3038,7 +2410,7 @@ export class AgentSession {
|
|
|
3038
2410
|
const result = (await this._extensionRunner.emit({
|
|
3039
2411
|
type: "session_before_tree",
|
|
3040
2412
|
preparation,
|
|
3041
|
-
signal: this.
|
|
2413
|
+
signal: this._compactionOrchestrator.branchSummaryAbortController.signal,
|
|
3042
2414
|
})) as SessionBeforeTreeResult | undefined;
|
|
3043
2415
|
|
|
3044
2416
|
if (result?.cancel) {
|
|
@@ -3075,12 +2447,12 @@ export class AgentSession {
|
|
|
3075
2447
|
const result = await generateBranchSummary(entriesToSummarize, {
|
|
3076
2448
|
model,
|
|
3077
2449
|
apiKey,
|
|
3078
|
-
signal: this.
|
|
2450
|
+
signal: this._compactionOrchestrator.branchSummaryAbortController.signal,
|
|
3079
2451
|
customInstructions,
|
|
3080
2452
|
replaceInstructions,
|
|
3081
2453
|
reserveTokens: branchSummarySettings.reserveTokens,
|
|
3082
2454
|
});
|
|
3083
|
-
this.
|
|
2455
|
+
this._compactionOrchestrator.branchSummaryAbortController = undefined;
|
|
3084
2456
|
if (result.aborted) {
|
|
3085
2457
|
return { cancelled: true, aborted: true };
|
|
3086
2458
|
}
|
|
@@ -3162,7 +2534,7 @@ export class AgentSession {
|
|
|
3162
2534
|
|
|
3163
2535
|
// Emit to custom tools
|
|
3164
2536
|
|
|
3165
|
-
this.
|
|
2537
|
+
this._compactionOrchestrator.branchSummaryAbortController = undefined;
|
|
3166
2538
|
return { editorText, cancelled: false, summaryEntry };
|
|
3167
2539
|
}
|
|
3168
2540
|
|
|
@@ -3230,17 +2602,17 @@ export class AgentSession {
|
|
|
3230
2602
|
sessionId: this.sessionId,
|
|
3231
2603
|
userMessages,
|
|
3232
2604
|
assistantMessages,
|
|
3233
|
-
toolCalls,
|
|
2605
|
+
toolCalls: Math.max(toolCalls, this._cumulativeToolCalls),
|
|
3234
2606
|
toolResults,
|
|
3235
2607
|
totalMessages: state.messages.length,
|
|
3236
2608
|
tokens: {
|
|
3237
|
-
input: totalInput,
|
|
3238
|
-
output: totalOutput,
|
|
2609
|
+
input: Math.max(totalInput, this._cumulativeInputTokens),
|
|
2610
|
+
output: Math.max(totalOutput, this._cumulativeOutputTokens),
|
|
3239
2611
|
cacheRead: totalCacheRead,
|
|
3240
2612
|
cacheWrite: totalCacheWrite,
|
|
3241
|
-
total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
|
|
2613
|
+
total: Math.max(totalInput + totalOutput, this._cumulativeInputTokens + this._cumulativeOutputTokens) + totalCacheRead + totalCacheWrite,
|
|
3242
2614
|
},
|
|
3243
|
-
cost: totalCost,
|
|
2615
|
+
cost: Math.max(totalCost, this._cumulativeCost),
|
|
3244
2616
|
};
|
|
3245
2617
|
}
|
|
3246
2618
|
|