@prestyj/cli 5.7.0 → 5.9.0
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/README.md +2 -2
- package/assets/skills/evidence-led-ui/LICENSES.md +12 -0
- package/assets/skills/evidence-led-ui/SKILL.md +14 -6
- package/assets/skills/evidence-led-ui/references/anti-defaults.md +9 -1
- package/assets/skills/evidence-led-ui/references/craft-rulings.md +33 -0
- package/assets/skills/evidence-led-ui/references/production-contract.md +41 -21
- package/assets/skills/evidence-led-ui/references/provenance.md +7 -5
- package/assets/skills/evidence-led-ui/references/quality-rubric.md +10 -10
- package/dist/app-sidecar-sessions.d.ts +8 -0
- package/dist/app-sidecar-sessions.d.ts.map +1 -0
- package/dist/app-sidecar-sessions.js +31 -0
- package/dist/app-sidecar-sessions.js.map +1 -0
- package/dist/app-sidecar-sessions.test.d.ts +2 -0
- package/dist/app-sidecar-sessions.test.d.ts.map +1 -0
- package/dist/app-sidecar-sessions.test.js +83 -0
- package/dist/app-sidecar-sessions.test.js.map +1 -0
- package/dist/app-sidecar.js +488 -156
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +5 -2
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/shared.d.ts.map +1 -1
- package/dist/cli/shared.js +2 -0
- package/dist/cli/shared.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +61 -29
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +7 -0
- package/dist/config.test.js.map +1 -1
- package/dist/core/agent-session-compaction.test.js +540 -10
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session-memory-tail.test.js +1 -1
- package/dist/core/agent-session-memory-tail.test.js.map +1 -1
- package/dist/core/agent-session-queue.test.js +1 -1
- package/dist/core/agent-session-queue.test.js.map +1 -1
- package/dist/core/agent-session-review-coverage.test.js +26 -0
- package/dist/core/agent-session-review-coverage.test.js.map +1 -1
- package/dist/core/agent-session-tool-result-policy.test.d.ts +2 -0
- package/dist/core/agent-session-tool-result-policy.test.d.ts.map +1 -0
- package/dist/core/agent-session-tool-result-policy.test.js +25 -0
- package/dist/core/agent-session-tool-result-policy.test.js.map +1 -0
- package/dist/core/agent-session.d.ts +44 -8
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +369 -99
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agents.d.ts +1 -1
- package/dist/core/agents.d.ts.map +1 -1
- package/dist/core/agents.js +4 -1
- package/dist/core/agents.js.map +1 -1
- package/dist/core/auth-providers.d.ts.map +1 -1
- package/dist/core/auth-providers.js +15 -8
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/auth-storage.d.ts +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +1 -1
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/active-context.d.ts +17 -0
- package/dist/core/compaction/active-context.d.ts.map +1 -0
- package/dist/core/compaction/active-context.js +20 -0
- package/dist/core/compaction/active-context.js.map +1 -0
- package/dist/core/compaction/active-context.test.d.ts +2 -0
- package/dist/core/compaction/active-context.test.d.ts.map +1 -0
- package/dist/core/compaction/active-context.test.js +36 -0
- package/dist/core/compaction/active-context.test.js.map +1 -0
- package/dist/core/compaction/compactor.d.ts +19 -18
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +84 -53
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +135 -65
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.d.ts +25 -1
- package/dist/core/compaction/token-estimator.d.ts.map +1 -1
- package/dist/core/compaction/token-estimator.js +86 -1
- package/dist/core/compaction/token-estimator.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +147 -1
- package/dist/core/compaction/token-estimator.test.js.map +1 -1
- package/dist/core/compaction/tool-result-pruner.d.ts +37 -0
- package/dist/core/compaction/tool-result-pruner.d.ts.map +1 -0
- package/dist/core/compaction/tool-result-pruner.js +103 -0
- package/dist/core/compaction/tool-result-pruner.js.map +1 -0
- package/dist/core/compaction/tool-result-pruner.test.d.ts +2 -0
- package/dist/core/compaction/tool-result-pruner.test.d.ts.map +1 -0
- package/dist/core/compaction/tool-result-pruner.test.js +148 -0
- package/dist/core/compaction/tool-result-pruner.test.js.map +1 -0
- package/dist/core/event-bus.d.ts +5 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +6 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/goal-controller.test.js +3 -3
- package/dist/core/goal-controller.test.js.map +1 -1
- package/dist/core/ideal-review.d.ts +6 -0
- package/dist/core/ideal-review.d.ts.map +1 -1
- package/dist/core/ideal-review.js +14 -0
- package/dist/core/ideal-review.js.map +1 -1
- package/dist/core/ideal-review.test.js +10 -1
- package/dist/core/ideal-review.test.js.map +1 -1
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/input-normalization.test.d.ts +2 -0
- package/dist/core/input-normalization.test.d.ts.map +1 -0
- package/dist/core/input-normalization.test.js +75 -0
- package/dist/core/input-normalization.test.js.map +1 -0
- package/dist/core/loop-breaker.d.ts +38 -1
- package/dist/core/loop-breaker.d.ts.map +1 -1
- package/dist/core/loop-breaker.js +84 -2
- package/dist/core/loop-breaker.js.map +1 -1
- package/dist/core/loop-breaker.test.js +108 -1
- package/dist/core/loop-breaker.test.js.map +1 -1
- package/dist/core/message-images.d.ts +12 -0
- package/dist/core/message-images.d.ts.map +1 -0
- package/dist/core/message-images.js +52 -0
- package/dist/core/message-images.js.map +1 -0
- package/dist/core/message-images.test.d.ts +2 -0
- package/dist/core/message-images.test.d.ts.map +1 -0
- package/dist/core/message-images.test.js +62 -0
- package/dist/core/message-images.test.js.map +1 -0
- package/dist/core/project-discovery.d.ts +7 -10
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +125 -66
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.js +177 -0
- package/dist/core/project-discovery.test.js.map +1 -1
- package/dist/core/resolve-start.test.js +1 -0
- package/dist/core/resolve-start.test.js.map +1 -1
- package/dist/core/session-compaction.d.ts +3 -0
- package/dist/core/session-compaction.d.ts.map +1 -1
- package/dist/core/session-compaction.js +14 -1
- package/dist/core/session-compaction.js.map +1 -1
- package/dist/core/session-compaction.test.js +5 -0
- package/dist/core/session-compaction.test.js.map +1 -1
- package/dist/core/session-manager.d.ts +35 -8
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +264 -125
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +17 -0
- package/dist/core/session-manager.test.js.map +1 -1
- package/dist/core/session-preview.d.ts +11 -0
- package/dist/core/session-preview.d.ts.map +1 -0
- package/dist/core/session-preview.js +50 -0
- package/dist/core/session-preview.js.map +1 -0
- package/dist/core/session-storage.d.ts +57 -0
- package/dist/core/session-storage.d.ts.map +1 -0
- package/dist/core/session-storage.js +568 -0
- package/dist/core/session-storage.js.map +1 -0
- package/dist/core/session-storage.test.d.ts +2 -0
- package/dist/core/session-storage.test.d.ts.map +1 -0
- package/dist/core/session-storage.test.js +269 -0
- package/dist/core/session-storage.test.js.map +1 -0
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +10 -2
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/sidecar-error-reporter.d.ts +12 -0
- package/dist/core/sidecar-error-reporter.d.ts.map +1 -0
- package/dist/core/sidecar-error-reporter.js +57 -0
- package/dist/core/sidecar-error-reporter.js.map +1 -0
- package/dist/core/sidecar-error-reporter.test.d.ts +2 -0
- package/dist/core/sidecar-error-reporter.test.d.ts.map +1 -0
- package/dist/core/sidecar-error-reporter.test.js +61 -0
- package/dist/core/sidecar-error-reporter.test.js.map +1 -0
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +4 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +9 -4
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +30 -2
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-manager.test.js +47 -3
- package/dist/core/subagent-manager.test.js.map +1 -1
- package/dist/core/verify-commands.d.ts.map +1 -1
- package/dist/core/verify-commands.js +1 -6
- package/dist/core/verify-commands.js.map +1 -1
- package/dist/core/verify-commands.test.js +1 -5
- package/dist/core/verify-commands.test.js.map +1 -1
- package/dist/core/workspace-guard.d.ts +38 -0
- package/dist/core/workspace-guard.d.ts.map +1 -0
- package/dist/core/workspace-guard.js +112 -0
- package/dist/core/workspace-guard.js.map +1 -0
- package/dist/core/workspace-guard.test.d.ts +2 -0
- package/dist/core/workspace-guard.test.d.ts.map +1 -0
- package/dist/core/workspace-guard.test.js +86 -0
- package/dist/core/workspace-guard.test.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +9 -0
- package/dist/modes/json-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
- package/dist/modes/subagent-worker-mode.js +11 -0
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/session.d.ts +4 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +38 -101
- package/dist/session.js.map +1 -1
- package/dist/session.test.d.ts +2 -0
- package/dist/session.test.d.ts.map +1 -0
- package/dist/session.test.js +35 -0
- package/dist/session.test.js.map +1 -0
- package/dist/system-prompt.d.ts +12 -0
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +86 -19
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +132 -8
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/bash.d.ts +12 -2
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +65 -24
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/bash.test.d.ts +2 -0
- package/dist/tools/bash.test.d.ts.map +1 -0
- package/dist/tools/bash.test.js +89 -0
- package/dist/tools/bash.test.js.map +1 -0
- package/dist/tools/edit.d.ts +2 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +7 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/edit.test.js +7 -0
- package/dist/tools/edit.test.js.map +1 -1
- package/dist/tools/exit-plan.d.ts.map +1 -1
- package/dist/tools/exit-plan.js +15 -4
- package/dist/tools/exit-plan.js.map +1 -1
- package/dist/tools/exit-plan.test.d.ts +2 -0
- package/dist/tools/exit-plan.test.d.ts.map +1 -0
- package/dist/tools/exit-plan.test.js +72 -0
- package/dist/tools/exit-plan.test.js.map +1 -0
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +8 -3
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/grep.test.js +6 -0
- package/dist/tools/grep.test.js.map +1 -1
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/overflow.d.ts +12 -2
- package/dist/tools/overflow.d.ts.map +1 -1
- package/dist/tools/overflow.js +49 -4
- package/dist/tools/overflow.js.map +1 -1
- package/dist/tools/overflow.test.d.ts +2 -0
- package/dist/tools/overflow.test.d.ts.map +1 -0
- package/dist/tools/overflow.test.js +62 -0
- package/dist/tools/overflow.test.js.map +1 -0
- package/dist/tools/screenshot.js +1 -1
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/skill.d.ts.map +1 -1
- package/dist/tools/skill.js +4 -2
- package/dist/tools/skill.js.map +1 -1
- package/dist/tools/source-path.d.ts.map +1 -1
- package/dist/tools/source-path.js +9 -0
- package/dist/tools/source-path.js.map +1 -1
- package/dist/tools/subagent-shared.d.ts +9 -1
- package/dist/tools/subagent-shared.d.ts.map +1 -1
- package/dist/tools/subagent-shared.js +4 -1
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/subagent-shared.test.js +21 -1
- package/dist/tools/subagent-shared.test.js.map +1 -1
- package/dist/tools/subagent.d.ts +3 -8
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +10 -8
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/subagent.test.js +17 -1
- package/dist/tools/subagent.test.js.map +1 -1
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +13 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/tools/write.d.ts +2 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +7 -1
- package/dist/tools/write.js.map +1 -1
- package/dist/tools/write.test.js +12 -0
- package/dist/tools/write.test.js.map +1 -1
- package/dist/ui/App.d.ts +0 -2
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +23 -87
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +10 -0
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/app-items.js +10 -0
- package/dist/ui/app-items.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +1 -0
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/Footer.test.js +1 -1
- package/dist/ui/components/ModelSelector.d.ts.map +1 -1
- package/dist/ui/components/ModelSelector.js +1 -0
- package/dist/ui/components/ModelSelector.js.map +1 -1
- package/dist/ui/components/SubAgentPanel.d.ts +2 -0
- package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
- package/dist/ui/components/SubAgentPanel.js +4 -2
- package/dist/ui/components/SubAgentPanel.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +7 -6
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +56 -11
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.test.js +31 -0
- package/dist/ui/hooks/useAgentLoop.test.js.map +1 -1
- package/dist/ui/hooks/useContextCompaction.d.ts +5 -8
- package/dist/ui/hooks/useContextCompaction.d.ts.map +1 -1
- package/dist/ui/hooks/useContextCompaction.js +77 -16
- package/dist/ui/hooks/useContextCompaction.js.map +1 -1
- package/dist/ui/hooks/useContextCompaction.test.d.ts +2 -0
- package/dist/ui/hooks/useContextCompaction.test.d.ts.map +1 -0
- package/dist/ui/hooks/useContextCompaction.test.js +135 -0
- package/dist/ui/hooks/useContextCompaction.test.js.map +1 -0
- package/dist/ui/hooks/useSessionPersistence.d.ts.map +1 -1
- package/dist/ui/hooks/useSessionPersistence.js +3 -0
- package/dist/ui/hooks/useSessionPersistence.js.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.d.ts +3 -3
- package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.js +5 -9
- package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
- package/dist/ui/hooks/useTranscriptHistory.d.ts +8 -5
- package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -1
- package/dist/ui/hooks/useTranscriptHistory.js +2 -30
- package/dist/ui/hooks/useTranscriptHistory.js.map +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +7 -2
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/render.d.ts +0 -2
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +0 -4
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/render.test.js +10 -1
- package/dist/ui/render.test.js.map +1 -1
- package/dist/ui/terminal-history.js +6 -2
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/terminal-history.test.js +2 -2
- package/dist/ui/terminal-history.test.js.map +1 -1
- package/dist/ui/tui-history-parity.test.js +8 -1
- package/dist/ui/tui-history-parity.test.js.map +1 -1
- package/dist/utils/git.d.ts +2 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +12 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/git.test.d.ts +2 -0
- package/dist/utils/git.test.d.ts.map +1 -0
- package/dist/utils/git.test.js +36 -0
- package/dist/utils/git.test.js.map +1 -0
- package/dist/utils/http-body.d.ts +19 -0
- package/dist/utils/http-body.d.ts.map +1 -0
- package/dist/utils/http-body.js +58 -0
- package/dist/utils/http-body.js.map +1 -0
- package/dist/utils/http-body.test.d.ts +2 -0
- package/dist/utils/http-body.test.d.ts.map +1 -0
- package/dist/utils/http-body.test.js +68 -0
- package/dist/utils/http-body.test.js.map +1 -0
- package/dist/utils/text.d.ts +9 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +11 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +5 -5
- package/dist/utils/session-title.d.ts +0 -19
- package/dist/utils/session-title.d.ts.map +0 -1
- package/dist/utils/session-title.js +0 -82
- package/dist/utils/session-title.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { agentLoop, isAbortError, } from "@prestyj/agent";
|
|
1
|
+
import { agentLoop, isAbortError, isUsageLimitError, } from "@prestyj/agent";
|
|
2
2
|
import { ProviderError, } from "@prestyj/ai";
|
|
3
3
|
import { EventBus } from "./event-bus.js";
|
|
4
4
|
import { SlashCommandRegistry, createBuiltinCommands, } from "./slash-commands.js";
|
|
@@ -6,12 +6,13 @@ import { PROMPT_COMMANDS, getPromptCommand } from "./prompt-commands.js";
|
|
|
6
6
|
import { loadCustomCommands } from "./custom-commands.js";
|
|
7
7
|
import { SettingsManager } from "./settings-manager.js";
|
|
8
8
|
import { AuthStorage } from "./auth-storage.js";
|
|
9
|
+
import { MOONSHOT_OAUTH_KEY } from "@prestyj/core";
|
|
9
10
|
import { getClaudeCliUserAgent } from "./claude-code-version.js";
|
|
10
11
|
import { kimiCodingHeaders, isKimiCodingEndpoint } from "./oauth/kimi.js";
|
|
11
12
|
import { SessionManager, NOLAN_TURN_CUSTOM_KIND, AUTOPILOT_MARKER_CUSTOM_KIND, APP_MARKER_CUSTOM_KIND, } from "./session-manager.js";
|
|
12
13
|
import { ExtensionLoader } from "./extensions/loader.js";
|
|
13
|
-
import { shouldCompact, compact
|
|
14
|
-
import { getAuthStorageKeys, getContextWindow, getModel, MODELS } from "./model-registry.js";
|
|
14
|
+
import { shouldCompact, compact } from "./compaction/compactor.js";
|
|
15
|
+
import { getAuthStorageKeys, getContextWindow, getModel, getToolResultCharLimit, MODELS, } from "./model-registry.js";
|
|
15
16
|
import { discoverSkills } from "./skills.js";
|
|
16
17
|
import { ensureAppDirs } from "../config.js";
|
|
17
18
|
import { buildSystemPrompt } from "../system-prompt.js";
|
|
@@ -22,18 +23,39 @@ import { MCPClientManager, getAllMcpServers } from "./mcp/index.js";
|
|
|
22
23
|
import { DeferredToolCatalog } from "./mcp/deferred-catalog.js";
|
|
23
24
|
import { createToolSearchTool } from "../tools/tool-search.js";
|
|
24
25
|
import { log } from "./logger.js";
|
|
25
|
-
import { setEstimatorModel } from "./compaction/token-estimator.js";
|
|
26
|
+
import { setEstimatorModel, calibrateEstimatorFromUsage } from "./compaction/token-estimator.js";
|
|
27
|
+
import { calculateActiveContextTokens } from "./compaction/active-context.js";
|
|
28
|
+
import { pruneStaleToolResults } from "./compaction/tool-result-pruner.js";
|
|
26
29
|
import { discoverAgents } from "./agents.js";
|
|
27
|
-
import { generateSessionTitle } from "../utils/session-title.js";
|
|
28
30
|
import { enhancePrompt } from "../utils/prompt-enhancer.js";
|
|
29
31
|
import { detectProjectStack } from "./language-detector.js";
|
|
30
|
-
import { evaluateIdealReview, buildIdealReviewMessage, buildReviewCoverageMessage, detectTestDrift, ReviewCoverageTracker, } from "./ideal-review.js";
|
|
31
|
-
import { evaluateLoopBreak, buildLoopBreakMessage, ToolCallProgressTracker, detectTextRepetition, } from "./loop-breaker.js";
|
|
32
|
+
import { evaluateIdealReview, buildIdealReviewMessage, buildReviewCoverageMessage, withReviewCoverageRequirements, detectTestDrift, ReviewCoverageTracker, } from "./ideal-review.js";
|
|
33
|
+
import { evaluateLoopBreak, buildLoopBreakMessage, CycleDetector, ToolCallProgressTracker, detectTextRepetition, } from "./loop-breaker.js";
|
|
32
34
|
import { buildRegroundingMessage } from "./regrounding.js";
|
|
33
35
|
import { wrapSteeringText, STEERING_PREFIX } from "./steering.js";
|
|
36
|
+
import { findUserSessionPrompt, getUserSessionPrompt } from "./session-preview.js";
|
|
37
|
+
import { normalizeMessageImages } from "./message-images.js";
|
|
34
38
|
import crypto from "node:crypto";
|
|
35
39
|
import fs from "node:fs/promises";
|
|
36
40
|
import path from "node:path";
|
|
41
|
+
// ── Tool-result policy ─────────────────────────────────────
|
|
42
|
+
/** Resolve the per-result cap passed to the agent loop for the active transport. */
|
|
43
|
+
export function resolveSessionToolResultCharLimit(model, provider, accountId) {
|
|
44
|
+
return (getToolResultCharLimit(model, { provider, accountId }) ??
|
|
45
|
+
Math.floor(getContextWindow(model, { provider, accountId }) * 3.5 * 0.3));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Aggregate budget for ALL tool results produced in one assistant turn.
|
|
49
|
+
* Individual results are already capped, but wide parallel fan-outs (GPT-5.6's
|
|
50
|
+
* signature behavior) were observed injecting 100k+ uncached tokens in a single
|
|
51
|
+
* turn. ~15% of the context window in chars (1 token ≈ 3.5 chars), floored at
|
|
52
|
+
* 100KB so small windows still fit two full-size reads, ceilinged at 240KB so
|
|
53
|
+
* 1M-context models don't waive the budget entirely.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveSessionTurnToolResultCharLimit(model, provider, accountId) {
|
|
56
|
+
const contextChars = getContextWindow(model, { provider, accountId }) * 3.5;
|
|
57
|
+
return Math.max(100_000, Math.min(Math.floor(contextChars * 0.15), 240_000));
|
|
58
|
+
}
|
|
37
59
|
// ── Agent Session ──────────────────────────────────────────
|
|
38
60
|
export class AgentSession {
|
|
39
61
|
eventBus = new EventBus();
|
|
@@ -84,13 +106,22 @@ export class AgentSession {
|
|
|
84
106
|
hookConsecutiveFailures = 0;
|
|
85
107
|
hookRepeatedNoProgressCalls = 0;
|
|
86
108
|
hookProgressTracker = new ToolCallProgressTracker();
|
|
109
|
+
hookCycleDetector = new CycleDetector();
|
|
110
|
+
hookCyclicPattern = null;
|
|
87
111
|
hookFileEditCounts = new Map();
|
|
88
112
|
hookToolCalls = new Map();
|
|
89
113
|
idealReviewPhase = "idle";
|
|
114
|
+
/** Runtime-only suppression while Nolan owns verification in autopilot mode. */
|
|
115
|
+
idealReviewSuppressed = false;
|
|
90
116
|
reviewCoverage;
|
|
91
|
-
|
|
117
|
+
/** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */
|
|
118
|
+
loopBreakInjected = 0;
|
|
92
119
|
regroundingInjected = false;
|
|
93
120
|
compactionOccurred = false;
|
|
121
|
+
lastCompactionCompacted = false;
|
|
122
|
+
compactionRetryAfter = 0;
|
|
123
|
+
/** Latest provider count, anchored to the assistant response it measured. */
|
|
124
|
+
providerContext = null;
|
|
94
125
|
originalRequest = "";
|
|
95
126
|
// Messages queued by the user while a run is in flight. Drained at the
|
|
96
127
|
// mid-loop steering boundary (user steering wins over the hooks), mirroring
|
|
@@ -128,6 +159,10 @@ export class AgentSession {
|
|
|
128
159
|
* model emits step-completion markers the UI's plan-progress widget reads. */
|
|
129
160
|
approvedPlanPath;
|
|
130
161
|
sessionId = "";
|
|
162
|
+
/** Stable identity shared by compaction and approved-plan checkpoint files. */
|
|
163
|
+
conversationId = "";
|
|
164
|
+
/** Original user-authored prompt, retained when internal messages replace history. */
|
|
165
|
+
sessionPreview = "";
|
|
131
166
|
/** Runtime conversation identity for provider transport headers. Transient
|
|
132
167
|
* children need one even though they intentionally have no persisted session. */
|
|
133
168
|
transportSessionId = crypto.randomUUID();
|
|
@@ -206,6 +241,9 @@ export class AgentSession {
|
|
|
206
241
|
provider: this.provider,
|
|
207
242
|
model: this.model,
|
|
208
243
|
lspDiagnostics: this.settingsManager.get("lspDiagnostics"),
|
|
244
|
+
getWriteGuardSettings: () => ({
|
|
245
|
+
allowOutsideWorkspaceWrites: this.settingsManager.get("allowOutsideWorkspaceWrites"),
|
|
246
|
+
}),
|
|
209
247
|
authStorage: this.authStorage,
|
|
210
248
|
onFileRead: (filePath) => this.reviewCoverage.recordRead(filePath),
|
|
211
249
|
onFileMutated: (filePath) => {
|
|
@@ -220,6 +258,7 @@ export class AgentSession {
|
|
|
220
258
|
getThinkingLevel: () => this.thinkingLevel,
|
|
221
259
|
getBaseUrl: () => this.baseUrl,
|
|
222
260
|
getCacheKey: () => this.getPromptCacheKey(),
|
|
261
|
+
getMaxPerModel: () => this.settingsManager.get("subagentMaxPerModel"),
|
|
223
262
|
disableAsyncSubagents: this.opts.subagentWorker,
|
|
224
263
|
onSubAgentState: (snapshot) => this.eventBus.emit("subagent_state", snapshot),
|
|
225
264
|
// Plan mode: only wired when the host supplies callbacks. The ref is
|
|
@@ -294,6 +333,32 @@ export class AgentSession {
|
|
|
294
333
|
}
|
|
295
334
|
if (this.sessionId)
|
|
296
335
|
await this.subAgentManager?.hydrate(this.sessionId);
|
|
336
|
+
// Maintenance is deliberately queued after initialization work and never
|
|
337
|
+
// awaited, so retention/compression cannot delay sidecar or CLI readiness.
|
|
338
|
+
if (!this.opts.transient) {
|
|
339
|
+
const retentionDays = this.settingsManager.get("sessionRetentionDays");
|
|
340
|
+
void Promise.resolve()
|
|
341
|
+
.then(() => this.sessionManager.runMaintenance({
|
|
342
|
+
retentionDays,
|
|
343
|
+
keepPaths: this.sessionPath ? [this.sessionPath] : [],
|
|
344
|
+
}))
|
|
345
|
+
.then((metrics) => {
|
|
346
|
+
if (metrics.deletedFiles > 0 || metrics.archivedFiles > 0 || metrics.failures > 0) {
|
|
347
|
+
log("INFO", "session", "Session maintenance complete", {
|
|
348
|
+
deletedFiles: String(metrics.deletedFiles),
|
|
349
|
+
deletedMB: (metrics.deletedBytes / 1024 / 1024).toFixed(1),
|
|
350
|
+
archivedFiles: String(metrics.archivedFiles),
|
|
351
|
+
savedMB: (metrics.bytesSaved / 1024 / 1024).toFixed(1),
|
|
352
|
+
failures: String(metrics.failures),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
.catch((error) => {
|
|
357
|
+
log("WARN", "session", "Session maintenance failed", {
|
|
358
|
+
message: error instanceof Error ? error.message : String(error),
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
297
362
|
// EZ Coder owns its command registry. Other agents start with an isolated
|
|
298
363
|
// empty registry and can register their own commands in their own file.
|
|
299
364
|
if (this.opts.coderSlashCommands !== false) {
|
|
@@ -586,11 +651,13 @@ export class AgentSession {
|
|
|
586
651
|
this.hookConsecutiveFailures = 0;
|
|
587
652
|
this.hookRepeatedNoProgressCalls = 0;
|
|
588
653
|
this.hookProgressTracker.reset();
|
|
654
|
+
this.hookCycleDetector.reset();
|
|
655
|
+
this.hookCyclicPattern = null;
|
|
589
656
|
this.hookFileEditCounts.clear();
|
|
590
657
|
this.hookToolCalls.clear();
|
|
591
658
|
this.reviewCoverage.reset();
|
|
592
659
|
this.idealReviewPhase = "idle";
|
|
593
|
-
this.loopBreakInjected =
|
|
660
|
+
this.loopBreakInjected = 0;
|
|
594
661
|
this.regroundingInjected = false;
|
|
595
662
|
this.compactionOccurred = false;
|
|
596
663
|
this.originalRequest = originalRequest;
|
|
@@ -623,6 +690,7 @@ export class AgentSession {
|
|
|
623
690
|
this.hookStats.bashCalls += 1;
|
|
624
691
|
this.hookConsecutiveFailures = event.isError ? this.hookConsecutiveFailures + 1 : 0;
|
|
625
692
|
this.hookRepeatedNoProgressCalls = this.hookProgressTracker.record(name, args, event.result, event.isError);
|
|
693
|
+
this.hookCyclicPattern = this.hookCycleDetector.record(name, args, event.result, event.isError);
|
|
626
694
|
if (name === "edit" && !event.isError) {
|
|
627
695
|
const diff = event.details?.diff ?? event.result;
|
|
628
696
|
const added = (diff.match(/^\+[^+]/gm) ?? []).length;
|
|
@@ -633,6 +701,13 @@ export class AgentSession {
|
|
|
633
701
|
}
|
|
634
702
|
case "turn_end":
|
|
635
703
|
this.hookStats.turns = event.turn;
|
|
704
|
+
for (let index = this.messages.length - 1; index >= 0; index--) {
|
|
705
|
+
const anchor = this.messages[index];
|
|
706
|
+
if (anchor?.role === "assistant") {
|
|
707
|
+
this.providerContext = { usage: { ...event.usage }, anchor };
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
636
711
|
await this.persistTurnMetric(event);
|
|
637
712
|
break;
|
|
638
713
|
}
|
|
@@ -670,19 +745,34 @@ export class AgentSession {
|
|
|
670
745
|
return null;
|
|
671
746
|
if (!this.settingsManager.get("idealReviewEnabled"))
|
|
672
747
|
return null;
|
|
673
|
-
|
|
748
|
+
// Two-stage loop-breaker: stage 1 nudges; a FRESH detection after that
|
|
749
|
+
// injects the harsher final stop-and-report prompt. Signals reset after
|
|
750
|
+
// each injection so stage 2 only fires on new evidence.
|
|
751
|
+
if (this.loopBreakInjected < 2) {
|
|
674
752
|
const decision = evaluateLoopBreak({
|
|
675
753
|
consecutiveFailures: this.hookConsecutiveFailures,
|
|
676
754
|
repeatedNoProgressCalls: this.hookRepeatedNoProgressCalls,
|
|
677
755
|
textRepetitionDetected: detectTextRepetition(this.hookText),
|
|
756
|
+
...(this.hookCyclicPattern ? { cyclicPattern: this.hookCyclicPattern } : {}),
|
|
678
757
|
});
|
|
679
758
|
if (decision.shouldBreak) {
|
|
680
|
-
this.loopBreakInjected
|
|
759
|
+
const stage = this.loopBreakInjected === 0 ? 1 : 2;
|
|
760
|
+
this.loopBreakInjected = stage;
|
|
761
|
+
this.hookProgressTracker.reset();
|
|
762
|
+
this.hookCycleDetector.reset();
|
|
763
|
+
this.hookCyclicPattern = null;
|
|
764
|
+
this.hookConsecutiveFailures = 0;
|
|
765
|
+
this.hookRepeatedNoProgressCalls = 0;
|
|
766
|
+
// Clear the text buffer too — otherwise a stage-1 text-repetition
|
|
767
|
+
// trigger still sees the same repeated tail on the next check and
|
|
768
|
+
// escalates to stage 2 on stale evidence.
|
|
769
|
+
this.hookText = "";
|
|
681
770
|
log("INFO", "loop-break", "Injecting loop-break nudge", {
|
|
771
|
+
stage: String(stage),
|
|
682
772
|
reasons: decision.reasons.join(", "),
|
|
683
773
|
});
|
|
684
774
|
this.eventBus.emit("hook", { kind: "loop_break" });
|
|
685
|
-
return [buildLoopBreakMessage(decision.reasons)];
|
|
775
|
+
return [buildLoopBreakMessage(decision.reasons, stage === 2)];
|
|
686
776
|
}
|
|
687
777
|
}
|
|
688
778
|
if (!this.regroundingInjected && this.compactionOccurred) {
|
|
@@ -700,7 +790,7 @@ export class AgentSession {
|
|
|
700
790
|
const childCompletionFollowUp = buildSubAgentCompletionFollowUp(this.subAgentManager);
|
|
701
791
|
if (childCompletionFollowUp)
|
|
702
792
|
return childCompletionFollowUp;
|
|
703
|
-
if (this.opts.selfCorrectionHooks === false)
|
|
793
|
+
if (this.opts.selfCorrectionHooks === false || this.idealReviewSuppressed)
|
|
704
794
|
return null;
|
|
705
795
|
if (this.idealReviewPhase === "reviewing") {
|
|
706
796
|
const coverage = this.reviewCoverage.evidence();
|
|
@@ -745,7 +835,7 @@ export class AgentSession {
|
|
|
745
835
|
lspMissing: lspEvidence.missing,
|
|
746
836
|
});
|
|
747
837
|
return [
|
|
748
|
-
this.withReviewLspEvidence(buildIdealReviewMessage(decision.reasons, driftedFiles), lspEvidence),
|
|
838
|
+
this.withReviewLspEvidence(withReviewCoverageRequirements(buildIdealReviewMessage(decision.reasons, driftedFiles), coverage.missing), lspEvidence),
|
|
749
839
|
];
|
|
750
840
|
}
|
|
751
841
|
reviewLspEvidence(files) {
|
|
@@ -805,24 +895,45 @@ export class AgentSession {
|
|
|
805
895
|
this.lastAccountId = creds.accountId;
|
|
806
896
|
// Auto-compact if needed. This must happen after credential resolution so
|
|
807
897
|
// OpenAI OAuth/Codex sessions use the Codex product context window instead
|
|
808
|
-
// of the public API model window.
|
|
809
|
-
|
|
898
|
+
// of the public API model window. Failed/no-op attempts cool down across
|
|
899
|
+
// prompts; provider overflow recovery still bypasses this path entirely.
|
|
900
|
+
if (this.settingsManager.get("autoCompact") && Date.now() >= this.compactionRetryAfter) {
|
|
810
901
|
const contextWindow = getContextWindow(this.model, {
|
|
811
902
|
provider: this.provider,
|
|
812
903
|
accountId: creds.accountId,
|
|
813
904
|
});
|
|
814
905
|
const threshold = this.settingsManager.get("compactThreshold");
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
906
|
+
let activeTokens;
|
|
907
|
+
if (this.providerContext) {
|
|
908
|
+
const anchorIndex = this.messages.lastIndexOf(this.providerContext.anchor);
|
|
909
|
+
if (anchorIndex >= 0) {
|
|
910
|
+
activeTokens = calculateActiveContextTokens(this.messages, {
|
|
911
|
+
usage: this.providerContext.usage,
|
|
912
|
+
pendingMessages: this.messages.slice(anchorIndex + 1),
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
this.providerContext = null;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (shouldCompact(this.messages, contextWindow, threshold, activeTokens)) {
|
|
920
|
+
try {
|
|
921
|
+
await this.compact(creds);
|
|
922
|
+
if (this.lastCompactionCompacted) {
|
|
923
|
+
// Re-grounding hook keys off this — the context was just summarized.
|
|
924
|
+
this.compactionOccurred = true;
|
|
925
|
+
this.compactionRetryAfter = 0;
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
this.compactionRetryAfter = Date.now() + 30_000;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
catch (error) {
|
|
932
|
+
this.compactionRetryAfter = Date.now() + 30_000;
|
|
933
|
+
if (isAbortError(error) || this.opts.signal?.aborted)
|
|
934
|
+
throw error;
|
|
935
|
+
log("WARN", "compaction", `Pre-run compaction failed; cooling down for 30s: ${error instanceof Error ? error.message : String(error)}`);
|
|
936
|
+
}
|
|
826
937
|
}
|
|
827
938
|
}
|
|
828
939
|
const userAgent = this.provider === "anthropic" ? await getClaudeCliUserAgent() : undefined;
|
|
@@ -856,29 +967,99 @@ export class AgentSession {
|
|
|
856
967
|
supportsImages: modelInfo?.supportsImages,
|
|
857
968
|
supportsVideo: modelInfo?.supportsVideo,
|
|
858
969
|
userAgent,
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
maxToolResultChars:
|
|
970
|
+
// Codex caps each tool output at 10K tokens. Other transports retain the
|
|
971
|
+
// generic 30%-of-context allowance used before this provider policy.
|
|
972
|
+
maxToolResultChars: resolveSessionToolResultCharLimit(this.model, this.provider, accountId),
|
|
973
|
+
// Aggregate per-turn budget across parallel tool results (fan-out guard).
|
|
974
|
+
maxTurnToolResultChars: resolveSessionTurnToolResultCharLimit(this.model, this.provider, accountId),
|
|
862
975
|
// Self-correction hooks (same as the TUI): loop-break + re-grounding are
|
|
863
976
|
// polled mid-loop; the ideal review is polled when the agent would stop.
|
|
864
977
|
getSteeringMessages: () => this.getHookSteeringMessages(),
|
|
865
978
|
getFollowUpMessages: () => this.getHookFollowUpMessages(),
|
|
866
|
-
//
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
// loop retries with a smaller request, instead of surfacing the error.
|
|
870
|
-
// Without this the desktop app (which drives the loop through
|
|
871
|
-
// AgentSession, not the TUI's useContextCompaction hook) had NO auto
|
|
872
|
-
// recovery on 413 — the error went straight to the user. The non-force
|
|
873
|
-
// pre-call invocations pass through untouched: pre-turn compaction is
|
|
874
|
-
// already handled above, so we only act on the overflow force path.
|
|
875
|
-
// `loopMessages === this.messages` (prepareDynamicContext returns it by
|
|
876
|
-
// reference) and the post-loop `this.messages = loopMessages` re-sync
|
|
877
|
-
// keeps persistence correct after compact() swaps the array.
|
|
979
|
+
// Check authoritative provider usage before every model/tool step.
|
|
980
|
+
// Forced overflow recovery bypasses settings and cooldown; proactive
|
|
981
|
+
// checks honor both and estimate only messages unseen by the provider.
|
|
878
982
|
transformContext: async (messages, transformOpts) => {
|
|
879
|
-
if (
|
|
983
|
+
if (transformOpts.usage) {
|
|
984
|
+
const anchorIndex = messages.length - transformOpts.pendingMessages.length - 1;
|
|
985
|
+
const anchor = messages[anchorIndex];
|
|
986
|
+
if (anchor?.role === "assistant") {
|
|
987
|
+
this.providerContext = { usage: { ...transformOpts.usage }, anchor };
|
|
988
|
+
// Feed the authoritative usage back into the token estimator so
|
|
989
|
+
// char-based estimates track this session's real tokenizer.
|
|
990
|
+
calibrateEstimatorFromUsage(messages.slice(0, anchorIndex), transformOpts.usage);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const force = transformOpts.force === true;
|
|
994
|
+
if (!force) {
|
|
995
|
+
if (!this.settingsManager.get("autoCompact"))
|
|
996
|
+
return messages;
|
|
997
|
+
// Cheap stale-tool-output pruning before the expensive LLM
|
|
998
|
+
// compaction check. In-place mutation preserves anchors; drop the
|
|
999
|
+
// retained usage afterwards since it counted the pruned content.
|
|
1000
|
+
const pruneResult = pruneStaleToolResults(messages);
|
|
1001
|
+
if (pruneResult.pruned) {
|
|
1002
|
+
this.providerContext = null;
|
|
1003
|
+
log("INFO", "compaction", "Pruned stale tool outputs", {
|
|
1004
|
+
prunedResults: String(pruneResult.prunedResults),
|
|
1005
|
+
freedTokens: String(pruneResult.freedTokens),
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
if (Date.now() < this.compactionRetryAfter)
|
|
1009
|
+
return messages;
|
|
1010
|
+
// The turn's own usage also counted the pruned content — after a
|
|
1011
|
+
// prune, fall back to estimating the (now smaller) history so the
|
|
1012
|
+
// freed tokens actually defer the LLM compaction.
|
|
1013
|
+
let usage = pruneResult.pruned ? undefined : transformOpts.usage;
|
|
1014
|
+
let pendingMessages = transformOpts.pendingMessages;
|
|
1015
|
+
if (!usage && this.providerContext) {
|
|
1016
|
+
const anchorIndex = messages.lastIndexOf(this.providerContext.anchor);
|
|
1017
|
+
if (anchorIndex >= 0) {
|
|
1018
|
+
usage = this.providerContext.usage;
|
|
1019
|
+
pendingMessages = messages.slice(anchorIndex + 1);
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
this.providerContext = null;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
const contextWindow = getContextWindow(this.model, {
|
|
1026
|
+
provider: this.provider,
|
|
1027
|
+
accountId,
|
|
1028
|
+
});
|
|
1029
|
+
const threshold = this.settingsManager.get("compactThreshold");
|
|
1030
|
+
const activeTokens = calculateActiveContextTokens(messages, {
|
|
1031
|
+
usage,
|
|
1032
|
+
pendingMessages,
|
|
1033
|
+
});
|
|
1034
|
+
if (!shouldCompact(messages, contextWindow, threshold, activeTokens))
|
|
1035
|
+
return messages;
|
|
1036
|
+
}
|
|
1037
|
+
// compact() operates on this.messages, while an earlier transform may
|
|
1038
|
+
// have replaced the loop's in-flight array. Rebind before every attempt
|
|
1039
|
+
// so the current tool results are included in the summary.
|
|
1040
|
+
this.messages = messages;
|
|
1041
|
+
try {
|
|
1042
|
+
await this.compact({
|
|
1043
|
+
accessToken: apiKey,
|
|
1044
|
+
accountId,
|
|
1045
|
+
projectId,
|
|
1046
|
+
baseUrl: effectiveBaseUrl,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
this.messages = messages;
|
|
1051
|
+
this.compactionRetryAfter = Date.now() + 30_000;
|
|
1052
|
+
if (force || isAbortError(error) || this.opts.signal?.aborted)
|
|
1053
|
+
throw error;
|
|
1054
|
+
log("WARN", "compaction", `In-flight compaction failed; cooling down for 30s: ${error instanceof Error ? error.message : String(error)}`);
|
|
1055
|
+
return messages;
|
|
1056
|
+
}
|
|
1057
|
+
if (!this.lastCompactionCompacted) {
|
|
1058
|
+
this.messages = messages;
|
|
1059
|
+
this.compactionRetryAfter = Date.now() + 30_000;
|
|
880
1060
|
return messages;
|
|
881
|
-
|
|
1061
|
+
}
|
|
1062
|
+
this.compactionRetryAfter = 0;
|
|
882
1063
|
this.compactionOccurred = true;
|
|
883
1064
|
return this.messages;
|
|
884
1065
|
},
|
|
@@ -888,6 +1069,19 @@ export class AgentSession {
|
|
|
888
1069
|
this.eventBus.forwardAgentEvent(event);
|
|
889
1070
|
}
|
|
890
1071
|
};
|
|
1072
|
+
const clearInvalidStaticApiKey = async (error) => {
|
|
1073
|
+
if (!(error instanceof ProviderError) || error.statusCode !== 401)
|
|
1074
|
+
return false;
|
|
1075
|
+
if (!(await this.authStorage.isStaticApiKey(this.provider)))
|
|
1076
|
+
return false;
|
|
1077
|
+
// Clear whichever key actually resolved (the request may have used a
|
|
1078
|
+
// fallback key, not the model's first preference).
|
|
1079
|
+
const badKey = (await this.authStorage.pickStorageKey(this.currentAuthStorageKeys())) ??
|
|
1080
|
+
this.currentAuthStorageKeys()[0];
|
|
1081
|
+
log("WARN", "auth", `Got 401 for ${this.provider} (${badKey}) — API key is invalid or revoked`);
|
|
1082
|
+
await this.authStorage.clearCredentials(badKey);
|
|
1083
|
+
return true;
|
|
1084
|
+
};
|
|
891
1085
|
try {
|
|
892
1086
|
await runAgentLoop(creds.accessToken, creds.accountId, creds.projectId);
|
|
893
1087
|
}
|
|
@@ -896,21 +1090,48 @@ export class AgentSession {
|
|
|
896
1090
|
if (isAbortError(err) || this.opts.signal?.aborted) {
|
|
897
1091
|
return;
|
|
898
1092
|
}
|
|
899
|
-
|
|
1093
|
+
// Kimi OAuth plan ran out of usage (hard usage-limit stop, or an HTTP 402
|
|
1094
|
+
// billing stop). If the user ALSO configured a Moonshot API key, mark
|
|
1095
|
+
// the OAuth credential usage-exhausted (honoring the provider-stated
|
|
1096
|
+
// reset time when present) and retry this turn on the API key — OAuth
|
|
1097
|
+
// stays the preferred credential and resumes automatically once the mark
|
|
1098
|
+
// lapses. A generic 429 is deliberately excluded: it may be a transient
|
|
1099
|
+
// rate limit and must not silently switch the user to a billed API key.
|
|
1100
|
+
// Guarded on the Kimi managed endpoint actually being in use: if the API
|
|
1101
|
+
// key was already active, the same error means BOTH are out and must surface.
|
|
1102
|
+
if (this.provider === "moonshot" &&
|
|
1103
|
+
!this.baseUrl &&
|
|
1104
|
+
isKimiCodingEndpoint(creds.baseUrl) &&
|
|
1105
|
+
(isUsageLimitError(err) || (err instanceof ProviderError && err.statusCode === 402)) &&
|
|
1106
|
+
(await this.authStorage.hasCredentials("moonshot"))) {
|
|
1107
|
+
const resetsAt = err instanceof ProviderError ? err.resetsAt : undefined;
|
|
1108
|
+
await this.authStorage.markUsageExhausted(MOONSHOT_OAUTH_KEY, resetsAt);
|
|
1109
|
+
log("WARN", "auth", "Kimi OAuth usage limit reached — retrying this turn on the Moonshot API key", { resetsAt: resetsAt !== undefined ? String(resetsAt) : "unknown" });
|
|
1110
|
+
creds = await this.authStorage.resolveCredentials(this.provider, {
|
|
1111
|
+
storageKeys: this.currentAuthStorageKeys(),
|
|
1112
|
+
});
|
|
1113
|
+
this.lastAccountId = creds.accountId;
|
|
1114
|
+
// The runAgentLoop closure re-reads `creds`, so the retry picks up the
|
|
1115
|
+
// API key's baseUrl (api.moonshot.ai) and drops the Kimi coding headers.
|
|
1116
|
+
try {
|
|
1117
|
+
await runAgentLoop(creds.accessToken, creds.accountId, creds.projectId);
|
|
1118
|
+
}
|
|
1119
|
+
catch (fallbackErr) {
|
|
1120
|
+
// The fallback is inside this catch branch, so its errors do not pass
|
|
1121
|
+
// through the outer 401 handler. Clear a rejected API key explicitly
|
|
1122
|
+
// before surfacing the error and prompting the user to log in again.
|
|
1123
|
+
await clearInvalidStaticApiKey(fallbackErr);
|
|
1124
|
+
throw fallbackErr;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
else if (err instanceof ProviderError && err.statusCode === 401) {
|
|
900
1128
|
// Static API-key providers (GLM, Moonshot API key, etc.) have no refresh
|
|
901
1129
|
// mechanism — retrying with the same key is pointless. Clear the
|
|
902
1130
|
// credential and surface the error so the user re-logins. Kimi OAuth
|
|
903
1131
|
// (active for `moonshot` when present) is refreshable, so it falls
|
|
904
1132
|
// through to the force-refresh path below.
|
|
905
|
-
if (await
|
|
906
|
-
// Clear whichever key actually resolved (the request may have used
|
|
907
|
-
// a fallback key, not the model's first preference).
|
|
908
|
-
const badKey = (await this.authStorage.pickStorageKey(this.currentAuthStorageKeys())) ??
|
|
909
|
-
this.currentAuthStorageKeys()[0];
|
|
910
|
-
log("WARN", "auth", `Got 401 for ${this.provider} (${badKey}) — API key is invalid or revoked`);
|
|
911
|
-
await this.authStorage.clearCredentials(badKey);
|
|
1133
|
+
if (await clearInvalidStaticApiKey(err))
|
|
912
1134
|
throw err;
|
|
913
|
-
}
|
|
914
1135
|
log("INFO", "auth", "Got 401, force-refreshing token and retrying");
|
|
915
1136
|
creds = await this.authStorage.resolveCredentials(this.provider, {
|
|
916
1137
|
forceRefresh: true,
|
|
@@ -935,6 +1156,7 @@ export class AgentSession {
|
|
|
935
1156
|
if (provider)
|
|
936
1157
|
this.provider = provider;
|
|
937
1158
|
this.model = model;
|
|
1159
|
+
this.providerContext = null;
|
|
938
1160
|
// Keep host-provided option closures (notably chat delegation) aligned with
|
|
939
1161
|
// the live selection after an in-session model switch.
|
|
940
1162
|
this.opts.provider = this.provider;
|
|
@@ -1012,6 +1234,7 @@ export class AgentSession {
|
|
|
1012
1234
|
}
|
|
1013
1235
|
}
|
|
1014
1236
|
async compact(existingCredentials) {
|
|
1237
|
+
this.lastCompactionCompacted = false;
|
|
1015
1238
|
const creds = existingCredentials ??
|
|
1016
1239
|
(await this.authStorage.resolveCredentials(this.provider, {
|
|
1017
1240
|
storageKeys: this.currentAuthStorageKeys(),
|
|
@@ -1032,6 +1255,16 @@ export class AgentSession {
|
|
|
1032
1255
|
signal: this.opts.signal,
|
|
1033
1256
|
});
|
|
1034
1257
|
this.messages = result.messages;
|
|
1258
|
+
this.lastCompactionCompacted = result.result.compacted;
|
|
1259
|
+
if (!result.result.compacted) {
|
|
1260
|
+
this.eventBus.emit("compaction_end", {
|
|
1261
|
+
compacted: false,
|
|
1262
|
+
originalCount: result.result.originalCount,
|
|
1263
|
+
newCount: result.result.newCount,
|
|
1264
|
+
});
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
this.providerContext = null;
|
|
1035
1268
|
// Transient sessions (Nolan chat/autopilot, subagent spawns) must NEVER touch
|
|
1036
1269
|
// the session store: without this guard, the first auto-compaction called
|
|
1037
1270
|
// sessionManager.create() and assigned a real sessionPath, silently turning
|
|
@@ -1044,9 +1277,13 @@ export class AgentSession {
|
|
|
1044
1277
|
else {
|
|
1045
1278
|
// Persist compacted messages to a new session file so `ezcoder continue`
|
|
1046
1279
|
// picks up the compacted state instead of the full original history.
|
|
1047
|
-
const session = await this.sessionManager.create(this.cwd, this.provider, this.model
|
|
1280
|
+
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
1281
|
+
conversationId: this.conversationId || undefined,
|
|
1282
|
+
preview: this.sessionPreview || undefined,
|
|
1283
|
+
});
|
|
1048
1284
|
this.sessionId = session.id;
|
|
1049
|
-
this.
|
|
1285
|
+
this.conversationId = session.header.conversationId ?? session.id;
|
|
1286
|
+
this.setSessionPath(session.path);
|
|
1050
1287
|
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
1051
1288
|
// Write compacted messages (skip system — it's rebuilt on load)
|
|
1052
1289
|
for (const msg of this.messages) {
|
|
@@ -1068,11 +1305,18 @@ export class AgentSession {
|
|
|
1068
1305
|
});
|
|
1069
1306
|
}
|
|
1070
1307
|
this.eventBus.emit("compaction_end", {
|
|
1308
|
+
compacted: true,
|
|
1071
1309
|
originalCount: result.result.originalCount,
|
|
1072
1310
|
newCount: result.result.newCount,
|
|
1073
1311
|
});
|
|
1074
1312
|
}
|
|
1075
|
-
async newSession() {
|
|
1313
|
+
async newSession(preserveConversation = false) {
|
|
1314
|
+
// Approved-plan execution is a clean checkpoint of the same conversation;
|
|
1315
|
+
// explicit new sessions reset the conversation identity.
|
|
1316
|
+
if (!preserveConversation) {
|
|
1317
|
+
this.conversationId = "";
|
|
1318
|
+
this.sessionPreview = "";
|
|
1319
|
+
}
|
|
1076
1320
|
// A fresh session drops any in-flight plan state so its prompt is clean.
|
|
1077
1321
|
this.planModeRef.current = false;
|
|
1078
1322
|
this.approvedPlanPath = undefined;
|
|
@@ -1098,7 +1342,9 @@ export class AgentSession {
|
|
|
1098
1342
|
// polluted the project's session list.
|
|
1099
1343
|
if (this.opts.transient) {
|
|
1100
1344
|
this.sessionId = "";
|
|
1101
|
-
this.
|
|
1345
|
+
this.conversationId = "";
|
|
1346
|
+
this.sessionPreview = "";
|
|
1347
|
+
this.setSessionPath("");
|
|
1102
1348
|
this.lastPersistedIndex = this.messages.length;
|
|
1103
1349
|
}
|
|
1104
1350
|
else {
|
|
@@ -1123,6 +1369,7 @@ export class AgentSession {
|
|
|
1123
1369
|
async branch(stepsBack = 2) {
|
|
1124
1370
|
// Load the full session to access the DAG
|
|
1125
1371
|
const loaded = await this.sessionManager.load(this.sessionPath);
|
|
1372
|
+
this.setSessionPath(loaded.path);
|
|
1126
1373
|
const branch = this.sessionManager.getBranch(loaded.entries, this.currentLeafId);
|
|
1127
1374
|
// Walk back stepsBack message entries
|
|
1128
1375
|
const messageEntries = branch.filter((e) => e.type === "message");
|
|
@@ -1153,6 +1400,7 @@ export class AgentSession {
|
|
|
1153
1400
|
*/
|
|
1154
1401
|
async listBranches() {
|
|
1155
1402
|
const loaded = await this.sessionManager.load(this.sessionPath);
|
|
1403
|
+
this.setSessionPath(loaded.path);
|
|
1156
1404
|
return this.sessionManager.listBranches(loaded.entries);
|
|
1157
1405
|
}
|
|
1158
1406
|
getState() {
|
|
@@ -1170,6 +1418,18 @@ export class AgentSession {
|
|
|
1170
1418
|
getPlanMode() {
|
|
1171
1419
|
return this.planModeRef.current;
|
|
1172
1420
|
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Suppress only the pre-final Ideal self-review for this live session.
|
|
1423
|
+
* Autopilot uses this while Nolan independently owns verification; loop-break
|
|
1424
|
+
* and post-compaction re-grounding remain active.
|
|
1425
|
+
*/
|
|
1426
|
+
setIdealReviewSuppressed(suppressed) {
|
|
1427
|
+
this.idealReviewSuppressed = suppressed;
|
|
1428
|
+
if (suppressed) {
|
|
1429
|
+
this.idealReviewPhase = "idle";
|
|
1430
|
+
this.reviewCoverage.reset();
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1173
1433
|
/** Queue a user message (optionally with attachments) to be injected mid-run
|
|
1174
1434
|
* as steering. Returns the new queue length. No-op semantics are the caller's
|
|
1175
1435
|
* concern. */
|
|
@@ -1467,46 +1727,12 @@ export class AgentSession {
|
|
|
1467
1727
|
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
1468
1728
|
}
|
|
1469
1729
|
}
|
|
1470
|
-
/**
|
|
1471
|
-
* Generate a short LLM session title from the conversation so far (first user
|
|
1472
|
-
* message + first assistant reply). Best-effort; returns null on failure or
|
|
1473
|
-
* when there's no user message yet. Uses the cheapest model for the provider.
|
|
1474
|
-
*/
|
|
1475
|
-
async generateTitle() {
|
|
1476
|
-
const extractText = (content) => typeof content === "string"
|
|
1477
|
-
? content
|
|
1478
|
-
: content
|
|
1479
|
-
.map((c) => c.type === "text" && "text" in c && typeof c.text === "string" ? c.text : "")
|
|
1480
|
-
.join(" ");
|
|
1481
|
-
const userMsg = this.messages.find((m) => m.role === "user");
|
|
1482
|
-
const assistantMsg = this.messages.find((m) => m.role === "assistant");
|
|
1483
|
-
const userText = userMsg ? extractText(userMsg.content) : "";
|
|
1484
|
-
if (!userText.trim())
|
|
1485
|
-
return null;
|
|
1486
|
-
try {
|
|
1487
|
-
const creds = await this.authStorage.resolveCredentials(this.provider, {
|
|
1488
|
-
storageKeys: this.currentAuthStorageKeys(),
|
|
1489
|
-
});
|
|
1490
|
-
const title = await generateSessionTitle({
|
|
1491
|
-
provider: this.provider,
|
|
1492
|
-
userMessage: userText,
|
|
1493
|
-
assistantPreview: assistantMsg ? extractText(assistantMsg.content).slice(0, 200) : "",
|
|
1494
|
-
apiKey: creds.accessToken,
|
|
1495
|
-
baseUrl: this.baseUrl ?? creds.baseUrl,
|
|
1496
|
-
accountId: creds.accountId,
|
|
1497
|
-
});
|
|
1498
|
-
return title || null;
|
|
1499
|
-
}
|
|
1500
|
-
catch {
|
|
1501
|
-
return null;
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
1730
|
/**
|
|
1505
1731
|
* Rewrite a draft prompt into a tighter, terminology-correct version using
|
|
1506
1732
|
* the ACTIVE provider/model. A stateless one-off LLM call (no agent loop, no
|
|
1507
1733
|
* tools, no session mutation) — safe to run even mid-run. Returns the plain
|
|
1508
1734
|
* enhanced text plus typed segments marking each corrected term. Errors throw
|
|
1509
|
-
* so the caller can surface them
|
|
1735
|
+
* so the caller can surface them.
|
|
1510
1736
|
*/
|
|
1511
1737
|
async enhancePrompt(text) {
|
|
1512
1738
|
if (!text.trim())
|
|
@@ -1598,21 +1824,44 @@ export class AgentSession {
|
|
|
1598
1824
|
this.lspManager?.shutdownAll();
|
|
1599
1825
|
await Promise.all([this.subAgentManager?.shutdownAll(), this.mcpManager?.dispose()]);
|
|
1600
1826
|
await this.extensionLoader.deactivateAll();
|
|
1827
|
+
this.setSessionPath("");
|
|
1601
1828
|
this.eventBus.removeAllListeners();
|
|
1602
1829
|
this.messages = [];
|
|
1603
1830
|
this.tools = [];
|
|
1604
1831
|
}
|
|
1605
1832
|
// ── Private ────────────────────────────────────────────
|
|
1833
|
+
setSessionPath(nextPath) {
|
|
1834
|
+
if (this.sessionPath === nextPath)
|
|
1835
|
+
return;
|
|
1836
|
+
if (this.sessionPath)
|
|
1837
|
+
this.sessionManager.unregisterActivePath(this.sessionPath);
|
|
1838
|
+
this.sessionPath = nextPath;
|
|
1839
|
+
if (nextPath)
|
|
1840
|
+
this.sessionManager.registerActivePath(nextPath);
|
|
1841
|
+
}
|
|
1606
1842
|
async createNewSession() {
|
|
1607
|
-
const session = await this.sessionManager.create(this.cwd, this.provider, this.model
|
|
1843
|
+
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
1844
|
+
conversationId: this.conversationId || undefined,
|
|
1845
|
+
preview: this.sessionPreview || undefined,
|
|
1846
|
+
});
|
|
1608
1847
|
this.sessionId = session.id;
|
|
1609
|
-
this.
|
|
1848
|
+
this.conversationId = session.header.conversationId ?? session.id;
|
|
1849
|
+
this.setSessionPath(session.path);
|
|
1610
1850
|
this.lastPersistedIndex = this.messages.length;
|
|
1611
1851
|
}
|
|
1612
1852
|
async loadExistingSession(sessionPath) {
|
|
1613
1853
|
const loaded = await this.sessionManager.load(sessionPath);
|
|
1614
1854
|
// Use the leaf from the header to walk the correct branch
|
|
1615
1855
|
const loadedMessages = this.sessionManager.getMessages(loaded.entries, loaded.header.leafId);
|
|
1856
|
+
this.conversationId = loaded.header.conversationId ?? loaded.header.id;
|
|
1857
|
+
const legacyLabel = [...loaded.entries]
|
|
1858
|
+
.reverse()
|
|
1859
|
+
.find((entry) => entry.type === "label")
|
|
1860
|
+
?.label.replace(/\s+/g, " ")
|
|
1861
|
+
.trim()
|
|
1862
|
+
.slice(0, 80);
|
|
1863
|
+
this.sessionPreview =
|
|
1864
|
+
legacyLabel || loaded.header.preview || findUserSessionPrompt(loadedMessages);
|
|
1616
1865
|
// Restore Nolan's advisory turns (custom entries, not on the message branch) so
|
|
1617
1866
|
// they reappear in the transcript and survive into the continuation file.
|
|
1618
1867
|
this.nolanTurns = this.sessionManager.getNolanTurns(loaded.entries);
|
|
@@ -1623,9 +1872,15 @@ export class AgentSession {
|
|
|
1623
1872
|
this.turnMetrics = this.sessionManager.getTurnMetrics(loaded.entries);
|
|
1624
1873
|
// Track the current leaf for subsequent entries
|
|
1625
1874
|
this.currentLeafId = loaded.header.leafId;
|
|
1626
|
-
// Rebuild messages: keep system, add loaded
|
|
1875
|
+
// Rebuild messages: keep system, add loaded. Older ezcoder-app sessions may
|
|
1876
|
+
// contain full-resolution attachments; repair them once on load so they do
|
|
1877
|
+
// not fail when Anthropic's stricter many-image limit activates later.
|
|
1627
1878
|
const systemMsg = this.messages[0]; // Already built
|
|
1628
1879
|
this.messages = [systemMsg, ...loadedMessages];
|
|
1880
|
+
const normalizedImageCount = await normalizeMessageImages(this.messages);
|
|
1881
|
+
if (normalizedImageCount > 0) {
|
|
1882
|
+
log("INFO", "session", `Resized ${normalizedImageCount} restored session image(s)`);
|
|
1883
|
+
}
|
|
1629
1884
|
// Auto-compact on load if the restored session exceeds the context window.
|
|
1630
1885
|
// Without this, huge sessions (1M+ tokens) get loaded into memory and OOM.
|
|
1631
1886
|
const creds = await this.authStorage.resolveCredentials(this.provider, {
|
|
@@ -1639,7 +1894,15 @@ export class AgentSession {
|
|
|
1639
1894
|
provider: this.provider,
|
|
1640
1895
|
accountId: creds.accountId,
|
|
1641
1896
|
});
|
|
1642
|
-
|
|
1897
|
+
const needsLoadCompaction = this.settingsManager.get("autoCompact") &&
|
|
1898
|
+
shouldCompact(this.messages, contextWindow, this.settingsManager.get("compactThreshold"));
|
|
1899
|
+
if (needsLoadCompaction && this.opts.deferLoadCompaction) {
|
|
1900
|
+
// Host readiness is gated on initialize() — don't block it on a summary
|
|
1901
|
+
// LLM call (up to 30s). runLoop()'s pre-run auto-compaction picks this
|
|
1902
|
+
// up on the first prompt and emits compaction_start/_end for the UI.
|
|
1903
|
+
log("INFO", "session", "Restored session exceeds context — deferring compaction to first prompt");
|
|
1904
|
+
}
|
|
1905
|
+
else if (needsLoadCompaction) {
|
|
1643
1906
|
await this.subAgentManager?.hydrate(loaded.header.id);
|
|
1644
1907
|
log("INFO", "session", `Restored session exceeds context — auto-compacting`);
|
|
1645
1908
|
const compacted = await compact(this.messages, {
|
|
@@ -1661,9 +1924,13 @@ export class AgentSession {
|
|
|
1661
1924
|
// what's in memory — fork a fresh session file for the compacted state
|
|
1662
1925
|
// (mirrors compact()'s own persistence) so `ezcoder continue` picks up
|
|
1663
1926
|
// the summary instead of the full original transcript.
|
|
1664
|
-
const session = await this.sessionManager.create(this.cwd, this.provider, this.model
|
|
1927
|
+
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
1928
|
+
conversationId: this.conversationId || undefined,
|
|
1929
|
+
preview: this.sessionPreview || undefined,
|
|
1930
|
+
});
|
|
1665
1931
|
this.sessionId = session.id;
|
|
1666
|
-
this.
|
|
1932
|
+
this.conversationId = session.header.conversationId ?? session.id;
|
|
1933
|
+
this.setSessionPath(session.path);
|
|
1667
1934
|
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
1668
1935
|
this.currentLeafId = null;
|
|
1669
1936
|
// Re-persist (compacted) messages — skip system, it's rebuilt on load
|
|
@@ -1691,13 +1958,16 @@ export class AgentSession {
|
|
|
1691
1958
|
// session was merely reopened (e.g. app/window restart) with zero new
|
|
1692
1959
|
// messages in between — the duplicate entries seen in the session list.
|
|
1693
1960
|
this.sessionId = loaded.header.id;
|
|
1694
|
-
this.
|
|
1961
|
+
this.setSessionPath(loaded.path);
|
|
1695
1962
|
this.lastPersistedIndex = this.messages.length;
|
|
1696
1963
|
}
|
|
1697
1964
|
async prepareDynamicContext(_latestUserPrompt) {
|
|
1698
1965
|
return this.messages;
|
|
1699
1966
|
}
|
|
1700
1967
|
async persistMessage(message) {
|
|
1968
|
+
if (!this.sessionPreview && message.role === "user") {
|
|
1969
|
+
this.sessionPreview = getUserSessionPrompt(message.content) ?? "";
|
|
1970
|
+
}
|
|
1701
1971
|
// Transient sessions (subagent spawns) have no session file — skip.
|
|
1702
1972
|
if (!this.sessionPath)
|
|
1703
1973
|
return;
|