@prestyj/cli 5.30.0 → 5.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -39
- package/assets/skills/bulletproof/SKILL.md +1 -1
- package/assets/skills/clarify/SKILL.md +1 -1
- package/assets/skills/code-review/SKILL.md +1 -1
- package/assets/skills/compliance-guard/SKILL.md +1 -1
- package/assets/skills/durable/SKILL.md +1 -1
- package/assets/skills/evidence-led-ui/SKILL.md +1 -1
- package/assets/skills/lean/SKILL.md +1 -1
- package/assets/skills/refactoring/SKILL.md +1 -1
- package/assets/skills/root-cause/SKILL.md +1 -1
- package/assets/skills/shared-language/SKILL.md +1 -1
- package/assets/skills/tdd/SKILL.md +1 -1
- package/dist/app-sidecar/app-settings.d.ts +63 -0
- package/dist/app-sidecar/app-settings.js +147 -0
- package/dist/app-sidecar/app-settings.js.map +1 -0
- package/dist/app-sidecar/attachments.d.ts +11 -0
- package/dist/app-sidecar/attachments.js +47 -0
- package/dist/app-sidecar/attachments.js.map +1 -0
- package/dist/app-sidecar/auth-routes.d.ts +3 -0
- package/dist/app-sidecar/auth-routes.js +293 -0
- package/dist/app-sidecar/auth-routes.js.map +1 -0
- package/dist/app-sidecar/autopilot-runner.d.ts +62 -0
- package/dist/app-sidecar/autopilot-runner.js +373 -0
- package/dist/app-sidecar/autopilot-runner.js.map +1 -0
- package/dist/app-sidecar/file-search.d.ts +15 -0
- package/dist/app-sidecar/file-search.js +103 -0
- package/dist/app-sidecar/file-search.js.map +1 -0
- package/dist/app-sidecar/hf-pull-manager.d.ts +20 -0
- package/dist/app-sidecar/hf-pull-manager.js +224 -0
- package/dist/app-sidecar/hf-pull-manager.js.map +1 -0
- package/dist/app-sidecar/history-routes.d.ts +3 -0
- package/dist/app-sidecar/history-routes.js +375 -0
- package/dist/app-sidecar/history-routes.js.map +1 -0
- package/dist/app-sidecar/history.d.ts +67 -0
- package/dist/app-sidecar/history.js +23 -0
- package/dist/app-sidecar/history.js.map +1 -0
- package/dist/app-sidecar/http.d.ts +5 -0
- package/dist/app-sidecar/http.js +27 -0
- package/dist/app-sidecar/http.js.map +1 -0
- package/dist/app-sidecar/integration-routes.d.ts +3 -0
- package/dist/app-sidecar/integration-routes.js +286 -0
- package/dist/app-sidecar/integration-routes.js.map +1 -0
- package/dist/app-sidecar/json-mode.d.ts +10 -0
- package/dist/app-sidecar/json-mode.js +73 -0
- package/dist/app-sidecar/json-mode.js.map +1 -0
- package/dist/app-sidecar/keep-awake.d.ts +4 -0
- package/dist/app-sidecar/keep-awake.js +5 -0
- package/dist/app-sidecar/keep-awake.js.map +1 -0
- package/dist/app-sidecar/mcp-routes.d.ts +3 -0
- package/dist/app-sidecar/mcp-routes.js +185 -0
- package/dist/app-sidecar/mcp-routes.js.map +1 -0
- package/dist/app-sidecar/mcp-rows.d.ts +31 -0
- package/dist/app-sidecar/mcp-rows.js +77 -0
- package/dist/app-sidecar/mcp-rows.js.map +1 -0
- package/dist/app-sidecar/nolan-context.d.ts +20 -0
- package/dist/app-sidecar/nolan-context.js +55 -0
- package/dist/app-sidecar/nolan-context.js.map +1 -0
- package/dist/app-sidecar/progress-manager.d.ts +10 -0
- package/dist/app-sidecar/progress-manager.js +116 -0
- package/dist/app-sidecar/progress-manager.js.map +1 -0
- package/dist/app-sidecar/prompt-routes.d.ts +3 -0
- package/dist/app-sidecar/prompt-routes.js +355 -0
- package/dist/app-sidecar/prompt-routes.js.map +1 -0
- package/dist/app-sidecar/repo-polls.d.ts +18 -0
- package/dist/app-sidecar/repo-polls.js +45 -0
- package/dist/app-sidecar/repo-polls.js.map +1 -0
- package/dist/app-sidecar/route-context.d.ts +147 -0
- package/dist/app-sidecar/route-context.js +2 -0
- package/dist/app-sidecar/route-context.js.map +1 -0
- package/dist/app-sidecar/run-control-routes.d.ts +3 -0
- package/dist/app-sidecar/run-control-routes.js +437 -0
- package/dist/app-sidecar/run-control-routes.js.map +1 -0
- package/dist/app-sidecar/session-routes.d.ts +3 -0
- package/dist/app-sidecar/session-routes.js +115 -0
- package/dist/app-sidecar/session-routes.js.map +1 -0
- package/dist/app-sidecar/session-types.d.ts +39 -0
- package/dist/app-sidecar/session-types.js +5 -0
- package/dist/app-sidecar/session-types.js.map +1 -0
- package/dist/app-sidecar/task-routes.d.ts +3 -0
- package/dist/app-sidecar/task-routes.js +232 -0
- package/dist/app-sidecar/task-routes.js.map +1 -0
- package/dist/app-sidecar/usage.d.ts +19 -0
- package/dist/app-sidecar/usage.js +153 -0
- package/dist/app-sidecar/usage.js.map +1 -0
- package/dist/app-sidecar/workspace-routes.d.ts +3 -0
- package/dist/app-sidecar/workspace-routes.js +374 -0
- package/dist/app-sidecar/workspace-routes.js.map +1 -0
- package/dist/app-sidecar.js +541 -3971
- package/dist/app-sidecar.js.map +1 -1
- package/dist/chat-agents/jiwa.js +2 -0
- package/dist/chat-agents/jiwa.js.map +1 -1
- package/dist/chat-agents/memory.js +2 -0
- package/dist/chat-agents/memory.js.map +1 -1
- package/dist/cli/auth.js +13 -9
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli.js +10 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/core/acceptance-checks.js +3 -9
- package/dist/core/acceptance-checks.js.map +1 -1
- package/dist/core/agent-session/helpers.d.ts +26 -0
- package/dist/core/agent-session/helpers.js +47 -0
- package/dist/core/agent-session/helpers.js.map +1 -0
- package/dist/core/agent-session/mcp-tools.d.ts +68 -0
- package/dist/core/agent-session/mcp-tools.js +195 -0
- package/dist/core/agent-session/mcp-tools.js.map +1 -0
- package/dist/core/agent-session/run-loop-monitor.d.ts +63 -0
- package/dist/core/agent-session/run-loop-monitor.js +164 -0
- package/dist/core/agent-session/run-loop-monitor.js.map +1 -0
- package/dist/core/agent-session/transcript-markers.d.ts +57 -0
- package/dist/core/agent-session/transcript-markers.js +166 -0
- package/dist/core/agent-session/transcript-markers.js.map +1 -0
- package/dist/core/agent-session/verification-tracker.d.ts +50 -0
- package/dist/core/agent-session/verification-tracker.js +269 -0
- package/dist/core/agent-session/verification-tracker.js.map +1 -0
- package/dist/core/agent-session.d.ts +27 -87
- package/dist/core/agent-session.js +166 -713
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-providers.js +3 -3
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/autopilot-cycle.d.ts +8 -2
- package/dist/core/autopilot-cycle.js +22 -3
- package/dist/core/autopilot-cycle.js.map +1 -1
- package/dist/core/bundled-agents.js +6 -6
- package/dist/core/bundled-agents.js.map +1 -1
- package/dist/core/checklist-git.d.ts +19 -0
- package/dist/core/checklist-git.js +54 -0
- package/dist/core/checklist-git.js.map +1 -0
- package/dist/core/checklist-items.d.ts +10 -1
- package/dist/core/checklist-items.js +22 -0
- package/dist/core/checklist-items.js.map +1 -1
- package/dist/core/checklist-prompt.d.ts +14 -3
- package/dist/core/checklist-prompt.js +38 -8
- package/dist/core/checklist-prompt.js.map +1 -1
- package/dist/core/checklist-snapshot.d.ts +5 -1
- package/dist/core/checklist-snapshot.js +20 -3
- package/dist/core/checklist-snapshot.js.map +1 -1
- package/dist/core/checklist-store.d.ts +5 -1
- package/dist/core/checklist-store.js +35 -4
- package/dist/core/checklist-store.js.map +1 -1
- package/dist/core/context-limits.js +1 -1
- package/dist/core/context-limits.js.map +1 -1
- package/dist/core/event-bus.d.ts +1 -1
- package/dist/core/event-bus.js +39 -13
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/extensions/plugin-bundles.js +4 -2
- package/dist/core/extensions/plugin-bundles.js.map +1 -1
- package/dist/core/hashline.d.ts +10 -8
- package/dist/core/hashline.js.map +1 -1
- package/dist/core/mcp/client.js +2 -0
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/loopback.d.ts +2 -2
- package/dist/core/mcp/loopback.js +2 -2
- package/dist/core/mcp/shared-pool.js +4 -3
- package/dist/core/mcp/shared-pool.js.map +1 -1
- package/dist/core/persistent-shell.js +2 -1
- package/dist/core/persistent-shell.js.map +1 -1
- package/dist/core/plan-handoff.d.ts +30 -0
- package/dist/core/plan-handoff.js +10 -0
- package/dist/core/plan-handoff.js.map +1 -0
- package/dist/core/plan-submission-gate.d.ts +28 -0
- package/dist/core/plan-submission-gate.js +27 -0
- package/dist/core/plan-submission-gate.js.map +1 -0
- package/dist/core/process-manager.js +9 -1
- package/dist/core/process-manager.js.map +1 -1
- package/dist/core/project-health-scan.d.ts +109 -0
- package/dist/core/project-health-scan.js +673 -0
- package/dist/core/project-health-scan.js.map +1 -0
- package/dist/core/project-health-score.d.ts +41 -0
- package/dist/core/project-health-score.js +257 -0
- package/dist/core/project-health-score.js.map +1 -0
- package/dist/core/prompt-commands.js +44 -6
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-storage.d.ts +1 -0
- package/dist/core/session-storage.js +1 -1
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/skills.js +6 -9
- package/dist/core/skills.js.map +1 -1
- package/dist/core/style-packs/index.js +2 -11
- package/dist/core/style-packs/index.js.map +1 -1
- package/dist/core/style-packs/packs.d.ts +3 -12
- package/dist/core/style-packs/packs.js +132 -173
- package/dist/core/style-packs/packs.js.map +1 -1
- package/dist/core/subagent-manager.js +11 -6
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-receipt.js +3 -4
- package/dist/core/subagent-receipt.js.map +1 -1
- package/dist/core/ui-adoption.js +2 -0
- package/dist/core/ui-adoption.js.map +1 -1
- package/dist/core/ui-registry.js +5 -4
- package/dist/core/ui-registry.js.map +1 -1
- package/dist/core/verification-evidence.d.ts +21 -2
- package/dist/core/verification-evidence.js +283 -8
- package/dist/core/verification-evidence.js.map +1 -1
- package/dist/core/verification-gate.js +4 -2
- package/dist/core/verification-gate.js.map +1 -1
- package/dist/core/verify-commands.js +2 -3
- package/dist/core/verify-commands.js.map +1 -1
- package/dist/modes/agent-home-mode.js +1 -4
- package/dist/modes/agent-home-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.js +13 -8
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/system-prompt.js +70 -87
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/ask-user.js +13 -74
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/bash.d.ts +3 -1
- package/dist/tools/bash.js +28 -84
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/checklist.d.ts +7 -14
- package/dist/tools/checklist.js +22 -41
- package/dist/tools/checklist.js.map +1 -1
- package/dist/tools/code-nav.js +7 -29
- package/dist/tools/code-nav.js.map +1 -1
- package/dist/tools/edit.d.ts +4 -9
- package/dist/tools/edit.js +175 -65
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/enter-plan.js +2 -7
- package/dist/tools/enter-plan.js.map +1 -1
- package/dist/tools/exit-plan.js +2 -3
- package/dist/tools/exit-plan.js.map +1 -1
- package/dist/tools/find.js +3 -4
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/grep.js +9 -20
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.js +10 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/ls.js +3 -3
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/prompt-hints.js +34 -47
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/read-only-bash.js +3 -0
- package/dist/tools/read-only-bash.js.map +1 -1
- package/dist/tools/read.js +8 -20
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/search-code.js +4 -12
- package/dist/tools/search-code.js.map +1 -1
- package/dist/tools/skill.js +13 -7
- package/dist/tools/skill.js.map +1 -1
- package/dist/tools/subagent-control.js +7 -22
- package/dist/tools/subagent-control.js.map +1 -1
- package/dist/tools/subagent.js +1 -1
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/task-output.js +4 -15
- package/dist/tools/task-output.js.map +1 -1
- package/dist/tools/task-send.js +5 -17
- package/dist/tools/task-send.js.map +1 -1
- package/dist/tools/task-stop.js +2 -2
- package/dist/tools/task-stop.js.map +1 -1
- package/dist/tools/tool-search.js +2 -7
- package/dist/tools/tool-search.js.map +1 -1
- package/dist/tools/tool-tiers.d.ts +21 -4
- package/dist/tools/tool-tiers.js +44 -17
- package/dist/tools/tool-tiers.js.map +1 -1
- package/dist/tools/web-fetch.js +7 -33
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/write.js +3 -4
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/App.js +20 -26
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/Footer.js +1 -2
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.js +0 -2
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/ToolExecution.js +4 -3
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +7 -6
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/sessions.js +1 -5
- package/dist/ui/sessions.js.map +1 -1
- package/dist/ui/utils/highlight.d.ts +0 -5
- package/dist/ui/utils/highlight.js +3 -49
- package/dist/ui/utils/highlight.js.map +1 -1
- package/dist/ui/utils/markdown-renderer.d.ts +6 -0
- package/dist/ui/utils/markdown-renderer.js +21 -5
- package/dist/ui/utils/markdown-renderer.js.map +1 -1
- package/dist/utils/process.js +3 -3
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/shared-poll.d.ts +37 -0
- package/dist/utils/shared-poll.js +104 -0
- package/dist/utils/shared-poll.js.map +1 -0
- package/package.json +8 -8
- package/dist/core/api-benchmark.d.ts +0 -63
- package/dist/core/api-benchmark.js +0 -381
- package/dist/core/api-benchmark.js.map +0 -1
- package/dist/core/bash-spawn-benchmark.d.ts +0 -1
- package/dist/core/bash-spawn-benchmark.js +0 -139
- package/dist/core/bash-spawn-benchmark.js.map +0 -1
- package/dist/core/cache-warm-benchmark.d.ts +0 -21
- package/dist/core/cache-warm-benchmark.js +0 -170
- package/dist/core/cache-warm-benchmark.js.map +0 -1
- package/dist/core/fast-apply-benchmark.d.ts +0 -25
- package/dist/core/fast-apply-benchmark.js +0 -284
- package/dist/core/fast-apply-benchmark.js.map +0 -1
- package/dist/core/hashline-edit-benchmark.d.ts +0 -88
- package/dist/core/hashline-edit-benchmark.js +0 -518
- package/dist/core/hashline-edit-benchmark.js.map +0 -1
- package/dist/core/semantic-search-benchmark.d.ts +0 -36
- package/dist/core/semantic-search-benchmark.js +0 -211
- package/dist/core/semantic-search-benchmark.js.map +0 -1
- package/dist/core/speed-benchmark.d.ts +0 -132
- package/dist/core/speed-benchmark.js +0 -410
- package/dist/core/speed-benchmark.js.map +0 -1
- package/dist/ui/activity-phrases.d.ts +0 -10
- package/dist/ui/activity-phrases.js +0 -184
- package/dist/ui/activity-phrases.js.map +0 -1
- package/dist/ui/components/PlanApproval.d.ts +0 -7
- package/dist/ui/components/PlanApproval.js +0 -60
- package/dist/ui/components/PlanApproval.js.map +0 -1
- package/dist/ui/components/PlanBanner.d.ts +0 -5
- package/dist/ui/components/PlanBanner.js +0 -31
- package/dist/ui/components/PlanBanner.js.map +0 -1
- package/dist/ui/components/PlanProgress.d.ts +0 -6
- package/dist/ui/components/PlanProgress.js +0 -30
- package/dist/ui/components/PlanProgress.js.map +0 -1
- package/dist/ui/hooks/useMinDisplayTime.d.ts +0 -10
- package/dist/ui/hooks/useMinDisplayTime.js +0 -34
- package/dist/ui/hooks/useMinDisplayTime.js.map +0 -1
- package/dist/ui/hooks/useSessionManager.d.ts +0 -12
- package/dist/ui/hooks/useSessionManager.js +0 -43
- package/dist/ui/hooks/useSessionManager.js.map +0 -1
- package/dist/ui/hooks/useSlashCommands.d.ts +0 -6
- package/dist/ui/hooks/useSlashCommands.js +0 -11
- package/dist/ui/hooks/useSlashCommands.js.map +0 -1
- package/dist/ui/terminal-history-spacing.d.ts +0 -1
- package/dist/ui/terminal-history-spacing.js +0 -2
- package/dist/ui/terminal-history-spacing.js.map +0 -1
- package/dist/ui/utils/markdown-cache.d.ts +0 -10
- package/dist/ui/utils/markdown-cache.js +0 -58
- package/dist/ui/utils/markdown-cache.js.map +0 -1
- package/dist/ui/utils/token-to-ansi.d.ts +0 -8
- package/dist/ui/utils/token-to-ansi.js +0 -323
- package/dist/ui/utils/token-to-ansi.js.map +0 -1
|
@@ -13,31 +13,27 @@ import { dualAuthProvider } from "@prestyj/core";
|
|
|
13
13
|
import { getClaudeCliUserAgent, recordRequiredClaudeCodeVersion } from "./claude-code-version.js";
|
|
14
14
|
import { kimiCodingHeaders, isKimiCodingEndpoint } from "./oauth/kimi.js";
|
|
15
15
|
import { isGrokCliEndpoint } from "./oauth/xai.js";
|
|
16
|
-
import { SessionManager,
|
|
16
|
+
import { SessionManager, } from "./session-manager.js";
|
|
17
17
|
import { ExtensionLoader } from "./extensions/loader.js";
|
|
18
18
|
import { shouldCompact, compact, } from "./compaction/compactor.js";
|
|
19
|
-
import { getHistoryMessageVisibility
|
|
19
|
+
import { getHistoryMessageVisibility } from "./session-history.js";
|
|
20
20
|
import { sourceFingerprint as computeSourceFingerprint } from "./session-compaction.js";
|
|
21
|
-
import { getAuthStorageKeys, getContextWindow, getModel,
|
|
21
|
+
import { getAuthStorageKeys, getContextWindow, getModel, MODELS } from "./model-registry.js";
|
|
22
22
|
import { discoverSkills } from "./skills.js";
|
|
23
23
|
import { ensureAppDirs } from "../config.js";
|
|
24
24
|
import { buildSubAgentSystemPrompt, buildSystemPrompt, } from "../system-prompt.js";
|
|
25
25
|
import { createTools, createWebSearchTool, } from "../tools/index.js";
|
|
26
26
|
import { partitionToolsByTier } from "../tools/tool-tiers.js";
|
|
27
27
|
import { formatImpactForVerification } from "./test-impact.js";
|
|
28
|
-
import { autoBackgroundedId, REVIEW_REJECTED_BEFORE_START } from "../tools/bash.js";
|
|
29
28
|
import { buildProcessCompletionFollowUp } from "./process-gate.js";
|
|
29
|
+
import { PLAN_SUBMISSION_NUDGE, shouldNudgePlanSubmission } from "./plan-submission-gate.js";
|
|
30
30
|
import { buildSubAgentCompletionFollowUp } from "./subagent-manager.js";
|
|
31
31
|
import { applyAsyncSubagentPolicy } from "./subagent-policy.js";
|
|
32
32
|
import { resolveResponsesLite, resolveStrictTools, } from "./codex-request-shape.js";
|
|
33
33
|
import { subAgentDescription } from "../tools/subagent.js";
|
|
34
|
-
import {
|
|
35
|
-
import { MCPClientManager, getAllMcpServers } from "./mcp/index.js";
|
|
36
|
-
import { clampMcpToolDescription, DeferredToolCatalog } from "./mcp/deferred-catalog.js";
|
|
34
|
+
import { getAllMcpServers } from "./mcp/index.js";
|
|
37
35
|
import { CONTEXT_LIMITS, resolveSessionContextLimits, } from "./context-limits.js";
|
|
38
|
-
import { McpCatalogCache } from "./mcp/catalog-cache.js";
|
|
39
36
|
import { describeDropped, importForeignSession, } from "./foreign-session-import.js";
|
|
40
|
-
import { createToolSearchTool } from "../tools/tool-search.js";
|
|
41
37
|
import { createSessionStatsTool } from "../tools/session-stats.js";
|
|
42
38
|
import { createDiagnoseCommand, isInternalDiagnosticsEnabled, SessionDiagnosticsRecorder, } from "./internal-diagnostics.js";
|
|
43
39
|
import { log } from "./logger.js";
|
|
@@ -54,15 +50,13 @@ import { pruneStaleToolResults } from "./compaction/tool-result-pruner.js";
|
|
|
54
50
|
import { discoverAgents } from "./agents.js";
|
|
55
51
|
import { enhancePrompt } from "../utils/prompt-enhancer.js";
|
|
56
52
|
import { detectLanguages, detectProjectStack } from "./language-detector.js";
|
|
57
|
-
import {
|
|
53
|
+
import { buildLoopBreakMessage } from "./loop-breaker.js";
|
|
58
54
|
import { buildRegroundingMessage, requestTextForRegrounding } from "./regrounding.js";
|
|
59
|
-
import { buildSemanticLoopJudgePrompt, buildSemanticLoopMessage,
|
|
55
|
+
import { buildSemanticLoopJudgePrompt, buildSemanticLoopMessage, parseSemanticLoopVerdict, SEMANTIC_LOOP_JUDGE_TIMEOUT_MS, withJudgeTimeout, } from "./semantic-loop-check.js";
|
|
60
56
|
import { buildEnvDeltaMessage } from "./env-delta.js";
|
|
61
57
|
import { wrapSteeringText, buildNotificationSteeringText, STEERING_PREFIX } from "./steering.js";
|
|
62
58
|
import { AgentNotificationQueue } from "./agent-notifications.js";
|
|
63
|
-
import { VerificationGate,
|
|
64
|
-
import { classifyVerificationCommand } from "./verification-evidence.js";
|
|
65
|
-
import { captureVerificationSnapshot } from "./verification-snapshot.js";
|
|
59
|
+
import { VerificationGate, VERIFICATION_STATE_KIND } from "./verification-gate.js";
|
|
66
60
|
import { findUserSessionPrompt, getUserSessionPrompt } from "./session-preview.js";
|
|
67
61
|
import { normalizeMessageImages } from "./message-images.js";
|
|
68
62
|
import { loadStreamRules } from "./stream-rules.js";
|
|
@@ -70,41 +64,12 @@ import crypto from "node:crypto";
|
|
|
70
64
|
import fs from "node:fs/promises";
|
|
71
65
|
import os from "node:os";
|
|
72
66
|
import path from "node:path";
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// ── Tool-result policy ─────────────────────────────────────
|
|
80
|
-
/** Resolve the per-result cap passed to the agent loop for the active transport. */
|
|
81
|
-
export function resolveSessionToolResultCharLimit(model, provider, accountId) {
|
|
82
|
-
return (getToolResultCharLimit(model, { provider, accountId }) ??
|
|
83
|
-
Math.floor(getContextWindow(model, { provider, accountId }) * 3.5 * 0.3));
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Aggregate budget for ALL tool results produced in one assistant turn.
|
|
87
|
-
* Individual results are already capped, but wide parallel fan-outs (GPT-5.6's
|
|
88
|
-
* signature behavior) were observed injecting 100k+ uncached tokens in a single
|
|
89
|
-
* turn. ~15% of the context window in chars (1 token ≈ 3.5 chars), floored at
|
|
90
|
-
* 100KB so small windows still fit two full-size reads, ceilinged at 240KB so
|
|
91
|
-
* 1M-context models don't waive the budget entirely.
|
|
92
|
-
*/
|
|
93
|
-
export function resolveSessionTurnToolResultCharLimit(model, provider, accountId) {
|
|
94
|
-
const contextChars = getContextWindow(model, { provider, accountId }) * 3.5;
|
|
95
|
-
return Math.max(100_000, Math.min(Math.floor(contextChars * 0.15), 240_000));
|
|
96
|
-
}
|
|
97
|
-
/** Marker the compactor prepends to the summary message it injects. */
|
|
98
|
-
/**
|
|
99
|
-
* True when an assistant message ends a turn with tool calls still awaiting
|
|
100
|
-
* their results. Inserting a user message there would orphan the tool_use
|
|
101
|
-
* blocks and the provider rejects the next request.
|
|
102
|
-
*/
|
|
103
|
-
function hasUnresolvedToolCalls(message) {
|
|
104
|
-
if (typeof message.content === "string" || !Array.isArray(message.content))
|
|
105
|
-
return false;
|
|
106
|
-
return message.content.some((part) => part.type === "tool_call");
|
|
107
|
-
}
|
|
67
|
+
import { resolveSessionToolResultCharLimit, resolveSessionTurnToolResultCharLimit, hasUnresolvedToolCalls, resolveHomePath, } from "./agent-session/helpers.js";
|
|
68
|
+
import { RunLoopMonitor } from "./agent-session/run-loop-monitor.js";
|
|
69
|
+
import { VerificationTracker } from "./agent-session/verification-tracker.js";
|
|
70
|
+
import { McpToolRegistry } from "./agent-session/mcp-tools.js";
|
|
71
|
+
import { TranscriptMarkers } from "./agent-session/transcript-markers.js";
|
|
72
|
+
export { resolveSessionToolResultCharLimit, resolveSessionTurnToolResultCharLimit, } from "./agent-session/helpers.js";
|
|
108
73
|
// ── Agent Session ──────────────────────────────────────────
|
|
109
74
|
export class AgentSession {
|
|
110
75
|
eventBus = new EventBus();
|
|
@@ -114,24 +79,10 @@ export class AgentSession {
|
|
|
114
79
|
sessionManager;
|
|
115
80
|
extensionLoader = new ExtensionLoader();
|
|
116
81
|
messages = [];
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
// alongside the session and reloaded on resume
|
|
120
|
-
|
|
121
|
-
// webview can interleave them chronologically.
|
|
122
|
-
nolanTurns = [];
|
|
123
|
-
// Autopilot Nolan (auto-reviewer) markers recorded against this build session:
|
|
124
|
-
// the review verdict shown in the transcript (prompted / done / human /
|
|
125
|
-
// capped). Same not-on-the-DAG treatment as nolanTurns — advisory only,
|
|
126
|
-
// persisted + reloaded so a resumed session shows the identical Nolan bubble
|
|
127
|
-
// the live run showed instead of dropping it or replaying a raw verdict.
|
|
128
|
-
autopilotMarkers = [];
|
|
129
|
-
// Generic app transcript markers (plan-mode banner, task header, error rows,
|
|
130
|
-
// user-bubble display hints). Same not-on-the-DAG treatment as nolanTurns —
|
|
131
|
-
// display only, persisted + reloaded so a resumed session shows the same
|
|
132
|
-
// transcript rows the live run showed.
|
|
133
|
-
appMarkers = [];
|
|
134
|
-
turnMetrics = [];
|
|
82
|
+
// Display-only transcript history (Nolan turns, autopilot verdicts, app markers,
|
|
83
|
+
// turn metrics). NEVER part of `messages` (EZ Coder must not see them), but
|
|
84
|
+
// persisted alongside the session and reloaded on resume.
|
|
85
|
+
markers = new TranscriptMarkers(() => this.sessionManager, () => this.sessionPath);
|
|
135
86
|
cacheDiagnostics = new CacheDiagnostics();
|
|
136
87
|
/** Internal-only (EZ_INTERNAL): live per-session cost/reliability recorder.
|
|
137
88
|
* Absent entirely in public builds — see core/internal-diagnostics.ts. */
|
|
@@ -150,25 +101,8 @@ export class AgentSession {
|
|
|
150
101
|
// ── Self-correction hook state (mirrors the TUI's useAgentLoop refs) ──
|
|
151
102
|
// Reset at the start of every run; observed from the event stream; read by
|
|
152
103
|
// the loop-break (mid-loop) callback.
|
|
153
|
-
|
|
154
|
-
hookText = "";
|
|
155
|
-
hookConsecutiveFailures = 0;
|
|
156
|
-
hookRepeatedNoProgressCalls = 0;
|
|
157
|
-
hookProgressTracker = new ToolCallProgressTracker();
|
|
158
|
-
hookCycleDetector = new CycleDetector();
|
|
159
|
-
hookCyclicPattern = null;
|
|
160
|
-
hookFileEditCounts = new Map();
|
|
161
|
-
hookToolCalls = new Map();
|
|
162
|
-
backgroundVerification = new Map();
|
|
163
|
-
/** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */
|
|
164
|
-
loopBreakInjected = 0;
|
|
104
|
+
loopMonitor = new RunLoopMonitor();
|
|
165
105
|
regroundingInjected = false;
|
|
166
|
-
/** Recent tool-call digests for the semantic loop judge — bounded ring. */
|
|
167
|
-
hookRecentCalls = [];
|
|
168
|
-
/** LLM-judged loop detection state. `verdict` holds a LOOP verdict awaiting
|
|
169
|
-
* injection at the next steering poll; judge failures fail open (no
|
|
170
|
-
* injection) and still consume budget + cooldown. */
|
|
171
|
-
semanticLoop = { checksUsed: 0, lastCheckTurn: 0, pending: false, verdict: null, injected: false };
|
|
172
106
|
/**
|
|
173
107
|
* The environment as the cached system prompt currently describes it.
|
|
174
108
|
* Re-recorded on every prompt build, so a rebuild (e.g. `/add-dir`) needs no
|
|
@@ -179,10 +113,14 @@ export class AgentSession {
|
|
|
179
113
|
runStartedAt = 0;
|
|
180
114
|
/** Gate injections spent this run, capped by MAX_PROCESS_GATE_INJECTIONS. */
|
|
181
115
|
processGateInjected = 0;
|
|
116
|
+
/** Plan-submission reminders spent this run (plan-submission-gate.ts). */
|
|
117
|
+
planSubmissionNudges = 0;
|
|
182
118
|
/** Verification gate: code edited this run, nothing proved it since. Always
|
|
183
119
|
* tracks evidence for run status; the `verificationGateEnabled` setting
|
|
184
120
|
* decides whether an unverified stop is also continued once. */
|
|
185
121
|
verificationGate = new VerificationGate();
|
|
122
|
+
/** Host-observed evidence (edited files, in-flight and background checks) feeding the gate. */
|
|
123
|
+
verificationTracker;
|
|
186
124
|
/** Mirror of the last `hook_armed` value, so the event fires only on an edge. */
|
|
187
125
|
preFinalArmed = false;
|
|
188
126
|
compactionOccurred = false;
|
|
@@ -249,9 +187,8 @@ export class AgentSession {
|
|
|
249
187
|
this.lspManager?.clearPendingDiagnostics();
|
|
250
188
|
void this.subAgentManager?.interruptAll();
|
|
251
189
|
};
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
mcpCatalog;
|
|
190
|
+
/** MCP client, deferred tool_search catalog and cached-stub reconcile state. */
|
|
191
|
+
mcp = new McpToolRegistry(() => this.tools, () => this.contextLimits);
|
|
255
192
|
/** Resolved prompt-injection byte budgets (contextLimits setting). */
|
|
256
193
|
contextLimits = CONTEXT_LIMITS;
|
|
257
194
|
/**
|
|
@@ -260,11 +197,6 @@ export class AgentSession {
|
|
|
260
197
|
* can discover and promote them; a promoted name drops out of this list.
|
|
261
198
|
*/
|
|
262
199
|
deferredBuiltinToolNames = [];
|
|
263
|
-
/** Live (connected) MCP tools by name — the reconcile target for cached stubs. */
|
|
264
|
-
liveMcpTools = new Map();
|
|
265
|
-
/** Server name for each cached-only tool, so a stub knows what to wait on. */
|
|
266
|
-
cachedMcpToolServers = new Map();
|
|
267
|
-
mcpCatalogCache = new McpCatalogCache();
|
|
268
200
|
provider;
|
|
269
201
|
model;
|
|
270
202
|
cwd;
|
|
@@ -337,6 +269,7 @@ export class AgentSession {
|
|
|
337
269
|
opts;
|
|
338
270
|
constructor(options) {
|
|
339
271
|
this.opts = options;
|
|
272
|
+
this.verificationTracker = new VerificationTracker(this.verificationGate, options.cwd, () => this.processManager?.list() ?? []);
|
|
340
273
|
this.provider = options.provider;
|
|
341
274
|
this.model = options.model;
|
|
342
275
|
this.cwd = options.cwd;
|
|
@@ -438,7 +371,7 @@ export class AgentSession {
|
|
|
438
371
|
authStorage: this.authStorage,
|
|
439
372
|
onFileMutated: (filePath) => {
|
|
440
373
|
const relative = path.relative(this.cwd, filePath) || path.basename(filePath);
|
|
441
|
-
this.
|
|
374
|
+
this.verificationTracker.recordFileMutated(relative);
|
|
442
375
|
},
|
|
443
376
|
// Lazy — sessionId/model/provider can change after createTools() runs, so
|
|
444
377
|
// sub-agent spawns read the current parent state at execution time.
|
|
@@ -485,6 +418,10 @@ export class AgentSession {
|
|
|
485
418
|
// every request. Allow-listed sessions keep the eager path — their fixed
|
|
486
419
|
// tool expectations predate the catalog, and tool_search isn't allow-listed.
|
|
487
420
|
if (!this.opts.allowedTools && this.settingsManager.get("deferredBuiltinTools")) {
|
|
421
|
+
// Wrap before partitioning: spawn_agent itself may be deferred, and the
|
|
422
|
+
// catalog must hold the wrapped tool so a promoted spawn still loads
|
|
423
|
+
// wait_agent.
|
|
424
|
+
this.promoteFollowUpTools();
|
|
488
425
|
const { core, deferred } = partitionToolsByTier(this.tools);
|
|
489
426
|
if (deferred.length > 0) {
|
|
490
427
|
// Append-only: `core` preserves the original relative order and
|
|
@@ -492,10 +429,7 @@ export class AgentSession {
|
|
|
492
429
|
// sits inside the cached prefix stays byte-stable across turns.
|
|
493
430
|
this.tools = core;
|
|
494
431
|
this.deferredBuiltinToolNames = deferred.map((t) => t.name);
|
|
495
|
-
this.
|
|
496
|
-
this.mcpCatalog.add(deferred);
|
|
497
|
-
this.ensureToolSearchTool();
|
|
498
|
-
this.promoteWaitAgentAfterSpawn();
|
|
432
|
+
this.mcp.addDeferred(deferred);
|
|
499
433
|
}
|
|
500
434
|
}
|
|
501
435
|
this.rebuildReadTool = rebuildReadTool;
|
|
@@ -510,8 +444,7 @@ export class AgentSession {
|
|
|
510
444
|
// Connect MCP servers. Child sessions skip user-configured servers to avoid
|
|
511
445
|
// cold-spawning every server per short-lived worker. Background connection
|
|
512
446
|
// keeps slow first-run stdio servers off the host readiness path.
|
|
513
|
-
this.
|
|
514
|
-
catalogCache: this.mcpCatalogCache,
|
|
447
|
+
this.mcp.createManager({
|
|
515
448
|
modernProtocol: this.settingsManager.get("mcpModernProtocol"),
|
|
516
449
|
onElicit: this.opts.onMcpElicit,
|
|
517
450
|
});
|
|
@@ -660,7 +593,8 @@ export class AgentSession {
|
|
|
660
593
|
* turn, so background-connected servers become available on the next prompt.
|
|
661
594
|
*/
|
|
662
595
|
async connectMcpServers() {
|
|
663
|
-
|
|
596
|
+
const mcpManager = this.mcp.manager;
|
|
597
|
+
if (!mcpManager)
|
|
664
598
|
return;
|
|
665
599
|
// Allow-listed (read-only advisory) sessions enforce a fixed tool set by
|
|
666
600
|
// name. An MCP server is only connected when its name is explicitly
|
|
@@ -696,7 +630,7 @@ export class AgentSession {
|
|
|
696
630
|
// empty catalog and tool_search would answer "the catalog is empty" for
|
|
697
631
|
// capabilities that genuinely exist — a wrong answer, not a slow one.
|
|
698
632
|
await this.seedMcpCatalogFromCache(servers);
|
|
699
|
-
const connected = await
|
|
633
|
+
const connected = await mcpManager.connectAll(servers);
|
|
700
634
|
// Defense-in-depth: even from a whitelisted server, only push tools that
|
|
701
635
|
// pass the allow-list (no-op when there's no allow-list).
|
|
702
636
|
const mcpTools = this.opts.allowedTools
|
|
@@ -735,170 +669,65 @@ export class AgentSession {
|
|
|
735
669
|
* pushed eagerly when the user opted out.
|
|
736
670
|
* Allow-listed sessions (Nolan) always get the eager path — their fixed tool
|
|
737
671
|
* expectations predate the catalog, and tool_search isn't allow-listed.
|
|
738
|
-
* Promotion pushes onto the live `this.tools` array the running agent loop
|
|
739
|
-
* re-reads every turn, so promoted tools are callable on the next step.
|
|
740
672
|
*/
|
|
741
673
|
addMcpTools(mcpTools) {
|
|
742
|
-
if (mcpTools.length === 0)
|
|
743
|
-
return;
|
|
744
|
-
for (const tool of mcpTools) {
|
|
745
|
-
this.liveMcpTools.set(tool.name, tool);
|
|
746
|
-
this.cachedMcpToolServers.delete(tool.name);
|
|
747
|
-
}
|
|
748
674
|
const defer = !this.opts.allowedTools && this.settingsManager.get("deferredMcpTools");
|
|
749
|
-
|
|
750
|
-
// Eager path bypasses the catalog, so budget descriptions here too.
|
|
751
|
-
this.replaceOrPushTools(mcpTools.map((tool) => clampMcpToolDescription(tool, this.contextLimits)));
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
this.mcpCatalog ??= new DeferredToolCatalog(this.contextLimits);
|
|
755
|
-
// `add` is name-keyed, so live definitions replace cached stubs in place.
|
|
756
|
-
this.mcpCatalog.add(mcpTools);
|
|
757
|
-
// A stub the model already promoted lives in `this.tools`; swap it for the
|
|
758
|
-
// live tool so later calls dispatch directly instead of through the stub.
|
|
759
|
-
this.replaceLivePromotedTools(mcpTools);
|
|
760
|
-
this.ensureToolSearchTool();
|
|
675
|
+
this.mcp.addMcpTools(mcpTools, defer);
|
|
761
676
|
}
|
|
762
677
|
/**
|
|
763
|
-
*
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
678
|
+
* Deferred follow-up tools whose need is certain once a trigger succeeds.
|
|
679
|
+
* `wait_agent` follows nearly every `spawn_agent`, and loading it through
|
|
680
|
+
* `tool_search` cost a whole turn (bench 41: ~6 s per fan-out). Likewise a
|
|
681
|
+
* background `bash` is always followed by `task_output` (and sometimes
|
|
682
|
+
* `task_send`/`task_stop`). Promote them as soon as the trigger succeeds: the
|
|
683
|
+
* tool list grows exactly as it would after that `tool_search`, one turn
|
|
684
|
+
* earlier, and sessions that never trigger keep the smaller prefix.
|
|
685
|
+
* Call before tier partitioning so a deferred trigger is wrapped too.
|
|
770
686
|
*/
|
|
771
|
-
|
|
772
|
-
this.
|
|
773
|
-
|
|
774
|
-
return;
|
|
775
|
-
this.tools.push(createToolSearchTool(this.mcpCatalog, (promoted) => {
|
|
776
|
-
this.tools.push(...promoted);
|
|
777
|
-
}, async (toolName) => {
|
|
778
|
-
if (this.liveMcpTools.has(toolName))
|
|
779
|
-
return undefined;
|
|
780
|
-
const serverName = this.cachedMcpToolServers.get(toolName);
|
|
781
|
-
if (!serverName)
|
|
782
|
-
return undefined;
|
|
783
|
-
const outcome = (await this.mcpManager?.whenConnected(serverName)) ?? {
|
|
784
|
-
ok: false,
|
|
785
|
-
error: "MCP is disabled for this session",
|
|
786
|
-
};
|
|
787
|
-
return outcome.ok
|
|
788
|
-
? { serverName, ok: true }
|
|
789
|
-
: { serverName, ok: false, error: outcome.error };
|
|
790
|
-
}, this.contextLimits));
|
|
687
|
+
promoteFollowUpTools() {
|
|
688
|
+
this.wrapToPromote("spawn_agent", ["wait_agent"]);
|
|
689
|
+
this.wrapToPromote("bash", ["task_output", "task_send", "task_stop"], (args) => args.run_in_background === true);
|
|
791
690
|
}
|
|
792
|
-
/**
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
691
|
+
/** Append a deferred built-in to the live tool list (append-only). */
|
|
692
|
+
promoteDeferredBuiltin(name) {
|
|
693
|
+
if (!this.deferredBuiltinToolNames.includes(name))
|
|
694
|
+
return undefined;
|
|
695
|
+
const live = this.tools.find((t) => t.name === name);
|
|
696
|
+
if (live)
|
|
697
|
+
return live;
|
|
698
|
+
const [tool] = this.mcp.promote([name]);
|
|
699
|
+
if (tool)
|
|
700
|
+
this.tools.push(tool);
|
|
701
|
+
return tool;
|
|
702
|
+
}
|
|
703
|
+
wrapToPromote(trigger, followUps, when = () => true) {
|
|
704
|
+
const index = this.tools.findIndex((t) => t.name === trigger);
|
|
705
|
+
const tool = index >= 0 ? this.tools[index] : undefined;
|
|
706
|
+
if (!tool)
|
|
803
707
|
return;
|
|
804
708
|
this.tools[index] = {
|
|
805
|
-
...
|
|
709
|
+
...tool,
|
|
806
710
|
execute: async (args, context) => {
|
|
807
|
-
const result = await
|
|
808
|
-
if (
|
|
809
|
-
this.tools.
|
|
711
|
+
const result = await tool.execute(args, context);
|
|
712
|
+
if (when(args)) {
|
|
713
|
+
const missing = followUps.filter((name) => !this.tools.some((t) => t.name === name));
|
|
714
|
+
if (missing.length > 0)
|
|
715
|
+
this.tools.push(...this.mcp.promote(missing));
|
|
810
716
|
}
|
|
811
717
|
return result;
|
|
812
718
|
},
|
|
813
719
|
};
|
|
814
720
|
}
|
|
815
|
-
/** Append tools, replacing any same-named entry (cached stub → live tool). */
|
|
816
|
-
replaceOrPushTools(tools) {
|
|
817
|
-
for (const tool of tools) {
|
|
818
|
-
const index = this.tools.findIndex((t) => t.name === tool.name);
|
|
819
|
-
if (index >= 0)
|
|
820
|
-
this.tools[index] = tool;
|
|
821
|
-
else
|
|
822
|
-
this.tools.push(tool);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/** Swap already-promoted cached stubs for their live equivalents, in place. */
|
|
826
|
-
replaceLivePromotedTools(tools) {
|
|
827
|
-
for (const tool of tools) {
|
|
828
|
-
const index = this.tools.findIndex((t) => t.name === tool.name);
|
|
829
|
-
if (index >= 0)
|
|
830
|
-
this.tools[index] = tool;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
721
|
/**
|
|
834
722
|
* Publish cached tool definitions into the deferred catalog so `tool_search`
|
|
835
|
-
* answers correctly on turn 1
|
|
836
|
-
* description and input schema; calling it waits for the live connection and
|
|
837
|
-
* then dispatches against the real client, or returns a clear error when that
|
|
838
|
-
* server ultimately failed. Live tools replace stubs on connect.
|
|
723
|
+
* answers correctly on turn 1 (see {@link McpToolRegistry.seedFromCache}).
|
|
839
724
|
*/
|
|
840
725
|
async seedMcpCatalogFromCache(servers) {
|
|
841
726
|
if (!this.opts.backgroundMcpConnect)
|
|
842
727
|
return;
|
|
843
728
|
if (this.opts.allowedTools || !this.settingsManager.get("deferredMcpTools"))
|
|
844
729
|
return;
|
|
845
|
-
|
|
846
|
-
try {
|
|
847
|
-
entries = await this.mcpCatalogCache.entriesFor(servers);
|
|
848
|
-
}
|
|
849
|
-
catch {
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
const stubs = [];
|
|
853
|
-
for (const [serverName, entry] of entries) {
|
|
854
|
-
for (const cached of entry.tools) {
|
|
855
|
-
if (this.liveMcpTools.has(cached.name))
|
|
856
|
-
continue;
|
|
857
|
-
this.cachedMcpToolServers.set(cached.name, serverName);
|
|
858
|
-
stubs.push(this.buildCachedMcpTool(serverName, cached));
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
if (stubs.length === 0)
|
|
862
|
-
return;
|
|
863
|
-
log("INFO", "mcp", "Seeded deferred tool catalog from cache", {
|
|
864
|
-
tools: String(stubs.length),
|
|
865
|
-
servers: String(entries.size),
|
|
866
|
-
});
|
|
867
|
-
this.addCachedMcpTools(stubs);
|
|
868
|
-
}
|
|
869
|
-
/** Catalog-only registration for cached stubs — never marks them live. */
|
|
870
|
-
addCachedMcpTools(stubs) {
|
|
871
|
-
this.mcpCatalog ??= new DeferredToolCatalog(this.contextLimits);
|
|
872
|
-
this.mcpCatalog.add(stubs);
|
|
873
|
-
this.ensureToolSearchTool();
|
|
874
|
-
}
|
|
875
|
-
buildCachedMcpTool(serverName, cached) {
|
|
876
|
-
return {
|
|
877
|
-
name: cached.name,
|
|
878
|
-
description: cached.description,
|
|
879
|
-
parameters: z.record(z.string(), z.unknown()),
|
|
880
|
-
rawInputSchema: cached.rawInputSchema,
|
|
881
|
-
execute: async (args, context) => {
|
|
882
|
-
const live = this.liveMcpTools.get(cached.name);
|
|
883
|
-
if (live)
|
|
884
|
-
return live.execute(args, context);
|
|
885
|
-
const outcome = (await this.mcpManager?.whenConnected(serverName)) ?? {
|
|
886
|
-
ok: false,
|
|
887
|
-
error: "MCP is disabled for this session",
|
|
888
|
-
};
|
|
889
|
-
if (!outcome.ok) {
|
|
890
|
-
return (`MCP tool ${cached.name} is unavailable: server "${serverName}" did not connect ` +
|
|
891
|
-
`(${outcome.error}). This tool was offered from a cached catalog. ` +
|
|
892
|
-
`Use a different approach or ask the user to check their MCP configuration.`);
|
|
893
|
-
}
|
|
894
|
-
const connected = this.liveMcpTools.get(cached.name);
|
|
895
|
-
if (!connected) {
|
|
896
|
-
return (`MCP tool ${cached.name} no longer exists: server "${serverName}" connected but ` +
|
|
897
|
-
`does not expose it. The cached catalog entry was stale.`);
|
|
898
|
-
}
|
|
899
|
-
return connected.execute(args, context);
|
|
900
|
-
},
|
|
901
|
-
};
|
|
730
|
+
await this.mcp.seedFromCache(servers);
|
|
902
731
|
}
|
|
903
732
|
/**
|
|
904
733
|
* Resolve a `/name [args]` input to the prompt template it expands into, or
|
|
@@ -1085,33 +914,15 @@ export class AgentSession {
|
|
|
1085
914
|
resetHookState(originalRequest) {
|
|
1086
915
|
this.opts.completionReview?.begin(originalRequest);
|
|
1087
916
|
this.lspManager?.clearPendingDiagnostics();
|
|
1088
|
-
this.
|
|
1089
|
-
this.
|
|
1090
|
-
this.hookConsecutiveFailures = 0;
|
|
1091
|
-
this.hookRepeatedNoProgressCalls = 0;
|
|
1092
|
-
this.hookProgressTracker.reset();
|
|
1093
|
-
this.hookCycleDetector.reset();
|
|
1094
|
-
this.hookCyclicPattern = null;
|
|
1095
|
-
this.hookFileEditCounts.clear();
|
|
1096
|
-
this.hookToolCalls.clear();
|
|
1097
|
-
this.loopBreakInjected = 0;
|
|
917
|
+
this.loopMonitor.reset();
|
|
918
|
+
this.verificationTracker.resetRun();
|
|
1098
919
|
this.regroundingInjected = false;
|
|
1099
|
-
this.hookRecentCalls = [];
|
|
1100
|
-
this.semanticLoop = {
|
|
1101
|
-
checksUsed: 0,
|
|
1102
|
-
lastCheckTurn: 0,
|
|
1103
|
-
pending: false,
|
|
1104
|
-
verdict: null,
|
|
1105
|
-
injected: false,
|
|
1106
|
-
};
|
|
1107
920
|
this.runStartedAt = Date.now();
|
|
1108
921
|
this.processGateInjected = 0;
|
|
922
|
+
this.planSubmissionNudges = 0;
|
|
1109
923
|
this.verificationGate.beginRun();
|
|
1110
924
|
const processes = new Set(this.processManager?.list().map((p) => p.id) ?? []);
|
|
1111
|
-
|
|
1112
|
-
if (!processes.has(id))
|
|
1113
|
-
this.backgroundVerification.delete(id);
|
|
1114
|
-
}
|
|
925
|
+
this.verificationTracker.pruneBackground(processes);
|
|
1115
926
|
this.compactionOccurred = false;
|
|
1116
927
|
// Post-turn compaction may have landed between runs — adopt its armed
|
|
1117
928
|
// signal so the re-grounding hook fires for this run exactly as it would
|
|
@@ -1135,161 +946,20 @@ export class AgentSession {
|
|
|
1135
946
|
}
|
|
1136
947
|
switch (event.type) {
|
|
1137
948
|
case "text_delta":
|
|
1138
|
-
this.
|
|
949
|
+
this.loopMonitor.recordText(event.text);
|
|
1139
950
|
break;
|
|
1140
951
|
case "tool_call_start": {
|
|
1141
|
-
this.
|
|
1142
|
-
name: event.name,
|
|
1143
|
-
args: event.args ?? {},
|
|
1144
|
-
revision: this.verificationGate.revision,
|
|
1145
|
-
});
|
|
1146
|
-
const startClassification = event.name === "bash" && typeof event.args?.command === "string"
|
|
1147
|
-
? classifyVerificationCommand(event.args.command)
|
|
1148
|
-
: null;
|
|
1149
|
-
if (startClassification &&
|
|
1150
|
-
typeof event.args?.command === "string" &&
|
|
1151
|
-
(isVerificationCommand(event.args.command) ||
|
|
1152
|
-
startClassification.accepted ||
|
|
1153
|
-
// Must match the tool_call_end predicate: a snapshot-eligible
|
|
1154
|
-
// command that never captured a "before" snapshot is misread at
|
|
1155
|
-
// the end as an uncomparable workspace and re-arms the gate.
|
|
1156
|
-
startClassification.snapshotEligible === true)) {
|
|
1157
|
-
// A check that can rewrite files (--fix, build scripts, emitters)
|
|
1158
|
-
// invalidates earlier in-flight evidence AND marks the run as
|
|
1159
|
-
// touched. A check that is merely UNRECOGNIZED (`make test`, `deno
|
|
1160
|
-
// test`) rewrites nothing we can point to: bumping the revision for
|
|
1161
|
-
// it poisoned the gate on green output and re-armed the hook into
|
|
1162
|
-
// every later question turn.
|
|
1163
|
-
this.verificationGate.recordVerificationAttempt();
|
|
1164
|
-
const classification = startClassification;
|
|
1165
|
-
if (classification.snapshotEligible && event.args.persist !== true) {
|
|
1166
|
-
const call = this.hookToolCalls.get(event.toolCallId);
|
|
1167
|
-
call.sourceSnapshot = await captureVerificationSnapshot(this.opts.cwd, [
|
|
1168
|
-
...this.hookFileEditCounts.keys(),
|
|
1169
|
-
]);
|
|
1170
|
-
if (call.sourceSnapshot === null)
|
|
1171
|
-
this.verificationGate.requireFreshVerification(true, event.args.command);
|
|
1172
|
-
}
|
|
1173
|
-
else if ((classification.accepted && event.args.persist !== true) ||
|
|
1174
|
-
(!classification.accepted && classification.mayMutate)) {
|
|
1175
|
-
// Flag the workspace unknown only when tool_call_end can resolve
|
|
1176
|
-
// it: a bounded check records pass/fail, a file-rewriting command
|
|
1177
|
-
// bumps the revision. An unrecognized read-only check (`biome ci`)
|
|
1178
|
-
// or a persistent-shell run records nothing at the end, so
|
|
1179
|
-
// flagging it left verified work Unverified forever — and
|
|
1180
|
-
// autopilot silently refused every later turn.
|
|
1181
|
-
this.verificationGate.requireFreshVerification(!classification.accepted && classification.mayMutate, event.args.command);
|
|
1182
|
-
}
|
|
952
|
+
if (await this.verificationTracker.recordToolCallStart(event)) {
|
|
1183
953
|
await this.persistVerificationState();
|
|
1184
954
|
}
|
|
1185
955
|
break;
|
|
1186
956
|
}
|
|
1187
957
|
case "tool_call_end": {
|
|
1188
|
-
const call = this.
|
|
958
|
+
const call = this.verificationTracker.getToolCall(event.toolCallId);
|
|
1189
959
|
const name = call?.name ?? "";
|
|
1190
960
|
const args = call?.args;
|
|
1191
|
-
this.
|
|
1192
|
-
|
|
1193
|
-
this.hookStats.toolFailures += 1;
|
|
1194
|
-
this.hookConsecutiveFailures = event.isError ? this.hookConsecutiveFailures + 1 : 0;
|
|
1195
|
-
this.hookRepeatedNoProgressCalls = this.hookProgressTracker.record(name, args, event.result, event.isError);
|
|
1196
|
-
this.hookCyclicPattern = this.hookCycleDetector.record(name, args, event.result, event.isError);
|
|
1197
|
-
// Semantic-loop judge input: a bounded digest of WHAT was attempted and
|
|
1198
|
-
// HOW it came out. Args/results are sliced AT RECORD TIME — a write with
|
|
1199
|
-
// a 50 KiB payload or a bash dump must never inflate the ring, and the
|
|
1200
|
-
// judge needs shapes, not payloads.
|
|
1201
|
-
this.hookRecentCalls.push({
|
|
1202
|
-
tool: name,
|
|
1203
|
-
args: args === undefined ? "" : JSON.stringify(args).slice(0, 300),
|
|
1204
|
-
ok: !event.isError,
|
|
1205
|
-
result: event.result.slice(0, 400),
|
|
1206
|
-
});
|
|
1207
|
-
if (this.hookRecentCalls.length > MAX_SEMANTIC_LOOP_CALLS) {
|
|
1208
|
-
this.hookRecentCalls.splice(0, this.hookRecentCalls.length - MAX_SEMANTIC_LOOP_CALLS);
|
|
1209
|
-
}
|
|
1210
|
-
// Only host-observed successful mutations and trustworthy check results
|
|
1211
|
-
// affect approval. The model's text is never evidence.
|
|
1212
|
-
let verificationChanged = false;
|
|
1213
|
-
if (!event.isError && args) {
|
|
1214
|
-
if (name === "edit" || name === "write") {
|
|
1215
|
-
// Check-owning files (tsconfig.json, pytest.ini, vitest.config.ts …)
|
|
1216
|
-
// are tracked even when they are not source code: editing one is how
|
|
1217
|
-
// a red suite is turned green without fixing anything. A multi-file
|
|
1218
|
-
// edit records every file; its combined diff is scanned for each.
|
|
1219
|
-
const addedText = name === "write"
|
|
1220
|
-
? String(args.content ?? "")
|
|
1221
|
-
: extractAddedLines(event.details?.diff ?? event.result);
|
|
1222
|
-
for (const filePath of editTargetPaths(args)) {
|
|
1223
|
-
if (isCodeFilePath(filePath) || isCheckOwnFile(filePath)) {
|
|
1224
|
-
this.verificationGate.recordMutation(filePath, addedText);
|
|
1225
|
-
verificationChanged = true;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
if (args && call && name === "bash") {
|
|
1231
|
-
const command = typeof args.command === "string" ? args.command : "";
|
|
1232
|
-
const classification = classifyVerificationCommand(command);
|
|
1233
|
-
if (args.review === true && event.result === REVIEW_REJECTED_BEFORE_START) {
|
|
1234
|
-
// The core tool rejected its arguments BEFORE spawning. Never mint
|
|
1235
|
-
// a pass, or poison the failed-check ledger with an unexecuted check.
|
|
1236
|
-
// A child's output cannot match: bash always prefixes it with Exit code.
|
|
1237
|
-
this.verificationGate.recordRejectedCheck(command, REVIEW_REJECTED_BEFORE_START);
|
|
1238
|
-
delete call.sourceSnapshot;
|
|
1239
|
-
verificationChanged = true;
|
|
1240
|
-
}
|
|
1241
|
-
else if (classification.accepted || classification.snapshotEligible) {
|
|
1242
|
-
// A foreground check that outlived the default budget was moved to
|
|
1243
|
-
// the background, not failed: track it to its real exit the same way.
|
|
1244
|
-
const autoBackgroundId = event.isError ? undefined : autoBackgroundedId(event.result);
|
|
1245
|
-
if ((autoBackgroundId !== undefined || args.run_in_background === true) &&
|
|
1246
|
-
!event.isError &&
|
|
1247
|
-
args.persist !== true) {
|
|
1248
|
-
const id = autoBackgroundId ?? /^ID:\s*(\S+)/m.exec(event.result)?.[1];
|
|
1249
|
-
// No parseable ID means the check cannot be tracked to a real exit
|
|
1250
|
-
// code — no evidence either way. Recording a FAILURE here made
|
|
1251
|
-
// every later green run of a different spelling look owed.
|
|
1252
|
-
if (id)
|
|
1253
|
-
this.backgroundVerification.set(id, {
|
|
1254
|
-
revision: call.revision,
|
|
1255
|
-
command,
|
|
1256
|
-
...(classification.snapshotEligible
|
|
1257
|
-
? { sourceSnapshot: call.sourceSnapshot ?? null }
|
|
1258
|
-
: {}),
|
|
1259
|
-
});
|
|
1260
|
-
else if (classification.snapshotEligible)
|
|
1261
|
-
this.verificationGate.requireFreshVerification(true, command);
|
|
1262
|
-
}
|
|
1263
|
-
else if (args.persist === true) {
|
|
1264
|
-
// Persistent-shell checks are not bounded evidence (steering can
|
|
1265
|
-
// interleave): neither a pass nor a failure. A recorded failure
|
|
1266
|
-
// here blocked approval for sessions that prefer the shell.
|
|
1267
|
-
}
|
|
1268
|
-
else if (classification.snapshotEligible) {
|
|
1269
|
-
await this.finishSnapshotVerification({ command, revision: call.revision, sourceSnapshot: call.sourceSnapshot ?? null }, !event.isError && /^Exit code:\s*0(?:\s|$)/i.test(event.result.trim()));
|
|
1270
|
-
verificationChanged = true;
|
|
1271
|
-
}
|
|
1272
|
-
else {
|
|
1273
|
-
if (!event.isError && /^Exit code:\s*0(?:\s|$)/i.test(event.result.trim())) {
|
|
1274
|
-
this.verificationGate.recordVerification(call.revision, command);
|
|
1275
|
-
}
|
|
1276
|
-
else {
|
|
1277
|
-
this.verificationGate.recordFailedVerification(command, call.revision);
|
|
1278
|
-
}
|
|
1279
|
-
verificationChanged = true;
|
|
1280
|
-
}
|
|
1281
|
-
delete call.sourceSnapshot;
|
|
1282
|
-
}
|
|
1283
|
-
else if (classification.candidate) {
|
|
1284
|
-
// Green but untrusted: remember WHY so the demand can tell the
|
|
1285
|
-
// agent which command shape actually clears the gate.
|
|
1286
|
-
this.verificationGate.recordRejectedCheck(command, classification.reason);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
if (!event.isError && args && name === "task_output" && typeof args.id === "string") {
|
|
1290
|
-
verificationChanged =
|
|
1291
|
-
(await this.recordFinishedBackgroundVerification(args.id)) || verificationChanged;
|
|
1292
|
-
}
|
|
961
|
+
this.loopMonitor.recordToolEnd(name, args, event.result, event.isError);
|
|
962
|
+
const verificationChanged = await this.verificationTracker.recordToolCallEnd(event, call, this.planModeRef.current);
|
|
1293
963
|
if (verificationChanged)
|
|
1294
964
|
await this.persistVerificationState();
|
|
1295
965
|
// Tool results are what push the run over the review gate, and they all
|
|
@@ -1299,7 +969,7 @@ export class AgentSession {
|
|
|
1299
969
|
break;
|
|
1300
970
|
}
|
|
1301
971
|
case "turn_end":
|
|
1302
|
-
this.
|
|
972
|
+
this.loopMonitor.recordTurn(event.turn);
|
|
1303
973
|
this.refreshHookArming();
|
|
1304
974
|
for (let index = this.messages.length - 1; index >= 0; index--) {
|
|
1305
975
|
const anchor = this.messages[index];
|
|
@@ -1476,51 +1146,26 @@ export class AgentSession {
|
|
|
1476
1146
|
// Deterministic stuck verdict, computed once and shared: the semantic
|
|
1477
1147
|
// judge must not spend tokens on a burst the deterministic breaker is
|
|
1478
1148
|
// about to correct itself.
|
|
1479
|
-
const deterministicDecision = evaluateLoopBreak(
|
|
1480
|
-
consecutiveFailures: this.hookConsecutiveFailures,
|
|
1481
|
-
repeatedNoProgressCalls: this.hookRepeatedNoProgressCalls,
|
|
1482
|
-
textRepetitionDetected: detectTextRepetition(this.hookText),
|
|
1483
|
-
...(this.hookCyclicPattern ? { cyclicPattern: this.hookCyclicPattern } : {}),
|
|
1484
|
-
});
|
|
1149
|
+
const deterministicDecision = this.loopMonitor.evaluateLoopBreak();
|
|
1485
1150
|
this.maybeStartSemanticLoopCheck(deterministicDecision.shouldBreak);
|
|
1486
1151
|
// Two-stage loop-breaker: stage 1 nudges; a FRESH detection after that
|
|
1487
1152
|
// injects the harsher final stop-and-report prompt. Signals reset after
|
|
1488
1153
|
// each injection so stage 2 only fires on new evidence.
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
this.hookConsecutiveFailures = 0;
|
|
1498
|
-
this.hookRepeatedNoProgressCalls = 0;
|
|
1499
|
-
// The deterministic breaker owns this burst — a semantic verdict from
|
|
1500
|
-
// the same burst must not double-correct on the next poll.
|
|
1501
|
-
this.semanticLoop.verdict = null;
|
|
1502
|
-
// Clear the text buffer too — otherwise a stage-1 text-repetition
|
|
1503
|
-
// trigger still sees the same repeated tail on the next check and
|
|
1504
|
-
// escalates to stage 2 on stale evidence.
|
|
1505
|
-
this.hookText = "";
|
|
1506
|
-
log("INFO", "loop-break", "Injecting loop-break nudge", {
|
|
1507
|
-
stage: String(stage),
|
|
1508
|
-
reasons: decision.reasons.join(", "),
|
|
1509
|
-
});
|
|
1510
|
-
this.eventBus.emit("hook", { kind: "loop_break" });
|
|
1511
|
-
return [buildLoopBreakMessage(decision.reasons, stage === 2)];
|
|
1512
|
-
}
|
|
1154
|
+
const stage = this.loopMonitor.takeLoopBreakStage(deterministicDecision);
|
|
1155
|
+
if (stage !== null) {
|
|
1156
|
+
log("INFO", "loop-break", "Injecting loop-break nudge", {
|
|
1157
|
+
stage: String(stage),
|
|
1158
|
+
reasons: deterministicDecision.reasons.join(", "),
|
|
1159
|
+
});
|
|
1160
|
+
this.eventBus.emit("hook", { kind: "loop_break" });
|
|
1161
|
+
return [buildLoopBreakMessage(deterministicDecision.reasons, stage === 2)];
|
|
1513
1162
|
}
|
|
1514
1163
|
// Semantic loop-break: an LLM verdict (started by maybeStartSemanticLoopCheck
|
|
1515
1164
|
// on a suspicious-but-syntactically-quiet burst) is consumed here, exactly
|
|
1516
1165
|
// once, with the deterministic breaker getting priority above. Fail-open:
|
|
1517
1166
|
// no verdict or no-loop verdict injects nothing.
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
this.semanticLoop.injected = true;
|
|
1521
|
-
this.semanticLoop.verdict = null;
|
|
1522
|
-
// The judged burst has been addressed; a fresh burst must re-accumulate.
|
|
1523
|
-
this.hookConsecutiveFailures = 0;
|
|
1167
|
+
const verdict = this.loopMonitor.takeSemanticVerdict();
|
|
1168
|
+
if (verdict) {
|
|
1524
1169
|
log("INFO", "loop-break", "Injecting semantic loop-break steering", {
|
|
1525
1170
|
reason: verdict.reason,
|
|
1526
1171
|
});
|
|
@@ -1540,34 +1185,26 @@ export class AgentSession {
|
|
|
1540
1185
|
* Fire-and-forget: the call runs while the next turn streams, and a finished
|
|
1541
1186
|
* verdict is consumed by the NEXT steering poll — never blocking a turn. */
|
|
1542
1187
|
maybeStartSemanticLoopCheck(deterministicBreak) {
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
totalFailures: this.hookStats.toolFailures,
|
|
1546
|
-
turns: this.hookStats.turns,
|
|
1547
|
-
lastCheckTurn: this.semanticLoop.lastCheckTurn,
|
|
1548
|
-
checksUsed: this.semanticLoop.checksUsed,
|
|
1549
|
-
checkPending: this.semanticLoop.pending,
|
|
1550
|
-
deterministicBreak,
|
|
1551
|
-
})) {
|
|
1188
|
+
const monitor = this.loopMonitor;
|
|
1189
|
+
if (!monitor.shouldRunSemanticCheck(deterministicBreak)) {
|
|
1552
1190
|
return;
|
|
1553
1191
|
}
|
|
1554
|
-
// resetHookState replaces
|
|
1192
|
+
// resetHookState replaces the run state on every prompt. A late judge must
|
|
1555
1193
|
// not publish a verdict or consume the next run's budget/cooldown.
|
|
1556
|
-
const runState =
|
|
1557
|
-
runState.pending = true;
|
|
1194
|
+
const runState = monitor.beginSemanticCheck();
|
|
1558
1195
|
log("INFO", "loop-break", "Starting semantic loop judge", {
|
|
1559
|
-
turn: String(
|
|
1560
|
-
consecutiveFailures: String(
|
|
1561
|
-
recentCalls: String(
|
|
1196
|
+
turn: String(monitor.stats.turns),
|
|
1197
|
+
consecutiveFailures: String(monitor.consecutiveFailures),
|
|
1198
|
+
recentCalls: String(monitor.recentCalls.length),
|
|
1562
1199
|
});
|
|
1563
1200
|
void (async () => {
|
|
1564
1201
|
try {
|
|
1565
|
-
const prompt = buildSemanticLoopJudgePrompt(
|
|
1202
|
+
const prompt = buildSemanticLoopJudgePrompt(monitor.recentCalls, this.originalRequest);
|
|
1566
1203
|
const raw = await (this.opts.semanticLoopJudge?.(prompt) ??
|
|
1567
1204
|
this.callSemanticLoopJudge(prompt));
|
|
1568
1205
|
const verdict = parseSemanticLoopVerdict(raw);
|
|
1569
|
-
if (
|
|
1570
|
-
runState
|
|
1206
|
+
if (verdict?.loop)
|
|
1207
|
+
monitor.publishSemanticVerdict(runState, verdict);
|
|
1571
1208
|
}
|
|
1572
1209
|
catch (error) {
|
|
1573
1210
|
// Fail open: judge errors never stop a run. Budget and cooldown are
|
|
@@ -1577,11 +1214,7 @@ export class AgentSession {
|
|
|
1577
1214
|
});
|
|
1578
1215
|
}
|
|
1579
1216
|
finally {
|
|
1580
|
-
|
|
1581
|
-
runState.pending = false;
|
|
1582
|
-
runState.checksUsed += 1;
|
|
1583
|
-
runState.lastCheckTurn = this.hookStats.turns;
|
|
1584
|
-
}
|
|
1217
|
+
monitor.settleSemanticCheck(runState);
|
|
1585
1218
|
}
|
|
1586
1219
|
})();
|
|
1587
1220
|
}
|
|
@@ -1677,23 +1310,7 @@ export class AgentSession {
|
|
|
1677
1310
|
* with — so reuse the same stuck signals that drive the loop-breaker.
|
|
1678
1311
|
*/
|
|
1679
1312
|
shouldExtendTurnBudget(ctx) {
|
|
1680
|
-
const refusals =
|
|
1681
|
-
const stuck = evaluateLoopBreak({
|
|
1682
|
-
consecutiveFailures: this.hookConsecutiveFailures,
|
|
1683
|
-
repeatedNoProgressCalls: this.hookRepeatedNoProgressCalls,
|
|
1684
|
-
textRepetitionDetected: detectTextRepetition(this.hookText),
|
|
1685
|
-
...(this.hookCyclicPattern ? { cyclicPattern: this.hookCyclicPattern } : {}),
|
|
1686
|
-
});
|
|
1687
|
-
if (stuck.shouldBreak)
|
|
1688
|
-
refusals.push(...stuck.reasons);
|
|
1689
|
-
// Stage 2 means the loop-breaker already detected spinning twice and told
|
|
1690
|
-
// the agent to stop and report. Do not overrule that with more turns.
|
|
1691
|
-
if (this.loopBreakInjected >= 2)
|
|
1692
|
-
refusals.push("loop-breaker already escalated");
|
|
1693
|
-
const { toolCalls, toolFailures } = this.hookStats;
|
|
1694
|
-
if (toolCalls > 0 && toolFailures / toolCalls > TURN_EXTENSION_MAX_FAILURE_RATIO) {
|
|
1695
|
-
refusals.push(`${toolFailures}/${toolCalls} tool calls failed`);
|
|
1696
|
-
}
|
|
1313
|
+
const refusals = this.loopMonitor.turnExtensionRefusals();
|
|
1697
1314
|
const granted = refusals.length === 0;
|
|
1698
1315
|
log("INFO", "turn-budget", granted ? "Extending turn budget" : "Refusing turn-budget extension", {
|
|
1699
1316
|
turn: String(ctx.turn),
|
|
@@ -1746,11 +1363,7 @@ export class AgentSession {
|
|
|
1746
1363
|
async getHookFollowUpMessages() {
|
|
1747
1364
|
// Exit notifications and task_output refer to the same host process record.
|
|
1748
1365
|
// Do not force an extra polling tool/turn merely to acknowledge a known exit.
|
|
1749
|
-
|
|
1750
|
-
for (const id of this.backgroundVerification.keys()) {
|
|
1751
|
-
backgroundChanged =
|
|
1752
|
-
(await this.recordFinishedBackgroundVerification(id)) || backgroundChanged;
|
|
1753
|
-
}
|
|
1366
|
+
const backgroundChanged = await this.verificationTracker.settleFinishedBackground();
|
|
1754
1367
|
if (backgroundChanged)
|
|
1755
1368
|
await this.persistVerificationState();
|
|
1756
1369
|
// Edits return immediately; only the completion boundary waits for remaining
|
|
@@ -1826,6 +1439,22 @@ export class AgentSession {
|
|
|
1826
1439
|
// verification demand above instead of manufacturing a separate hook.
|
|
1827
1440
|
if (diagnosticMessages.length > 0)
|
|
1828
1441
|
return diagnosticMessages;
|
|
1442
|
+
if (shouldNudgePlanSubmission({
|
|
1443
|
+
enabled: this.opts.planSubmissionGate === true,
|
|
1444
|
+
planMode: this.planModeRef.current,
|
|
1445
|
+
aborted: this.opts.signal?.aborted === true,
|
|
1446
|
+
nudgesThisRun: this.planSubmissionNudges,
|
|
1447
|
+
})) {
|
|
1448
|
+
this.planSubmissionNudges += 1;
|
|
1449
|
+
log("INFO", "plan-gate", "Run stopping in plan mode without a submitted plan; reminding");
|
|
1450
|
+
return [
|
|
1451
|
+
{
|
|
1452
|
+
role: "user",
|
|
1453
|
+
content: buildNotificationSteeringText([PLAN_SUBMISSION_NUDGE]),
|
|
1454
|
+
provenance: { source: "runtime", kind: "notification", visibility: "hidden" },
|
|
1455
|
+
},
|
|
1456
|
+
];
|
|
1457
|
+
}
|
|
1829
1458
|
if (this.opts.completionReview) {
|
|
1830
1459
|
const followUp = await this.opts.completionReview.followUp((request, signal) => this.callCompletionReviewer(request, signal), this.opts.signal);
|
|
1831
1460
|
await this.persistCompletionReviewState();
|
|
@@ -2052,6 +1681,11 @@ export class AgentSession {
|
|
|
2052
1681
|
maxTurnToolResultChars: resolveSessionTurnToolResultCharLimit(this.model, this.provider, accountId),
|
|
2053
1682
|
// Warn when web/MCP output contains instruction-like text (see injection-detect.ts).
|
|
2054
1683
|
transformToolResult: flagUntrustedToolResult,
|
|
1684
|
+
// A deferred built-in called by its advertised name runs directly
|
|
1685
|
+
// instead of costing a turn on "Unknown tool" then tool_search.
|
|
1686
|
+
...(options.disableTools
|
|
1687
|
+
? {}
|
|
1688
|
+
: { resolveTool: (name) => this.promoteDeferredBuiltin(name) }),
|
|
2055
1689
|
// Self-correction hooks (same as the TUI): loop-break + re-grounding are
|
|
2056
1690
|
// polled mid-loop; the ideal review is polled when the agent would stop.
|
|
2057
1691
|
getSteeringMessages: () => this.getHookSteeringMessages(),
|
|
@@ -2385,11 +2019,12 @@ export class AgentSession {
|
|
|
2385
2019
|
// there avoids tearing down a live stdio child and
|
|
2386
2020
|
// gambling on a `npx` re-spawn that could fail and drop the tools.
|
|
2387
2021
|
const glmInvolved = this.provider === "glm" || prevProvider === "glm";
|
|
2388
|
-
|
|
2022
|
+
const mcpManager = this.mcp.manager;
|
|
2023
|
+
if (mcpManager && glmInvolved) {
|
|
2389
2024
|
// Remove old MCP tools
|
|
2390
2025
|
this.tools = this.tools.filter((t) => !t.name.startsWith("mcp__"));
|
|
2391
2026
|
// Disconnect old MCP servers
|
|
2392
|
-
await
|
|
2027
|
+
await mcpManager.dispose();
|
|
2393
2028
|
// Connect new MCP servers for the new provider
|
|
2394
2029
|
try {
|
|
2395
2030
|
let apiKey;
|
|
@@ -2406,13 +2041,11 @@ export class AgentSession {
|
|
|
2406
2041
|
const servers = await getAllMcpServers(this.provider, apiKey, this.cwd, {
|
|
2407
2042
|
allowProjectScope: this.settingsManager.isProjectTrusted(this.cwd),
|
|
2408
2043
|
});
|
|
2409
|
-
const mcpTools = await
|
|
2044
|
+
const mcpTools = await mcpManager.connectAll(servers);
|
|
2410
2045
|
// Drop stale MCP tools from both the live set and deferred catalog before
|
|
2411
2046
|
// re-adding. Some tools may already have been promoted out of the catalog.
|
|
2412
2047
|
this.tools = this.tools.filter((t) => !t.name.startsWith("mcp__"));
|
|
2413
|
-
this.
|
|
2414
|
-
this.liveMcpTools.clear();
|
|
2415
|
-
this.cachedMcpToolServers.clear();
|
|
2048
|
+
this.mcp.clearMcpTools();
|
|
2416
2049
|
this.addMcpTools(mcpTools);
|
|
2417
2050
|
}
|
|
2418
2051
|
catch (err) {
|
|
@@ -2455,10 +2088,7 @@ export class AgentSession {
|
|
|
2455
2088
|
this.checkpointGeneration = loaded.header.generation ?? 0;
|
|
2456
2089
|
this.currentLeafId = loaded.header.leafId;
|
|
2457
2090
|
this.setSessionPath(loaded.path);
|
|
2458
|
-
this.
|
|
2459
|
-
this.autopilotMarkers = this.sessionManager.getAutopilotMarkers(loaded.entries, loaded.header.leafId);
|
|
2460
|
-
this.appMarkers = this.sessionManager.getAppMarkers(loaded.entries, loaded.header.leafId);
|
|
2461
|
-
this.turnMetrics = this.sessionManager.getTurnMetrics(loaded.entries);
|
|
2091
|
+
this.markers.restore(loaded.entries, loaded.header.leafId);
|
|
2462
2092
|
this.lastPersistedIndex = this.messages.length;
|
|
2463
2093
|
this.providerContext = null;
|
|
2464
2094
|
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
@@ -2498,10 +2128,7 @@ export class AgentSession {
|
|
|
2498
2128
|
await this.persistMessage(message);
|
|
2499
2129
|
}
|
|
2500
2130
|
this.lastPersistedIndex = this.messages.length;
|
|
2501
|
-
await this.
|
|
2502
|
-
await this.rePersistNolanTurns();
|
|
2503
|
-
await this.rePersistAutopilotMarkers();
|
|
2504
|
-
await this.rePersistAppMarkers();
|
|
2131
|
+
await this.markers.rePersistAll();
|
|
2505
2132
|
await this.persistVerificationState();
|
|
2506
2133
|
await this.persistAppMarker("compaction", {
|
|
2507
2134
|
originalCount: result.originalCount,
|
|
@@ -2860,12 +2487,17 @@ export class AgentSession {
|
|
|
2860
2487
|
});
|
|
2861
2488
|
}
|
|
2862
2489
|
async newSession(preserveConversation = false) {
|
|
2490
|
+
// A background post-turn compaction replaces `this.messages` when it
|
|
2491
|
+
// lands. Letting it finish after the reset would swap the old history back
|
|
2492
|
+
// into the fresh session (e.g. an accepted plan's build would inherit the
|
|
2493
|
+
// whole planning conversation and lose the approved-plan prompt).
|
|
2494
|
+
await this.settlePostTurnCompaction();
|
|
2863
2495
|
this.cacheDiagnostics.reset();
|
|
2864
2496
|
// Approved-plan execution is a clean checkpoint of the same conversation;
|
|
2865
2497
|
// explicit new sessions reset the conversation identity.
|
|
2866
2498
|
if (!preserveConversation) {
|
|
2867
2499
|
this.verificationGate.reset();
|
|
2868
|
-
this.
|
|
2500
|
+
this.verificationTracker.clearBackground();
|
|
2869
2501
|
this.conversationId = "";
|
|
2870
2502
|
this.checkpointGeneration = 0;
|
|
2871
2503
|
this.sessionPreview = "";
|
|
@@ -2877,10 +2509,7 @@ export class AgentSession {
|
|
|
2877
2509
|
// turns / autopilot verdicts / app markers linger in memory, show up in the
|
|
2878
2510
|
// new session's /history, and get re-persisted into the new file by the
|
|
2879
2511
|
// next compaction — the cross-session duplicate-marker propagation bug.
|
|
2880
|
-
this.
|
|
2881
|
-
this.autopilotMarkers = [];
|
|
2882
|
-
this.appMarkers = [];
|
|
2883
|
-
this.turnMetrics = [];
|
|
2512
|
+
this.markers.clear();
|
|
2884
2513
|
const basePrompt = await this.buildBasePrompt(false, undefined);
|
|
2885
2514
|
this.baseSystemPrompt = basePrompt;
|
|
2886
2515
|
this.messages = [{ role: "system", content: this.withSystemPromptTail(basePrompt) }];
|
|
@@ -3002,9 +2631,7 @@ export class AgentSession {
|
|
|
3002
2631
|
else {
|
|
3003
2632
|
used = calculateActiveContextTokens(this.messages);
|
|
3004
2633
|
}
|
|
3005
|
-
const costUsd = this.
|
|
3006
|
-
? this.turnMetrics.reduce((sum, m) => sum + (m.cost.status === "known" ? m.cost.usd : 0), 0)
|
|
3007
|
-
: undefined;
|
|
2634
|
+
const costUsd = this.markers.knownCostUsd();
|
|
3008
2635
|
return costUsd === undefined ? { used, size } : { used, size, costUsd };
|
|
3009
2636
|
}
|
|
3010
2637
|
/**
|
|
@@ -3128,6 +2755,11 @@ export class AgentSession {
|
|
|
3128
2755
|
*/
|
|
3129
2756
|
async setPlanMode(active) {
|
|
3130
2757
|
this.planModeRef.current = active;
|
|
2758
|
+
// Plan mode always ends with `exit_plan`; a deferred one must be callable
|
|
2759
|
+
// without a tool_search turn. Append-only, like any promotion.
|
|
2760
|
+
if (active && !this.tools.some((t) => t.name === "exit_plan")) {
|
|
2761
|
+
this.tools.push(...this.mcp.promote(["exit_plan"]));
|
|
2762
|
+
}
|
|
3131
2763
|
// Entering plan mode discards any prior approved-plan contract (a new plan
|
|
3132
2764
|
// is about to be drafted); exiting keeps it (set explicitly via accept).
|
|
3133
2765
|
if (active)
|
|
@@ -3331,7 +2963,10 @@ export class AgentSession {
|
|
|
3331
2963
|
orchestrationPolicyTail() {
|
|
3332
2964
|
if (this.opts.orchestrationPrompt === false)
|
|
3333
2965
|
return "";
|
|
3334
|
-
return applyAsyncSubagentPolicy("", this.provider, this.model, this.thinkingLevel,
|
|
2966
|
+
return applyAsyncSubagentPolicy("", this.provider, this.model, this.thinkingLevel,
|
|
2967
|
+
// Deferred spawn_agent/wait_agent are one call away (resolveTool loads
|
|
2968
|
+
// them on first use), so the policy must still see them.
|
|
2969
|
+
[...this.tools.map((tool) => tool.name), ...this.deferredBuiltinToolNames]).trim();
|
|
3335
2970
|
}
|
|
3336
2971
|
/**
|
|
3337
2972
|
* Re-render the uncached tail of messages[0] in place. The cached prefix is
|
|
@@ -3353,12 +2988,7 @@ export class AgentSession {
|
|
|
3353
2988
|
return this.messages;
|
|
3354
2989
|
}
|
|
3355
2990
|
getTurnMetrics() {
|
|
3356
|
-
return this.
|
|
3357
|
-
...metric,
|
|
3358
|
-
usage: { ...metric.usage },
|
|
3359
|
-
timing: { ...metric.timing },
|
|
3360
|
-
cost: { ...metric.cost },
|
|
3361
|
-
}));
|
|
2991
|
+
return this.markers.getTurnMetrics();
|
|
3362
2992
|
}
|
|
3363
2993
|
async persistTurnMetric(event) {
|
|
3364
2994
|
if (event.stopReason === "error")
|
|
@@ -3385,31 +3015,23 @@ export class AgentSession {
|
|
|
3385
3015
|
reason: "No authoritative effective-dated provider pricing is available",
|
|
3386
3016
|
},
|
|
3387
3017
|
};
|
|
3388
|
-
this.
|
|
3018
|
+
this.markers.addTurnMetric(payload);
|
|
3389
3019
|
// Internal diagnostics piggyback on the authoritative per-turn metric —
|
|
3390
3020
|
// one source of truth, and the record flushes to disk every turn.
|
|
3391
3021
|
this.diagnosticsRecorder?.recordTurnMetric(payload);
|
|
3392
|
-
|
|
3393
|
-
await this.sessionManager.appendTurnMetric(this.sessionPath, payload);
|
|
3394
|
-
}
|
|
3395
|
-
async rePersistTurnMetrics() {
|
|
3396
|
-
if (!this.sessionPath)
|
|
3397
|
-
return;
|
|
3398
|
-
for (const metric of this.turnMetrics) {
|
|
3399
|
-
await this.sessionManager.appendTurnMetric(this.sessionPath, metric);
|
|
3400
|
-
}
|
|
3022
|
+
await this.markers.persistTurnMetric(payload);
|
|
3401
3023
|
}
|
|
3402
3024
|
/** Nolan Grout (mentor) turns recorded against this session, in record order. Used
|
|
3403
3025
|
* by the host to interleave Nolan's advisory exchanges back into the transcript
|
|
3404
3026
|
* on resume. Never part of the LLM message history. */
|
|
3405
3027
|
getNolanTurns() {
|
|
3406
|
-
return this.
|
|
3028
|
+
return this.markers.getNolanTurns();
|
|
3407
3029
|
}
|
|
3408
3030
|
/** Autopilot verdict markers recorded against this session, in record order.
|
|
3409
3031
|
* Used by the host to interleave the auto-review loop's markers back into
|
|
3410
3032
|
* the transcript on resume, mirroring `getNolanTurns`. */
|
|
3411
3033
|
getAutopilotMarkers() {
|
|
3412
|
-
return this.
|
|
3034
|
+
return this.markers.getAutopilotMarkers();
|
|
3413
3035
|
}
|
|
3414
3036
|
/** Non-system messages that are actually on disk. Transcript markers anchor
|
|
3415
3037
|
* against this (not the in-memory list, which can run ahead after a failed
|
|
@@ -3424,15 +3046,7 @@ export class AgentSession {
|
|
|
3424
3046
|
* file, so the new file carries positions that match its own transcript.
|
|
3425
3047
|
*/
|
|
3426
3048
|
remapMarkerAnchors(remap) {
|
|
3427
|
-
|
|
3428
|
-
return;
|
|
3429
|
-
const move = (payload) => ({
|
|
3430
|
-
...payload,
|
|
3431
|
-
afterMessageCount: remapAnchorForCompaction(payload.afterMessageCount, remap),
|
|
3432
|
-
});
|
|
3433
|
-
this.nolanTurns = this.nolanTurns.map(move);
|
|
3434
|
-
this.autopilotMarkers = this.autopilotMarkers.map(move);
|
|
3435
|
-
this.appMarkers = this.appMarkers.map(move);
|
|
3049
|
+
this.markers.remapAnchors(remap);
|
|
3436
3050
|
}
|
|
3437
3051
|
/**
|
|
3438
3052
|
* Record one Nolan Grout (mentor agent) turn against this build session: the
|
|
@@ -3445,39 +3059,7 @@ export class AgentSession {
|
|
|
3445
3059
|
* appendEntry's own handling.
|
|
3446
3060
|
*/
|
|
3447
3061
|
async persistNolanTurn(question, reply) {
|
|
3448
|
-
|
|
3449
|
-
const payload = { version: 1, question, reply, afterMessageCount };
|
|
3450
|
-
this.nolanTurns.push(payload);
|
|
3451
|
-
if (!this.sessionPath)
|
|
3452
|
-
return;
|
|
3453
|
-
const entry = {
|
|
3454
|
-
type: "custom",
|
|
3455
|
-
kind: NOLAN_TURN_CUSTOM_KIND,
|
|
3456
|
-
id: crypto.randomUUID(),
|
|
3457
|
-
parentId: null,
|
|
3458
|
-
timestamp: new Date().toISOString(),
|
|
3459
|
-
data: payload,
|
|
3460
|
-
};
|
|
3461
|
-
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
3462
|
-
}
|
|
3463
|
-
/** Re-append the in-memory Nolan turns to the current session file. Called after
|
|
3464
|
-
* a continuation/compaction file is created so Nolan's advisory history isn't
|
|
3465
|
-
* lost when the session is rewritten (those rewrites only re-persist
|
|
3466
|
-
* messages). Each turn keeps its original `afterMessageCount` anchor. */
|
|
3467
|
-
async rePersistNolanTurns() {
|
|
3468
|
-
if (!this.sessionPath)
|
|
3469
|
-
return;
|
|
3470
|
-
for (const payload of this.nolanTurns) {
|
|
3471
|
-
const entry = {
|
|
3472
|
-
type: "custom",
|
|
3473
|
-
kind: NOLAN_TURN_CUSTOM_KIND,
|
|
3474
|
-
id: crypto.randomUUID(),
|
|
3475
|
-
parentId: null,
|
|
3476
|
-
timestamp: new Date().toISOString(),
|
|
3477
|
-
data: stripRecordedPosition(payload),
|
|
3478
|
-
};
|
|
3479
|
-
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
3480
|
-
}
|
|
3062
|
+
await this.markers.recordNolanTurn(question, reply, this.persistedTranscriptCount());
|
|
3481
3063
|
}
|
|
3482
3064
|
/**
|
|
3483
3065
|
* Record one autopilot verdict marker (prompted / done / human / capped)
|
|
@@ -3487,23 +3069,6 @@ export class AgentSession {
|
|
|
3487
3069
|
* instead of dropping the marker or falling back to a raw verdict string.
|
|
3488
3070
|
* No-op persistence for transient sessions (kept in memory only).
|
|
3489
3071
|
*/
|
|
3490
|
-
async recordFinishedBackgroundVerification(id) {
|
|
3491
|
-
const started = this.backgroundVerification.get(id);
|
|
3492
|
-
const proc = this.processManager?.list().find((entry) => entry.id === id);
|
|
3493
|
-
if (!started || !proc || proc.exitCode === null)
|
|
3494
|
-
return false;
|
|
3495
|
-
if (started.sourceSnapshot !== undefined) {
|
|
3496
|
-
await this.finishSnapshotVerification(started, proc.exitCode === 0);
|
|
3497
|
-
}
|
|
3498
|
-
else if (proc.exitCode === 0) {
|
|
3499
|
-
this.verificationGate.recordVerification(started.revision, started.command);
|
|
3500
|
-
}
|
|
3501
|
-
else {
|
|
3502
|
-
this.verificationGate.recordFailedVerification(started.command, started.revision);
|
|
3503
|
-
}
|
|
3504
|
-
this.backgroundVerification.delete(id);
|
|
3505
|
-
return true;
|
|
3506
|
-
}
|
|
3507
3072
|
getVerificationEvidence() {
|
|
3508
3073
|
return this.verificationGate.evidence();
|
|
3509
3074
|
}
|
|
@@ -3515,39 +3080,8 @@ export class AgentSession {
|
|
|
3515
3080
|
evidence: this.verificationGate.evidence("run"),
|
|
3516
3081
|
};
|
|
3517
3082
|
}
|
|
3518
|
-
async finishSnapshotVerification(check, passed) {
|
|
3519
|
-
const after = check.sourceSnapshot
|
|
3520
|
-
? await captureVerificationSnapshot(this.opts.cwd, [...this.hookFileEditCounts.keys()])
|
|
3521
|
-
: null;
|
|
3522
|
-
if (after === null || after !== check.sourceSnapshot) {
|
|
3523
|
-
this.verificationGate.requireFreshVerification(true, check.command);
|
|
3524
|
-
this.verificationGate.recordRejectedCheck(check.command, after === null
|
|
3525
|
-
? "Workspace inputs could not be compared; run a read-only check after the command"
|
|
3526
|
-
: "Command changed workspace inputs; run checks against the changed source");
|
|
3527
|
-
if (!passed)
|
|
3528
|
-
this.verificationGate.recordFailedVerification(check.command);
|
|
3529
|
-
}
|
|
3530
|
-
else if (passed) {
|
|
3531
|
-
const classification = classifyVerificationCommand(check.command);
|
|
3532
|
-
if (classification.snapshotPreserveOnly) {
|
|
3533
|
-
this.verificationGate.recordRejectedCheck(check.command, classification.reason);
|
|
3534
|
-
}
|
|
3535
|
-
else {
|
|
3536
|
-
this.verificationGate.recordVerification(check.revision, check.command);
|
|
3537
|
-
}
|
|
3538
|
-
}
|
|
3539
|
-
else {
|
|
3540
|
-
this.verificationGate.recordFailedVerification(check.command, check.revision);
|
|
3541
|
-
}
|
|
3542
|
-
}
|
|
3543
3083
|
getVerificationProblem() {
|
|
3544
|
-
|
|
3545
|
-
return "Unverified: a build is still running or its workspace comparison is pending.";
|
|
3546
|
-
}
|
|
3547
|
-
return (this.verificationGate.verificationProblem() ??
|
|
3548
|
-
(this.backgroundVerification.size > 0
|
|
3549
|
-
? "Unverified: a background check is still running or its result has not been confirmed."
|
|
3550
|
-
: null));
|
|
3084
|
+
return this.verificationTracker.verificationProblem();
|
|
3551
3085
|
}
|
|
3552
3086
|
async persistCompletionReviewState() {
|
|
3553
3087
|
if (!this.sessionPath || !this.opts.completionReview)
|
|
@@ -3580,50 +3114,13 @@ export class AgentSession {
|
|
|
3580
3114
|
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
3581
3115
|
}
|
|
3582
3116
|
async persistAutopilotMarker(phase, extra) {
|
|
3583
|
-
|
|
3584
|
-
const payload = {
|
|
3585
|
-
version: 1,
|
|
3586
|
-
phase,
|
|
3587
|
-
afterMessageCount,
|
|
3588
|
-
...(extra?.reason !== undefined ? { reason: extra.reason } : {}),
|
|
3589
|
-
...(extra?.body !== undefined ? { body: extra.body } : {}),
|
|
3590
|
-
};
|
|
3591
|
-
this.autopilotMarkers.push(payload);
|
|
3592
|
-
if (!this.sessionPath)
|
|
3593
|
-
return;
|
|
3594
|
-
const entry = {
|
|
3595
|
-
type: "custom",
|
|
3596
|
-
kind: AUTOPILOT_MARKER_CUSTOM_KIND,
|
|
3597
|
-
id: crypto.randomUUID(),
|
|
3598
|
-
parentId: null,
|
|
3599
|
-
timestamp: new Date().toISOString(),
|
|
3600
|
-
data: payload,
|
|
3601
|
-
};
|
|
3602
|
-
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
3603
|
-
}
|
|
3604
|
-
/** Re-append the in-memory autopilot markers to the current session file.
|
|
3605
|
-
* Mirrors `rePersistNolanTurns` — called after a continuation/compaction file
|
|
3606
|
-
* is created so the auto-review history survives the rewrite. */
|
|
3607
|
-
async rePersistAutopilotMarkers() {
|
|
3608
|
-
if (!this.sessionPath)
|
|
3609
|
-
return;
|
|
3610
|
-
for (const payload of this.autopilotMarkers) {
|
|
3611
|
-
const entry = {
|
|
3612
|
-
type: "custom",
|
|
3613
|
-
kind: AUTOPILOT_MARKER_CUSTOM_KIND,
|
|
3614
|
-
id: crypto.randomUUID(),
|
|
3615
|
-
parentId: null,
|
|
3616
|
-
timestamp: new Date().toISOString(),
|
|
3617
|
-
data: stripRecordedPosition(payload),
|
|
3618
|
-
};
|
|
3619
|
-
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
3620
|
-
}
|
|
3117
|
+
await this.markers.recordAutopilotMarker(phase, extra, this.persistedTranscriptCount());
|
|
3621
3118
|
}
|
|
3622
3119
|
/** App transcript markers recorded against this session, in record order.
|
|
3623
3120
|
* Used by the host to interleave display-only rows (plan banner, task
|
|
3624
3121
|
* header, errors, user-bubble hints) back into the transcript on resume. */
|
|
3625
3122
|
getAppMarkers() {
|
|
3626
|
-
return this.
|
|
3123
|
+
return this.markers.getAppMarkers();
|
|
3627
3124
|
}
|
|
3628
3125
|
/**
|
|
3629
3126
|
* Record one app transcript marker (display-only row) against this session.
|
|
@@ -3635,20 +3132,7 @@ export class AgentSession {
|
|
|
3635
3132
|
* No-op persistence for transient sessions.
|
|
3636
3133
|
*/
|
|
3637
3134
|
async persistAppMarker(kind, data, anchorOffset = 0) {
|
|
3638
|
-
|
|
3639
|
-
const payload = { version: 1, kind, afterMessageCount, data };
|
|
3640
|
-
this.appMarkers.push(payload);
|
|
3641
|
-
if (!this.sessionPath)
|
|
3642
|
-
return;
|
|
3643
|
-
const entry = {
|
|
3644
|
-
type: "custom",
|
|
3645
|
-
kind: APP_MARKER_CUSTOM_KIND,
|
|
3646
|
-
id: crypto.randomUUID(),
|
|
3647
|
-
parentId: null,
|
|
3648
|
-
timestamp: new Date().toISOString(),
|
|
3649
|
-
data: payload,
|
|
3650
|
-
};
|
|
3651
|
-
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
3135
|
+
await this.markers.recordAppMarker(kind, data, this.persistedTranscriptCount() + anchorOffset);
|
|
3652
3136
|
}
|
|
3653
3137
|
/**
|
|
3654
3138
|
* Open the run journal for one `RunLifecycle` generation.
|
|
@@ -3677,24 +3161,6 @@ export class AgentSession {
|
|
|
3677
3161
|
outcome,
|
|
3678
3162
|
});
|
|
3679
3163
|
}
|
|
3680
|
-
/** Re-append the in-memory app markers to the current session file. Mirrors
|
|
3681
|
-
* `rePersistNolanTurns` — called after a continuation/compaction file is
|
|
3682
|
-
* created so display-only rows survive the rewrite. */
|
|
3683
|
-
async rePersistAppMarkers() {
|
|
3684
|
-
if (!this.sessionPath)
|
|
3685
|
-
return;
|
|
3686
|
-
for (const payload of this.appMarkers) {
|
|
3687
|
-
const entry = {
|
|
3688
|
-
type: "custom",
|
|
3689
|
-
kind: APP_MARKER_CUSTOM_KIND,
|
|
3690
|
-
id: crypto.randomUUID(),
|
|
3691
|
-
parentId: null,
|
|
3692
|
-
timestamp: new Date().toISOString(),
|
|
3693
|
-
data: stripRecordedPosition(payload),
|
|
3694
|
-
};
|
|
3695
|
-
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
3696
|
-
}
|
|
3697
|
-
}
|
|
3698
3164
|
/**
|
|
3699
3165
|
* Rewrite a draft prompt into a tighter, terminology-correct version using
|
|
3700
3166
|
* the ACTIVE provider/model. A stateless one-off LLM call (no agent loop, no
|
|
@@ -3936,7 +3402,7 @@ export class AgentSession {
|
|
|
3936
3402
|
this.stopBackgroundProcesses();
|
|
3937
3403
|
this.lspManager?.shutdownAll();
|
|
3938
3404
|
this.debugManager?.shutdown();
|
|
3939
|
-
await Promise.all([this.subAgentManager?.shutdownAll(), this.
|
|
3405
|
+
await Promise.all([this.subAgentManager?.shutdownAll(), this.mcp.manager?.dispose()]);
|
|
3940
3406
|
await this.extensionLoader.deactivateAll();
|
|
3941
3407
|
this.setSessionPath("");
|
|
3942
3408
|
this.eventBus.removeAllListeners();
|
|
@@ -3981,7 +3447,7 @@ export class AgentSession {
|
|
|
3981
3447
|
.reverse()
|
|
3982
3448
|
.find((entry) => entry.type === "custom" && entry.kind === COMPLETION_REVIEW_STATE_KIND);
|
|
3983
3449
|
this.opts.completionReview?.restore(savedCompletionReview?.type === "custom" ? savedCompletionReview.data : null);
|
|
3984
|
-
this.
|
|
3450
|
+
this.verificationTracker.clearBackground();
|
|
3985
3451
|
const savedVerification = [...loaded.entries]
|
|
3986
3452
|
.reverse()
|
|
3987
3453
|
.find((entry) => entry.type === "custom" && entry.kind === VERIFICATION_STATE_KIND);
|
|
@@ -4011,12 +3477,8 @@ export class AgentSession {
|
|
|
4011
3477
|
// Restore Nolan's advisory turns (custom entries, not on the message branch) so
|
|
4012
3478
|
// they reappear in the transcript and survive into the continuation file.
|
|
4013
3479
|
// The leaf lets marker replay retain its independent file-order position.
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
this.autopilotMarkers = this.sessionManager.getAutopilotMarkers(loaded.entries, loaded.header.leafId);
|
|
4017
|
-
// Restore app transcript markers (plan banner / task header / errors / hints).
|
|
4018
|
-
this.appMarkers = this.sessionManager.getAppMarkers(loaded.entries, loaded.header.leafId);
|
|
4019
|
-
this.turnMetrics = this.sessionManager.getTurnMetrics(loaded.entries);
|
|
3480
|
+
// Autopilot verdicts, app markers and turn metrics are restored the same way.
|
|
3481
|
+
this.markers.restore(loaded.entries, loaded.header.leafId);
|
|
4020
3482
|
// A run that opened the journal and never closed it died mid-flight. Read
|
|
4021
3483
|
// it here, before anything rewrites the file, and report it once the
|
|
4022
3484
|
// transcript is in place.
|
|
@@ -4216,13 +3678,4 @@ export class AgentSession {
|
|
|
4216
3678
|
}
|
|
4217
3679
|
}
|
|
4218
3680
|
}
|
|
4219
|
-
/** Expand a leading `~` so `/import ~/.codex/...` works from any shell. */
|
|
4220
|
-
function resolveHomePath(filePath) {
|
|
4221
|
-
if (filePath === "~")
|
|
4222
|
-
return os.homedir();
|
|
4223
|
-
if (filePath.startsWith("~/") || filePath.startsWith("~\\")) {
|
|
4224
|
-
return path.join(os.homedir(), filePath.slice(2));
|
|
4225
|
-
}
|
|
4226
|
-
return filePath;
|
|
4227
|
-
}
|
|
4228
3681
|
//# sourceMappingURL=agent-session.js.map
|