@kenkaiiii/ggcoder 5.69.0 → 5.71.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/assets/motion/bin/contact-sheet.mjs +9 -3
- package/assets/motion/bin/cues.mjs +337 -0
- package/assets/motion/bin/library.mjs +53 -10
- package/assets/motion/bin/motion-blur.mjs +943 -0
- package/assets/motion/bin/motion-check.mjs +354 -3
- package/assets/motion/bin/music-fit.mjs +436 -0
- package/assets/motion/bin/pdf-extract.mjs +22 -10
- package/assets/motion/bin/reference-study.mjs +346 -0
- package/assets/motion/bin/score-synth.mjs +1052 -93
- package/assets/motion/library/README.md +50 -14
- package/assets/motion/library/kit/moves.js +1981 -0
- package/assets/motion/library/library.json +232 -0
- package/assets/motion/library/pieces/camera-rig/meta.json +13 -0
- package/assets/motion/library/pieces/camera-rig/piece.html +153 -0
- package/assets/motion/library/pieces/camera-rig/preview.jpg +0 -0
- package/assets/motion/library/pieces/chain-knock/meta.json +13 -0
- package/assets/motion/library/pieces/chain-knock/piece.html +195 -0
- package/assets/motion/library/pieces/chain-knock/preview.jpg +0 -0
- package/assets/motion/library/pieces/gather-to-logo/meta.json +13 -0
- package/assets/motion/library/pieces/gather-to-logo/piece.html +159 -0
- package/assets/motion/library/pieces/gather-to-logo/preview.jpg +0 -0
- package/assets/motion/library/pieces/morph-carry/meta.json +13 -0
- package/assets/motion/library/pieces/morph-carry/piece.html +173 -0
- package/assets/motion/library/pieces/morph-carry/preview.jpg +0 -0
- package/assets/motion/library/pieces/one-shape-journey/meta.json +13 -0
- package/assets/motion/library/pieces/one-shape-journey/piece.html +195 -0
- package/assets/motion/library/pieces/one-shape-journey/preview.jpg +0 -0
- package/assets/motion/library/pieces/open-from-subject/meta.json +13 -0
- package/assets/motion/library/pieces/open-from-subject/piece.html +168 -0
- package/assets/motion/library/pieces/open-from-subject/preview.jpg +0 -0
- package/assets/motion/library/pieces/request-to-result/meta.json +13 -0
- package/assets/motion/library/pieces/request-to-result/piece.html +212 -0
- package/assets/motion/library/pieces/request-to-result/preview.jpg +0 -0
- package/assets/motion/library/pieces/scale-dive/meta.json +13 -0
- package/assets/motion/library/pieces/scale-dive/piece.html +321 -0
- package/assets/motion/library/pieces/scale-dive/preview.jpg +0 -0
- package/assets/motion/library/pieces/screen-replica-steps/meta.json +13 -0
- package/assets/motion/library/pieces/screen-replica-steps/piece.html +366 -0
- package/assets/motion/library/pieces/screen-replica-steps/preview.jpg +0 -0
- package/assets/motion/library/pieces/zoom-into-card/meta.json +13 -0
- package/assets/motion/library/pieces/zoom-into-card/piece.html +179 -0
- package/assets/motion/library/pieces/zoom-into-card/preview.jpg +0 -0
- package/assets/motion/library/sheets/diagram.jpg +0 -0
- package/assets/motion/library/sheets/frame.jpg +0 -0
- package/assets/motion/library/sheets/transition.jpg +0 -0
- package/assets/motion/library/sheets/ui.jpg +0 -0
- package/assets/motion/references/build-sheet.md +206 -0
- package/assets/motion/references/runtime/determinism-rules.md +1 -1
- package/assets/motion/references/runtime/gsap-easing-and-stagger.md +29 -29
- package/assets/motion/references/runtime/inputs-and-assets.md +7 -12
- package/assets/motion/references/runtime/lint-validate-inspect.md +3 -3
- package/assets/motion/references/runtime/minimal-composition.md +1 -1
- package/assets/motion/references/runtime/preview-render.md +3 -3
- package/assets/motion/skills/app-walkthrough/SKILL.md +66 -0
- package/assets/motion/skills/before-after/SKILL.md +53 -0
- package/assets/motion/skills/brand-kit/SKILL.md +3 -3
- package/assets/motion/skills/dev-tool-video/SKILL.md +57 -0
- package/assets/motion/skills/launch-video/SKILL.md +62 -0
- package/assets/motion/skills/match-reference/SKILL.md +58 -0
- package/assets/motion/skills/motion/SKILL.md +78 -85
- package/assets/motion/skills/source-ingest/SKILL.md +17 -6
- package/assets/motion/skills/website-video/SKILL.md +59 -0
- package/assets/skills/bulletproof/SKILL.md +36 -11
- package/assets/skills/bulletproof/references/agent-surface.md +19 -9
- package/assets/skills/bulletproof/references/audit-protocol.md +20 -5
- package/assets/skills/bulletproof/references/platform-playbooks.md +5 -4
- package/assets/skills/bulletproof/references/provenance.md +26 -1
- package/assets/skills/bulletproof/references/secure-defaults.md +6 -5
- package/assets/skills/bulletproof/references/supply-chain.md +21 -17
- package/assets/skills/bulletproof/references/threat-landscape.md +28 -26
- package/assets/skills/bulletproof/references/verification.md +2 -0
- package/assets/skills/clarify/SKILL.md +25 -16
- package/assets/skills/code-review/SKILL.md +71 -13
- package/assets/skills/code-review/references/agent-diffs.md +27 -0
- package/assets/skills/code-review/references/tests.md +19 -0
- package/assets/skills/compliance-guard/SKILL.md +20 -5
- package/assets/skills/compliance-guard/references/artifacts.md +1 -1
- package/assets/skills/compliance-guard/references/eu-uk.md +16 -16
- package/assets/skills/compliance-guard/references/lawsuit-vectors.md +5 -5
- package/assets/skills/compliance-guard/references/provenance.md +41 -2
- package/assets/skills/compliance-guard/references/sector-gates.md +3 -3
- package/assets/skills/compliance-guard/references/security-baseline.md +2 -2
- package/assets/skills/compliance-guard/references/trigger-map.md +5 -5
- package/assets/skills/compliance-guard/references/us.md +27 -21
- package/assets/skills/durable/SKILL.md +87 -79
- package/assets/skills/durable/references/agent-db-safety.md +69 -0
- package/assets/skills/durable/references/backups-and-runtime.md +19 -12
- package/assets/skills/durable/references/migrations-and-schema.md +13 -6
- package/assets/skills/evidence-led-ui/SKILL.md +69 -127
- package/assets/skills/evidence-led-ui/references/anti-defaults.md +107 -208
- package/assets/skills/evidence-led-ui/references/direction.md +124 -0
- package/assets/skills/evidence-led-ui/references/production-contract.md +8 -0
- package/assets/skills/evidence-led-ui/references/provenance.md +24 -1
- package/assets/skills/lean/SKILL.md +90 -71
- package/assets/skills/lean/references/memory-and-processes.md +3 -2
- package/assets/skills/lean/references/playbooks.md +37 -12
- package/assets/skills/refactoring/SKILL.md +24 -3
- package/assets/skills/refactoring/references/agent-pitfalls.md +4 -1
- package/assets/skills/refactoring/references/legacy.md +21 -0
- package/assets/skills/root-cause/SKILL.md +20 -10
- package/assets/skills/shared-language/SKILL.md +16 -14
- package/assets/skills/tdd/SKILL.md +27 -15
- package/dist/app-sidecar.js +56 -33
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli.js +2 -11
- package/dist/cli.js.map +1 -1
- package/dist/core/acceptance-checks.d.ts +48 -0
- package/dist/core/acceptance-checks.js +144 -0
- package/dist/core/acceptance-checks.js.map +1 -0
- package/dist/core/agent-session.d.ts +32 -70
- package/dist/core/agent-session.js +92 -413
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agents.d.ts +6 -5
- package/dist/core/agents.js.map +1 -1
- package/dist/core/bundled-agents.js +1 -3
- package/dist/core/bundled-agents.js.map +1 -1
- package/dist/core/compaction/compactor.js +78 -48
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/mcp/content.js +6 -2
- package/dist/core/mcp/content.js.map +1 -1
- package/dist/core/model-unavailable.d.ts +14 -0
- package/dist/core/model-unavailable.js +23 -0
- package/dist/core/model-unavailable.js.map +1 -0
- package/dist/core/node-debugger.d.ts +148 -0
- package/dist/core/node-debugger.js +642 -0
- package/dist/core/node-debugger.js.map +1 -0
- package/dist/core/process-manager.d.ts +14 -0
- package/dist/core/process-manager.js +61 -0
- package/dist/core/process-manager.js.map +1 -1
- package/dist/core/session-history.d.ts +12 -0
- package/dist/core/session-history.js +27 -0
- package/dist/core/session-history.js.map +1 -1
- package/dist/core/settings-manager.d.ts +0 -1
- package/dist/core/settings-manager.js +2 -5
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.js +3 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +11 -5
- package/dist/core/subagent-manager.js +15 -7
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/test-impact.d.ts +73 -0
- package/dist/core/test-impact.js +467 -0
- package/dist/core/test-impact.js.map +1 -0
- package/dist/core/thinking-level.d.ts +1 -1
- package/dist/core/thinking-level.js +1 -1
- package/dist/core/thinking-level.js.map +1 -1
- package/dist/core/verification-gate.d.ts +8 -98
- package/dist/core/verification-gate.js +26 -256
- package/dist/core/verification-gate.js.map +1 -1
- package/dist/interactive.js +2 -1
- package/dist/interactive.js.map +1 -1
- package/dist/modes/subagent-worker-mode.js +17 -5
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/motion-agent/motion-agent.d.ts +6 -2
- package/dist/motion-agent/motion-agent.js +5 -7
- package/dist/motion-agent/motion-agent.js.map +1 -1
- package/dist/motion-agent/motion-prompt.d.ts +1 -1
- package/dist/motion-agent/motion-prompt.js +14 -19
- package/dist/motion-agent/motion-prompt.js.map +1 -1
- package/dist/motion-agent/motion-review.d.ts +5 -1
- package/dist/motion-agent/motion-review.js +8 -3
- package/dist/motion-agent/motion-review.js.map +1 -1
- package/dist/motion-agent/motion-studio-context.js +1 -1
- package/dist/motion-agent/motion-studio-context.js.map +1 -1
- package/dist/system-prompt.js +3 -1
- package/dist/system-prompt.js.map +1 -1
- package/dist/test-support/keep-alive.d.ts +14 -0
- package/dist/test-support/keep-alive.js +17 -0
- package/dist/test-support/keep-alive.js.map +1 -0
- package/dist/tools/bash-read-evidence.d.ts +10 -0
- package/dist/tools/bash-read-evidence.js +133 -0
- package/dist/tools/bash-read-evidence.js.map +1 -0
- package/dist/tools/bash.d.ts +10 -1
- package/dist/tools/bash.js +66 -3
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/debug.d.ts +54 -0
- package/dist/tools/debug.js +233 -0
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/edit.js +12 -4
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/index.d.ts +19 -2
- package/dist/tools/index.js +46 -6
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/prompt-hints.js +2 -0
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/read-tracker.d.ts +5 -0
- package/dist/tools/read-tracker.js +19 -9
- package/dist/tools/read-tracker.js.map +1 -1
- package/dist/tools/skill.js +5 -0
- package/dist/tools/skill.js.map +1 -1
- package/dist/tools/steroids.d.ts +1 -1
- package/dist/tools/subagent-control.js +11 -3
- package/dist/tools/subagent-control.js.map +1 -1
- package/dist/tools/subagent-shared.d.ts +16 -8
- package/dist/tools/subagent-shared.js +21 -15
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/subagent.d.ts +8 -2
- package/dist/tools/subagent.js +16 -9
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/tool-tiers.d.ts +2 -2
- package/dist/tools/tool-tiers.js +3 -2
- package/dist/tools/tool-tiers.js.map +1 -1
- package/dist/ui/App.d.ts +0 -7
- package/dist/ui/App.js +6 -79
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +0 -7
- package/dist/ui/hooks/useAgentLoop.js +1 -119
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/render.d.ts +0 -4
- package/dist/ui/render.js +0 -2
- package/dist/ui/render.js.map +1 -1
- package/dist/utils/image.d.ts +14 -0
- package/dist/utils/image.js +16 -0
- package/dist/utils/image.js.map +1 -1
- package/package.json +4 -4
- package/assets/motion/references/motion-language.md +0 -128
- package/assets/motion/skills/video-qa/SKILL.md +0 -89
- package/dist/core/ideal-review-subagent.d.ts +0 -56
- package/dist/core/ideal-review-subagent.js +0 -112
- package/dist/core/ideal-review-subagent.js.map +0 -1
- package/dist/core/ideal-review.d.ts +0 -82
- package/dist/core/ideal-review.js +0 -242
- package/dist/core/ideal-review.js.map +0 -1
- package/dist/motion-agent/motion-check-tool.d.ts +0 -42
- package/dist/motion-agent/motion-check-tool.js +0 -564
- package/dist/motion-agent/motion-check-tool.js.map +0 -1
|
@@ -24,8 +24,9 @@ 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
|
+
import { autoBackgroundedId } from "../tools/bash.js";
|
|
27
28
|
import { buildProcessCompletionFollowUp } from "./process-gate.js";
|
|
28
|
-
import { buildSubAgentCompletionFollowUp
|
|
29
|
+
import { buildSubAgentCompletionFollowUp } from "./subagent-manager.js";
|
|
29
30
|
import { applyAsyncSubagentPolicy } from "./subagent-policy.js";
|
|
30
31
|
import { z } from "zod";
|
|
31
32
|
import { MCPClientManager, getAllMcpServers } from "./mcp/index.js";
|
|
@@ -50,15 +51,13 @@ import { pruneStaleToolResults } from "./compaction/tool-result-pruner.js";
|
|
|
50
51
|
import { discoverAgents } from "./agents.js";
|
|
51
52
|
import { enhancePrompt } from "../utils/prompt-enhancer.js";
|
|
52
53
|
import { detectLanguages, detectProjectStack } from "./language-detector.js";
|
|
53
|
-
import { evaluateIdealReview, buildIdealReviewMessage, buildReviewCoverageEscalationMessage, buildReviewCoverageMessage, MAX_REVIEW_COVERAGE_INJECTIONS, withReviewCoverageRequirements, detectTestDrift, ReviewCoverageTracker, } from "./ideal-review.js";
|
|
54
54
|
import { evaluateLoopBreak, buildLoopBreakMessage, CycleDetector, ToolCallProgressTracker, detectTextRepetition, } from "./loop-breaker.js";
|
|
55
55
|
import { buildRegroundingMessage, requestTextForRegrounding } from "./regrounding.js";
|
|
56
56
|
import { buildSemanticLoopJudgePrompt, buildSemanticLoopMessage, MAX_SEMANTIC_LOOP_CALLS, parseSemanticLoopVerdict, shouldRunSemanticLoopCheck, SEMANTIC_LOOP_JUDGE_TIMEOUT_MS, withJudgeTimeout, } from "./semantic-loop-check.js";
|
|
57
|
-
import { buildIndependentReviewMessage, buildReviewerTask, INDEPENDENT_REVIEW_SCORE_THRESHOLD, parseReviewerFindings, REVIEWER_TOOLS, REVIEWER_TURN_TIMEOUT_MS, REVIEWER_WAIT_MS, } from "./ideal-review-subagent.js";
|
|
58
57
|
import { buildEnvDeltaMessage } from "./env-delta.js";
|
|
59
58
|
import { wrapSteeringText, buildNotificationSteeringText, STEERING_PREFIX } from "./steering.js";
|
|
60
59
|
import { AgentNotificationQueue } from "./agent-notifications.js";
|
|
61
|
-
import { VerificationGate,
|
|
60
|
+
import { VerificationGate, isCheckOwnFile, isCodeFilePath, VERIFICATION_STATE_KIND, isVerificationCommand, } from "./verification-gate.js";
|
|
62
61
|
import { classifyVerificationCommand } from "./verification-evidence.js";
|
|
63
62
|
import { captureVerificationSnapshot } from "./verification-snapshot.js";
|
|
64
63
|
import { findUserSessionPrompt, getUserSessionPrompt } from "./session-preview.js";
|
|
@@ -73,14 +72,6 @@ import path from "node:path";
|
|
|
73
72
|
* progressing — refuse to extend its turn budget.
|
|
74
73
|
*/
|
|
75
74
|
const TURN_EXTENSION_MAX_FAILURE_RATIO = 0.5;
|
|
76
|
-
/** Terminal subagent states — mirrors SubAgentManager's private isTerminal. */
|
|
77
|
-
function isTerminalSubAgentState(state) {
|
|
78
|
-
return (state === "completed" ||
|
|
79
|
-
state === "failed" ||
|
|
80
|
-
state === "interrupted" ||
|
|
81
|
-
state === "closed" ||
|
|
82
|
-
state === "reaped");
|
|
83
|
-
}
|
|
84
75
|
// ── Tool-result policy ─────────────────────────────────────
|
|
85
76
|
/** Resolve the per-result cap passed to the agent loop for the active transport. */
|
|
86
77
|
export function resolveSessionToolResultCharLimit(model, provider, accountId) {
|
|
@@ -149,20 +140,13 @@ export class AgentSession {
|
|
|
149
140
|
/** Forgets every file read; called whenever the conversation is replaced or
|
|
150
141
|
* rewound, so the model must re-read a file before changing it. */
|
|
151
142
|
clearReadTracker;
|
|
143
|
+
recordBashReads;
|
|
152
144
|
skills = [];
|
|
153
145
|
cacheKeyLogged = false;
|
|
154
146
|
// ── Self-correction hook state (mirrors the TUI's useAgentLoop refs) ──
|
|
155
147
|
// Reset at the start of every run; observed from the event stream; read by
|
|
156
|
-
// the loop-break (mid-loop)
|
|
157
|
-
hookStats = {
|
|
158
|
-
changedLines: 0,
|
|
159
|
-
toolCalls: 0,
|
|
160
|
-
toolFailures: 0,
|
|
161
|
-
turns: 0,
|
|
162
|
-
writeCalls: 0,
|
|
163
|
-
editCalls: 0,
|
|
164
|
-
bashCalls: 0,
|
|
165
|
-
};
|
|
148
|
+
// the loop-break (mid-loop) callback.
|
|
149
|
+
hookStats = { toolCalls: 0, toolFailures: 0, turns: 0 };
|
|
166
150
|
hookText = "";
|
|
167
151
|
hookConsecutiveFailures = 0;
|
|
168
152
|
hookRepeatedNoProgressCalls = 0;
|
|
@@ -172,20 +156,6 @@ export class AgentSession {
|
|
|
172
156
|
hookFileEditCounts = new Map();
|
|
173
157
|
hookToolCalls = new Map();
|
|
174
158
|
backgroundVerification = new Map();
|
|
175
|
-
idealReviewPhase = "idle";
|
|
176
|
-
/** Runtime-only suppression while Ken owns verification in autopilot mode. */
|
|
177
|
-
idealReviewSuppressed = false;
|
|
178
|
-
/** Mirror of the last `hook_armed` value broadcast this run, so the event
|
|
179
|
-
* fires only on a real edge. */
|
|
180
|
-
idealReviewArmed = false;
|
|
181
|
-
/** Cached test-drift probe, keyed by the size of the edited-file set. Drift
|
|
182
|
-
* depends only on WHICH files were edited and that set only grows, so this
|
|
183
|
-
* keeps the arming check off the filesystem on most tool results — the probe
|
|
184
|
-
* is several sync existsSync calls per edited file. */
|
|
185
|
-
idealDriftProbe = null;
|
|
186
|
-
reviewCoverage;
|
|
187
|
-
/** Coverage follow-ups spent this run, capped by MAX_REVIEW_COVERAGE_INJECTIONS. */
|
|
188
|
-
reviewCoverageInjected = 0;
|
|
189
159
|
/** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */
|
|
190
160
|
loopBreakInjected = 0;
|
|
191
161
|
regroundingInjected = false;
|
|
@@ -195,8 +165,6 @@ export class AgentSession {
|
|
|
195
165
|
* injection at the next steering poll; judge failures fail open (no
|
|
196
166
|
* injection) and still consume budget + cooldown. */
|
|
197
167
|
semanticLoop = { checksUsed: 0, lastCheckTurn: 0, pending: false, verdict: null, injected: false };
|
|
198
|
-
/** Independent Ideal reviewer spawned once per run (score-gated). */
|
|
199
|
-
independentReviewStarted = false;
|
|
200
168
|
/**
|
|
201
169
|
* The environment as the cached system prompt currently describes it.
|
|
202
170
|
* Re-recorded on every prompt build, so a rebuild (e.g. `/add-dir`) needs no
|
|
@@ -207,11 +175,11 @@ export class AgentSession {
|
|
|
207
175
|
runStartedAt = 0;
|
|
208
176
|
/** Gate injections spent this run, capped by MAX_PROCESS_GATE_INJECTIONS. */
|
|
209
177
|
processGateInjected = 0;
|
|
210
|
-
/** Verification
|
|
178
|
+
/** Verification evidence: code edited this run, and what has proved it since.
|
|
179
|
+
* Passive tracking only — it feeds run status and autopilot, never a turn. */
|
|
211
180
|
verificationGate = new VerificationGate();
|
|
212
|
-
/** Mirror of the last
|
|
213
|
-
|
|
214
|
-
verificationArmed = false;
|
|
181
|
+
/** Mirror of the last `hook_armed` value, so the event fires only on an edge. */
|
|
182
|
+
preFinalArmed = false;
|
|
215
183
|
compactionOccurred = false;
|
|
216
184
|
/**
|
|
217
185
|
* Re-grounding carry-over for post-turn compaction. `resetHookState` clears
|
|
@@ -262,6 +230,7 @@ export class AgentSession {
|
|
|
262
230
|
steeringListeners = new Set();
|
|
263
231
|
processManager;
|
|
264
232
|
lspManager;
|
|
233
|
+
debugManager;
|
|
265
234
|
subAgentManager;
|
|
266
235
|
/**
|
|
267
236
|
* Out-of-band push notifications (finished children, background-process
|
|
@@ -365,7 +334,6 @@ export class AgentSession {
|
|
|
365
334
|
this.provider = options.provider;
|
|
366
335
|
this.model = options.model;
|
|
367
336
|
this.cwd = options.cwd;
|
|
368
|
-
this.reviewCoverage = new ReviewCoverageTracker(this.cwd);
|
|
369
337
|
this.baseUrl = options.baseUrl;
|
|
370
338
|
this.maxTokens = this.resolveMaxTokens(options.model);
|
|
371
339
|
this.thinkingLevel = options.thinkingLevel;
|
|
@@ -431,7 +399,7 @@ export class AgentSession {
|
|
|
431
399
|
: this.opts.globalSubagents
|
|
432
400
|
? await discoverAgents({ globalAgentsDir: paths.agentsDir })
|
|
433
401
|
: [];
|
|
434
|
-
const { tools: builtInTools, processManager, rebuildReadTool, clearReadTracker, lspManager, subAgentManager, } = await createTools(this.cwd, {
|
|
402
|
+
const { tools: builtInTools, processManager, rebuildReadTool, clearReadTracker, recordBashReads, lspManager, debugManager, subAgentManager, } = await createTools(this.cwd, {
|
|
435
403
|
agents,
|
|
436
404
|
skills: this.skills,
|
|
437
405
|
contextLimits: this.contextLimits,
|
|
@@ -462,17 +430,14 @@ export class AgentSession {
|
|
|
462
430
|
}),
|
|
463
431
|
getUseExternalGrep: () => this.settingsManager.get("grepUseRipgrep"),
|
|
464
432
|
authStorage: this.authStorage,
|
|
465
|
-
onFileRead: (filePath) => this.reviewCoverage.recordRead(filePath),
|
|
466
433
|
onFileMutated: (filePath) => {
|
|
467
434
|
const relative = path.relative(this.cwd, filePath) || path.basename(filePath);
|
|
468
435
|
this.hookFileEditCounts.set(relative, (this.hookFileEditCounts.get(relative) ?? 0) + 1);
|
|
469
|
-
this.reviewCoverage.recordChanged(filePath);
|
|
470
436
|
},
|
|
471
437
|
// Lazy — sessionId/model/provider can change after createTools() runs, so
|
|
472
438
|
// sub-agent spawns read the current parent state at execution time.
|
|
473
439
|
getProvider: () => this.provider,
|
|
474
440
|
getModel: () => this.model,
|
|
475
|
-
getThinkingLevel: () => this.thinkingLevel,
|
|
476
441
|
getBaseUrl: () => this.baseUrl,
|
|
477
442
|
getCacheKey: () => this.getPromptCacheKey(),
|
|
478
443
|
getMaxPerModel: () => this.settingsManager.get("subagentMaxPerModel"),
|
|
@@ -523,8 +488,10 @@ export class AgentSession {
|
|
|
523
488
|
}
|
|
524
489
|
this.rebuildReadTool = rebuildReadTool;
|
|
525
490
|
this.clearReadTracker = clearReadTracker;
|
|
491
|
+
this.recordBashReads = recordBashReads;
|
|
526
492
|
this.processManager = processManager;
|
|
527
493
|
this.lspManager = lspManager;
|
|
494
|
+
this.debugManager = debugManager;
|
|
528
495
|
this.subAgentManager = subAgentManager;
|
|
529
496
|
this.bindManagerCancellation(this.opts.signal);
|
|
530
497
|
// Connect MCP servers. The connect attempt itself can block for up to the
|
|
@@ -1111,15 +1078,7 @@ export class AgentSession {
|
|
|
1111
1078
|
resetHookState(originalRequest) {
|
|
1112
1079
|
this.opts.completionReview?.begin(originalRequest);
|
|
1113
1080
|
this.lspManager?.clearPendingDiagnostics();
|
|
1114
|
-
this.hookStats = {
|
|
1115
|
-
changedLines: 0,
|
|
1116
|
-
toolCalls: 0,
|
|
1117
|
-
toolFailures: 0,
|
|
1118
|
-
turns: 0,
|
|
1119
|
-
writeCalls: 0,
|
|
1120
|
-
editCalls: 0,
|
|
1121
|
-
bashCalls: 0,
|
|
1122
|
-
};
|
|
1081
|
+
this.hookStats = { toolCalls: 0, toolFailures: 0, turns: 0 };
|
|
1123
1082
|
this.hookText = "";
|
|
1124
1083
|
this.hookConsecutiveFailures = 0;
|
|
1125
1084
|
this.hookRepeatedNoProgressCalls = 0;
|
|
@@ -1128,13 +1087,6 @@ export class AgentSession {
|
|
|
1128
1087
|
this.hookCyclicPattern = null;
|
|
1129
1088
|
this.hookFileEditCounts.clear();
|
|
1130
1089
|
this.hookToolCalls.clear();
|
|
1131
|
-
this.reviewCoverage.reset();
|
|
1132
|
-
this.reviewCoverageInjected = 0;
|
|
1133
|
-
this.idealReviewPhase = "idle";
|
|
1134
|
-
// No event here: clients reset their own hold on run_start.
|
|
1135
|
-
this.idealReviewArmed = false;
|
|
1136
|
-
this.verificationArmed = false;
|
|
1137
|
-
this.idealDriftProbe = null;
|
|
1138
1090
|
this.loopBreakInjected = 0;
|
|
1139
1091
|
this.regroundingInjected = false;
|
|
1140
1092
|
this.hookRecentCalls = [];
|
|
@@ -1145,7 +1097,6 @@ export class AgentSession {
|
|
|
1145
1097
|
verdict: null,
|
|
1146
1098
|
injected: false,
|
|
1147
1099
|
};
|
|
1148
|
-
this.independentReviewStarted = false;
|
|
1149
1100
|
this.runStartedAt = Date.now();
|
|
1150
1101
|
this.processGateInjected = 0;
|
|
1151
1102
|
this.verificationGate.beginRun();
|
|
@@ -1166,8 +1117,8 @@ export class AgentSession {
|
|
|
1166
1117
|
}
|
|
1167
1118
|
/**
|
|
1168
1119
|
* Fold one agent event into the hook stat accumulators. Pure bookkeeping —
|
|
1169
|
-
* the same signals the TUI's useAgentLoop collects, so
|
|
1170
|
-
*
|
|
1120
|
+
* the same signals the TUI's useAgentLoop collects, so loop-break decisions
|
|
1121
|
+
* match across the CLI and the app.
|
|
1171
1122
|
*/
|
|
1172
1123
|
async trackHookEvent(event) {
|
|
1173
1124
|
if (this.opts.completionReview) {
|
|
@@ -1210,7 +1161,7 @@ export class AgentSession {
|
|
|
1210
1161
|
...this.hookFileEditCounts.keys(),
|
|
1211
1162
|
]);
|
|
1212
1163
|
if (call.sourceSnapshot === null)
|
|
1213
|
-
this.verificationGate.requireFreshVerification(true
|
|
1164
|
+
this.verificationGate.requireFreshVerification(true);
|
|
1214
1165
|
}
|
|
1215
1166
|
else if ((classification.accepted && event.args.persist !== true) ||
|
|
1216
1167
|
(!classification.accepted && classification.mayMutate)) {
|
|
@@ -1220,7 +1171,7 @@ export class AgentSession {
|
|
|
1220
1171
|
// or a persistent-shell run records nothing at the end, so
|
|
1221
1172
|
// flagging it left verified work Unverified forever — and
|
|
1222
1173
|
// autopilot silently refused every later turn.
|
|
1223
|
-
this.verificationGate.requireFreshVerification(!classification.accepted && classification.mayMutate
|
|
1174
|
+
this.verificationGate.requireFreshVerification(!classification.accepted && classification.mayMutate);
|
|
1224
1175
|
}
|
|
1225
1176
|
await this.persistVerificationState();
|
|
1226
1177
|
}
|
|
@@ -1233,12 +1184,6 @@ export class AgentSession {
|
|
|
1233
1184
|
this.hookStats.toolCalls += 1;
|
|
1234
1185
|
if (event.isError)
|
|
1235
1186
|
this.hookStats.toolFailures += 1;
|
|
1236
|
-
if (name === "write")
|
|
1237
|
-
this.hookStats.writeCalls += 1;
|
|
1238
|
-
if (name === "edit")
|
|
1239
|
-
this.hookStats.editCalls += 1;
|
|
1240
|
-
if (name === "bash")
|
|
1241
|
-
this.hookStats.bashCalls += 1;
|
|
1242
1187
|
this.hookConsecutiveFailures = event.isError ? this.hookConsecutiveFailures + 1 : 0;
|
|
1243
1188
|
this.hookRepeatedNoProgressCalls = this.hookProgressTracker.record(name, args, event.result, event.isError);
|
|
1244
1189
|
this.hookCyclicPattern = this.hookCycleDetector.record(name, args, event.result, event.isError);
|
|
@@ -1255,12 +1200,6 @@ export class AgentSession {
|
|
|
1255
1200
|
if (this.hookRecentCalls.length > MAX_SEMANTIC_LOOP_CALLS) {
|
|
1256
1201
|
this.hookRecentCalls.splice(0, this.hookRecentCalls.length - MAX_SEMANTIC_LOOP_CALLS);
|
|
1257
1202
|
}
|
|
1258
|
-
if (name === "edit" && !event.isError) {
|
|
1259
|
-
const diff = event.details?.diff ?? event.result;
|
|
1260
|
-
const added = (diff.match(/^\+[^+]/gm) ?? []).length;
|
|
1261
|
-
const removed = (diff.match(/^-[^-]/gm) ?? []).length;
|
|
1262
|
-
this.hookStats.changedLines += added + removed;
|
|
1263
|
-
}
|
|
1264
1203
|
// Only host-observed successful mutations and trustworthy check results
|
|
1265
1204
|
// affect approval. The model's text is never evidence.
|
|
1266
1205
|
let verificationChanged = false;
|
|
@@ -1268,13 +1207,10 @@ export class AgentSession {
|
|
|
1268
1207
|
if (name === "edit" || name === "write") {
|
|
1269
1208
|
const filePath = String(args.file_path ?? "");
|
|
1270
1209
|
// Check-owning files (tsconfig.json, pytest.ini, vitest.config.ts …)
|
|
1271
|
-
// are tracked even when they are not source code: editing one
|
|
1272
|
-
//
|
|
1210
|
+
// are tracked even when they are not source code: editing one
|
|
1211
|
+
// invalidates earlier check results.
|
|
1273
1212
|
if (filePath && (isCodeFilePath(filePath) || isCheckOwnFile(filePath))) {
|
|
1274
|
-
|
|
1275
|
-
? String(args.content ?? "")
|
|
1276
|
-
: extractAddedLines(event.details?.diff ?? event.result);
|
|
1277
|
-
this.verificationGate.recordMutation(filePath, addedText);
|
|
1213
|
+
this.verificationGate.recordMutation(filePath);
|
|
1278
1214
|
verificationChanged = true;
|
|
1279
1215
|
}
|
|
1280
1216
|
}
|
|
@@ -1283,8 +1219,13 @@ export class AgentSession {
|
|
|
1283
1219
|
const command = typeof args.command === "string" ? args.command : "";
|
|
1284
1220
|
const classification = classifyVerificationCommand(command);
|
|
1285
1221
|
if (classification.accepted || classification.snapshotEligible) {
|
|
1286
|
-
|
|
1287
|
-
|
|
1222
|
+
// A foreground check that outlived the default budget was moved to
|
|
1223
|
+
// the background, not failed: track it to its real exit the same way.
|
|
1224
|
+
const autoBackgroundId = event.isError ? undefined : autoBackgroundedId(event.result);
|
|
1225
|
+
if ((autoBackgroundId !== undefined || args.run_in_background === true) &&
|
|
1226
|
+
!event.isError &&
|
|
1227
|
+
args.persist !== true) {
|
|
1228
|
+
const id = autoBackgroundId ?? /^ID:\s*(\S+)/m.exec(event.result)?.[1];
|
|
1288
1229
|
// No parseable ID means the check cannot be tracked to a real exit
|
|
1289
1230
|
// code — no evidence either way. Recording a FAILURE here made
|
|
1290
1231
|
// every later green run of a different spelling look owed.
|
|
@@ -1297,7 +1238,7 @@ export class AgentSession {
|
|
|
1297
1238
|
: {}),
|
|
1298
1239
|
});
|
|
1299
1240
|
else if (classification.snapshotEligible)
|
|
1300
|
-
this.verificationGate.requireFreshVerification(true
|
|
1241
|
+
this.verificationGate.requireFreshVerification(true);
|
|
1301
1242
|
}
|
|
1302
1243
|
else if (args.persist === true) {
|
|
1303
1244
|
// Persistent-shell checks are not bounded evidence (steering can
|
|
@@ -1355,10 +1296,35 @@ export class AgentSession {
|
|
|
1355
1296
|
// Tool results for this step are in the array and their side effects
|
|
1356
1297
|
// already hit the filesystem. Flushing here is what makes a crash lose
|
|
1357
1298
|
// at most the in-flight step instead of the entire turn.
|
|
1299
|
+
await this.creditBashReads();
|
|
1358
1300
|
await this.flushPendingMessages();
|
|
1359
1301
|
break;
|
|
1360
1302
|
}
|
|
1361
1303
|
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Count full-file `cat` output from the step that just finished as reads, so
|
|
1306
|
+
* an edit after `cat` does not cost a second read. Uses the step's results as
|
|
1307
|
+
* stored in the transcript (after per-turn trimming): only bytes the model
|
|
1308
|
+
* actually received count.
|
|
1309
|
+
*/
|
|
1310
|
+
async creditBashReads() {
|
|
1311
|
+
if (!this.recordBashReads)
|
|
1312
|
+
return;
|
|
1313
|
+
const messages = this.activeLoopMessages ?? this.messages;
|
|
1314
|
+
const toolMessage = messages.at(-1);
|
|
1315
|
+
const assistant = messages.at(-2);
|
|
1316
|
+
if (toolMessage?.role !== "tool" || assistant?.role !== "assistant")
|
|
1317
|
+
return;
|
|
1318
|
+
if (typeof assistant.content === "string")
|
|
1319
|
+
return;
|
|
1320
|
+
const calls = assistant.content.filter((part) => part.type === "tool_call");
|
|
1321
|
+
try {
|
|
1322
|
+
await this.recordBashReads(calls, toolMessage.content);
|
|
1323
|
+
}
|
|
1324
|
+
catch (err) {
|
|
1325
|
+
log("WARN", "agent-session", "Crediting bash reads failed", { error: String(err) });
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1362
1328
|
/**
|
|
1363
1329
|
* Append every message added since the last flush to the session file.
|
|
1364
1330
|
*
|
|
@@ -1421,7 +1387,7 @@ export class AgentSession {
|
|
|
1421
1387
|
const diagnosticText = this.lspManager?.drainDiagnostics(this.getVerificationProblem() !== null, { deferUnverified: true });
|
|
1422
1388
|
if (diagnosticText)
|
|
1423
1389
|
this.eventBus.emit("diagnostics", { text: diagnosticText });
|
|
1424
|
-
this.
|
|
1390
|
+
this.refreshHookArming();
|
|
1425
1391
|
const notified = this.notifications.drain();
|
|
1426
1392
|
const notificationMessage = notified.length > 0 || diagnosticText
|
|
1427
1393
|
? {
|
|
@@ -1484,6 +1450,7 @@ export class AgentSession {
|
|
|
1484
1450
|
}
|
|
1485
1451
|
if (this.opts.selfCorrectionHooks === false)
|
|
1486
1452
|
return null;
|
|
1453
|
+
// Legacy key: the user-facing switch for loop-break and re-grounding nudges.
|
|
1487
1454
|
if (!this.settingsManager.get("idealReviewEnabled"))
|
|
1488
1455
|
return null;
|
|
1489
1456
|
// Deterministic stuck verdict, computed once and shared: the semantic
|
|
@@ -1683,83 +1650,6 @@ export class AgentSession {
|
|
|
1683
1650
|
.join("\n")
|
|
1684
1651
|
: "";
|
|
1685
1652
|
}
|
|
1686
|
-
/** Independent fresh-context review of the finished work (Codex Guardian
|
|
1687
|
-
* pattern). Spawns a READ-ONLY child on the ACTIVE model, waits bounded,
|
|
1688
|
-
* and returns findings for the acting agent to address — or nothing when
|
|
1689
|
-
* the review passes, is unavailable, or fails (in-thread review remains the
|
|
1690
|
-
* fallback; the feature degrades, never blocks).
|
|
1691
|
-
*
|
|
1692
|
-
* Runs inside the pre-stop poll, so the candidate final answer is already
|
|
1693
|
-
* held by arming and this wait cannot race a streamed answer. */
|
|
1694
|
-
async runIndependentReview(decision) {
|
|
1695
|
-
if (!this.subAgentManager)
|
|
1696
|
-
return [];
|
|
1697
|
-
if (this.independentReviewStarted)
|
|
1698
|
-
return [];
|
|
1699
|
-
// An allow-listed session (a subagent worker itself) must not spawn
|
|
1700
|
-
// harness-owned grandchildren the tool policy never granted.
|
|
1701
|
-
if (this.opts.allowedTools && !this.opts.allowedTools.includes("spawn_agent"))
|
|
1702
|
-
return [];
|
|
1703
|
-
if (decision.score < INDEPENDENT_REVIEW_SCORE_THRESHOLD)
|
|
1704
|
-
return [];
|
|
1705
|
-
this.independentReviewStarted = true;
|
|
1706
|
-
const taskName = `ideal-reviewer-${Math.random().toString(36).slice(2, 8)}`;
|
|
1707
|
-
let agentId;
|
|
1708
|
-
try {
|
|
1709
|
-
const task = buildReviewerTask({
|
|
1710
|
-
originalRequest: this.originalRequest,
|
|
1711
|
-
changedFiles: [...this.hookFileEditCounts.keys()],
|
|
1712
|
-
stats: this.hookStats,
|
|
1713
|
-
triggerReasons: decision.reasons,
|
|
1714
|
-
});
|
|
1715
|
-
// Active model forced at spawn time — never routed to a fast/review model.
|
|
1716
|
-
// The reviewer's own time limit ends it with a verdict on what it read;
|
|
1717
|
-
// the wait below is only a backstop against a hung child.
|
|
1718
|
-
const snapshot = await this.subAgentManager.spawn(taskName, task, undefined, {
|
|
1719
|
-
model: this.model,
|
|
1720
|
-
tools: REVIEWER_TOOLS,
|
|
1721
|
-
turnTimeoutMs: REVIEWER_TURN_TIMEOUT_MS,
|
|
1722
|
-
});
|
|
1723
|
-
agentId = snapshot.agent_id;
|
|
1724
|
-
const waited = await this.subAgentManager.wait([agentId], "all", REVIEWER_WAIT_MS);
|
|
1725
|
-
const agent = waited.agents[0];
|
|
1726
|
-
if (!agent || !isTerminalSubAgentState(agent.state)) {
|
|
1727
|
-
// Timeout: collect the straggler so the completion gate cannot fire on
|
|
1728
|
-
// it later, then fall back to the in-thread review.
|
|
1729
|
-
await this.subAgentManager.interrupt(agentId, true).catch(() => { });
|
|
1730
|
-
log("WARN", "ideal", "Independent reviewer timed out; falling back to in-thread review", {
|
|
1731
|
-
agentId,
|
|
1732
|
-
});
|
|
1733
|
-
return [];
|
|
1734
|
-
}
|
|
1735
|
-
const findings = parseReviewerFindings(agent.output ?? "");
|
|
1736
|
-
if (!findings) {
|
|
1737
|
-
log("WARN", "ideal", "Independent reviewer output unparseable; falling back", {
|
|
1738
|
-
agentId,
|
|
1739
|
-
state: agent.state,
|
|
1740
|
-
...(agent.error ? { error: agent.error } : {}),
|
|
1741
|
-
});
|
|
1742
|
-
return [];
|
|
1743
|
-
}
|
|
1744
|
-
if (findings.clean) {
|
|
1745
|
-
log("INFO", "ideal", "Independent reviewer verdict: clean", { agentId });
|
|
1746
|
-
return [];
|
|
1747
|
-
}
|
|
1748
|
-
log("INFO", "ideal", "Independent reviewer flagged findings", {
|
|
1749
|
-
agentId,
|
|
1750
|
-
count: String(findings.findings.length),
|
|
1751
|
-
});
|
|
1752
|
-
return [buildIndependentReviewMessage(findings.findings)];
|
|
1753
|
-
}
|
|
1754
|
-
catch (error) {
|
|
1755
|
-
if (agentId)
|
|
1756
|
-
await this.subAgentManager.interrupt(agentId, true).catch(() => { });
|
|
1757
|
-
log("WARN", "ideal", "Independent reviewer failed; falling back to in-thread review", {
|
|
1758
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1759
|
-
});
|
|
1760
|
-
return [];
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
1653
|
/**
|
|
1764
1654
|
* Turn-budget extension gate. The loop consults this instead of stopping
|
|
1765
1655
|
* mid-task when it exhausts `maxTurns`. Grant ONLY on evidence of progress —
|
|
@@ -1793,98 +1683,28 @@ export class AgentSession {
|
|
|
1793
1683
|
});
|
|
1794
1684
|
return granted;
|
|
1795
1685
|
}
|
|
1796
|
-
/**
|
|
1797
|
-
*
|
|
1798
|
-
*
|
|
1799
|
-
|
|
1800
|
-
*
|
|
1801
|
-
* The turn count is looked ahead by one on purpose. `hookStats.turns` only
|
|
1802
|
-
* advances at `turn_end`, so while the model is writing the draft the counter
|
|
1803
|
-
* still reads the PREVIOUS turn; the real gate sees one more. Without the
|
|
1804
|
-
* lookahead a run sitting on score 3 crosses to 4 on the draft's own
|
|
1805
|
-
* `turn_end` — after the text already streamed — which is precisely the
|
|
1806
|
-
* appear-then-vanish flash. Over-arming by one turn point costs only live
|
|
1807
|
-
* token streaming on a final answer that then shows whole; under-arming costs
|
|
1808
|
-
* the flash, so this errs toward arming.
|
|
1809
|
-
*/
|
|
1810
|
-
wouldInjectIdealReview() {
|
|
1686
|
+
/** Would a stop right now inject a pre-final follow-up? Only queued LSP
|
|
1687
|
+
* diagnostics (real errors injected below) or a mode-owned completion
|
|
1688
|
+
* review can, so clients hold the candidate answer only then. */
|
|
1689
|
+
wouldInjectBeforeFinal() {
|
|
1811
1690
|
if (this.opts.completionReview?.armed)
|
|
1812
1691
|
return true;
|
|
1813
|
-
|
|
1814
|
-
return false;
|
|
1815
|
-
// Mid-review a stop still injects: the coverage follow-up while files are
|
|
1816
|
-
// unread, or its escalation once the budget is spent. Both make the model
|
|
1817
|
-
// answer again, so the candidate answer is a draft exactly as it is before
|
|
1818
|
-
// the review starts — without arming here it paints and the reviewed answer
|
|
1819
|
-
// lands under it as a duplicate.
|
|
1820
|
-
if (this.idealReviewPhase === "reviewing") {
|
|
1821
|
-
return this.reviewCoverage.evidence().missing.length > 0;
|
|
1822
|
-
}
|
|
1823
|
-
if (this.idealReviewPhase !== "idle")
|
|
1824
|
-
return false;
|
|
1825
|
-
if (!this.settingsManager.get("idealReviewEnabled"))
|
|
1826
|
-
return false;
|
|
1827
|
-
if (evaluateIdealReview({ ...this.hookStats, turns: this.hookStats.turns + 1 }).shouldReview) {
|
|
1828
|
-
return true;
|
|
1829
|
-
}
|
|
1830
|
-
const files = this.hookFileEditCounts.size;
|
|
1831
|
-
if (files === 0)
|
|
1832
|
-
return false;
|
|
1833
|
-
if (this.idealDriftProbe?.files !== files) {
|
|
1834
|
-
this.idealDriftProbe = {
|
|
1835
|
-
files,
|
|
1836
|
-
drifted: detectTestDrift(this.hookFileEditCounts.keys(), this.cwd).length > 0,
|
|
1837
|
-
};
|
|
1838
|
-
}
|
|
1839
|
-
return this.idealDriftProbe.drifted;
|
|
1840
|
-
}
|
|
1841
|
-
/** Would a stop right now inject the verification gate? Same conditions as
|
|
1842
|
-
* the pre-stop branch below, so arming and injection cannot disagree. */
|
|
1843
|
-
wouldInjectVerification() {
|
|
1844
|
-
if (this.lspManager?.hasQueuedDiagnostics())
|
|
1845
|
-
return true;
|
|
1846
|
-
if (this.opts.selfCorrectionHooks === false)
|
|
1847
|
-
return false;
|
|
1848
|
-
if (!this.settingsManager.get("verificationGateEnabled"))
|
|
1849
|
-
return false;
|
|
1850
|
-
if (this.opts.allowedTools && !this.opts.allowedTools.includes("bash"))
|
|
1851
|
-
return false;
|
|
1852
|
-
return this.verificationGate.willInject();
|
|
1692
|
+
return this.lspManager?.hasQueuedDiagnostics() ?? false;
|
|
1853
1693
|
}
|
|
1854
1694
|
/** Broadcast pre-final hook arming on change. Both edges matter: armed=false
|
|
1855
|
-
* after the hook fires is what lets a client stream the
|
|
1856
|
-
*
|
|
1857
|
-
*
|
|
1858
|
-
* Callable before `initialize()`: the sidecar sets Ken's review suppression
|
|
1859
|
-
* on a freshly constructed session, and every arming predicate below reads
|
|
1860
|
-
* settings that `initialize()` has not loaded yet. Nothing can be armed
|
|
1861
|
-
* before the session can run a turn, and the first `tool_result`/`turn_end`
|
|
1862
|
-
* recomputes both edges — so skipping is the correct answer, not a patch. */
|
|
1695
|
+
* after the hook fires is what lets a client stream the final answer live
|
|
1696
|
+
* again. Callable before `initialize()`, when no manager exists yet. */
|
|
1863
1697
|
refreshHookArming() {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
this.refreshIdealReviewArmed();
|
|
1867
|
-
this.refreshVerificationArmed();
|
|
1868
|
-
}
|
|
1869
|
-
refreshVerificationArmed() {
|
|
1870
|
-
if (!this.settingsManager)
|
|
1698
|
+
const armed = this.wouldInjectBeforeFinal();
|
|
1699
|
+
if (armed === this.preFinalArmed)
|
|
1871
1700
|
return;
|
|
1872
|
-
|
|
1873
|
-
if (armed === this.verificationArmed)
|
|
1874
|
-
return;
|
|
1875
|
-
this.verificationArmed = armed;
|
|
1701
|
+
this.preFinalArmed = armed;
|
|
1876
1702
|
this.eventBus.emit("hook_armed", { kind: "verification", armed });
|
|
1877
1703
|
}
|
|
1878
|
-
refreshIdealReviewArmed() {
|
|
1879
|
-
const armed = this.wouldInjectIdealReview();
|
|
1880
|
-
if (armed === this.idealReviewArmed)
|
|
1881
|
-
return;
|
|
1882
|
-
this.idealReviewArmed = armed;
|
|
1883
|
-
this.eventBus.emit("hook_armed", { kind: "ideal", armed });
|
|
1884
|
-
}
|
|
1885
1704
|
/**
|
|
1886
|
-
* Pre-stop
|
|
1887
|
-
*
|
|
1705
|
+
* Pre-stop follow-ups: LSP errors, unread child agents and background
|
|
1706
|
+
* processes, and a mode-owned completion review. Verification evidence is
|
|
1707
|
+
* tracked passively for run status; it never forces another turn.
|
|
1888
1708
|
*/
|
|
1889
1709
|
async getHookFollowUpMessages() {
|
|
1890
1710
|
// Exit notifications and task_output refer to the same host process record.
|
|
@@ -1897,14 +1717,16 @@ export class AgentSession {
|
|
|
1897
1717
|
if (backgroundChanged)
|
|
1898
1718
|
await this.persistVerificationState();
|
|
1899
1719
|
// Edits return immediately; only the completion boundary waits for remaining
|
|
1900
|
-
// checks.
|
|
1720
|
+
// checks. Only real errors cost another turn: a timed-out or unavailable
|
|
1721
|
+
// server proves nothing either way, and run status already reports
|
|
1722
|
+
// unverified changes without making the model answer twice.
|
|
1901
1723
|
await this.lspManager?.flushDiagnostics(this.opts.signal);
|
|
1902
1724
|
if (this.opts.signal?.aborted)
|
|
1903
1725
|
return null;
|
|
1904
|
-
const diagnosticText = this.lspManager?.drainDiagnostics(
|
|
1726
|
+
const diagnosticText = this.lspManager?.drainDiagnostics(false);
|
|
1905
1727
|
if (diagnosticText)
|
|
1906
1728
|
this.eventBus.emit("diagnostics", { text: diagnosticText });
|
|
1907
|
-
this.
|
|
1729
|
+
this.refreshHookArming();
|
|
1908
1730
|
const diagnosticMessages = diagnosticText
|
|
1909
1731
|
? [
|
|
1910
1732
|
{
|
|
@@ -1928,33 +1750,6 @@ export class AgentSession {
|
|
|
1928
1750
|
});
|
|
1929
1751
|
return [...diagnosticMessages, ...processFollowUp];
|
|
1930
1752
|
}
|
|
1931
|
-
// Verification gate: code was edited but nothing verified since the last
|
|
1932
|
-
// edit. Above the Ideal review so checks RUN before the read-based review
|
|
1933
|
-
// starts; off for allow-listed sessions that cannot run commands at all.
|
|
1934
|
-
if (this.opts.selfCorrectionHooks !== false &&
|
|
1935
|
-
this.settingsManager.get("verificationGateEnabled") &&
|
|
1936
|
-
(!this.opts.allowedTools || this.opts.allowedTools.includes("bash"))) {
|
|
1937
|
-
const verificationReason = this.verificationGate.pendingReason();
|
|
1938
|
-
const verificationFollowUp = this.verificationGate.followUp();
|
|
1939
|
-
if (verificationFollowUp) {
|
|
1940
|
-
log("INFO", "verification-gate", "Injecting verification follow-up", {});
|
|
1941
|
-
// Announce, THEN disarm: clients release held text on disarm, so the
|
|
1942
|
-
// reverse order paints the draft and immediately deletes it — the exact
|
|
1943
|
-
// flash arming exists to prevent.
|
|
1944
|
-
this.eventBus.emit("hook", {
|
|
1945
|
-
kind: "verification",
|
|
1946
|
-
...(verificationReason === "tamper"
|
|
1947
|
-
? { verificationReason: "check_review" }
|
|
1948
|
-
: verificationReason === "recheck"
|
|
1949
|
-
? { verificationReason }
|
|
1950
|
-
: {}),
|
|
1951
|
-
});
|
|
1952
|
-
this.refreshHookArming();
|
|
1953
|
-
return [...diagnosticMessages, ...verificationFollowUp];
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
// Address real errors before review; unavailable checks share the existing
|
|
1957
|
-
// verification demand above instead of manufacturing a separate hook.
|
|
1958
1753
|
if (diagnosticMessages.length > 0)
|
|
1959
1754
|
return diagnosticMessages;
|
|
1960
1755
|
if (this.opts.completionReview) {
|
|
@@ -1973,122 +1768,7 @@ export class AgentSession {
|
|
|
1973
1768
|
}
|
|
1974
1769
|
this.refreshHookArming();
|
|
1975
1770
|
}
|
|
1976
|
-
|
|
1977
|
-
return null;
|
|
1978
|
-
if (this.idealReviewPhase === "reviewing") {
|
|
1979
|
-
const coverage = this.reviewCoverage.evidence();
|
|
1980
|
-
const lspEvidence = this.reviewLspEvidence(coverage.expected);
|
|
1981
|
-
log("INFO", "ideal", "Ideal review coverage check", {
|
|
1982
|
-
covered: coverage.covered,
|
|
1983
|
-
missing: coverage.missing,
|
|
1984
|
-
lspLowConfidence: lspEvidence.lowConfidence,
|
|
1985
|
-
lspMissing: lspEvidence.missing,
|
|
1986
|
-
});
|
|
1987
|
-
if (coverage.missing.length > 0) {
|
|
1988
|
-
// Announce like any other pre-final injection: this follow-up makes the
|
|
1989
|
-
// model answer again, so the answer it interrupts is a draft and the
|
|
1990
|
-
// hook event is what tells clients to discard it. Injecting silently is
|
|
1991
|
-
// what let the pre-coverage answer paint above the reviewed one.
|
|
1992
|
-
this.eventBus.emit("hook", {
|
|
1993
|
-
kind: "ideal",
|
|
1994
|
-
coverageExpected: coverage.expected,
|
|
1995
|
-
coverageMissing: coverage.missing,
|
|
1996
|
-
});
|
|
1997
|
-
if (this.reviewCoverageInjected < MAX_REVIEW_COVERAGE_INJECTIONS) {
|
|
1998
|
-
this.reviewCoverageInjected += 1;
|
|
1999
|
-
// Stays armed (coverage is still outstanding) — this call is here so a
|
|
2000
|
-
// client that missed the earlier edge is armed before the next draft.
|
|
2001
|
-
this.refreshIdealReviewArmed();
|
|
2002
|
-
return [
|
|
2003
|
-
this.withReviewLspEvidence(buildReviewCoverageMessage(coverage.missing), lspEvidence),
|
|
2004
|
-
];
|
|
2005
|
-
}
|
|
2006
|
-
// Budget spent: close the gate so the run cannot spin on a file that
|
|
2007
|
-
// never becomes readable, and require the gap be reported to the user.
|
|
2008
|
-
this.idealReviewPhase = "complete";
|
|
2009
|
-
// The gate is shut, so this is the real disarm: the answer to the
|
|
2010
|
-
// escalation is final and streams live.
|
|
2011
|
-
this.refreshIdealReviewArmed();
|
|
2012
|
-
log("INFO", "ideal", "Ideal review coverage escalated after retry budget", {
|
|
2013
|
-
injected: String(this.reviewCoverageInjected),
|
|
2014
|
-
missing: coverage.missing,
|
|
2015
|
-
});
|
|
2016
|
-
return [buildReviewCoverageEscalationMessage(coverage.missing)];
|
|
2017
|
-
}
|
|
2018
|
-
this.idealReviewPhase = "complete";
|
|
2019
|
-
return null;
|
|
2020
|
-
}
|
|
2021
|
-
if (this.idealReviewPhase === "complete")
|
|
2022
|
-
return null;
|
|
2023
|
-
if (!this.settingsManager.get("idealReviewEnabled"))
|
|
2024
|
-
return null;
|
|
2025
|
-
const decision = evaluateIdealReview(this.hookStats);
|
|
2026
|
-
// Test drift fires the review even on a small change the score would skip:
|
|
2027
|
-
// a green-but-stale test is exactly what the volume gate sleeps through.
|
|
2028
|
-
const driftedFiles = detectTestDrift(this.hookFileEditCounts.keys(), this.cwd).slice(0, 5);
|
|
2029
|
-
if (!decision.shouldReview && driftedFiles.length === 0)
|
|
2030
|
-
return null;
|
|
2031
|
-
// Independent reviewer first (async, bounded): its findings ride in the
|
|
2032
|
-
// SAME follow-up batch as the in-thread review + coverage requirements, so
|
|
2033
|
-
// addressing everything still costs one extra turn.
|
|
2034
|
-
this.reviewCoverage.start(this.hookFileEditCounts.keys());
|
|
2035
|
-
this.idealReviewPhase = "reviewing";
|
|
2036
|
-
const coverage = this.reviewCoverage.evidence();
|
|
2037
|
-
const lspEvidence = this.reviewLspEvidence(coverage.expected);
|
|
2038
|
-
this.eventBus.emit("hook", {
|
|
2039
|
-
kind: "ideal",
|
|
2040
|
-
coverageExpected: coverage.expected,
|
|
2041
|
-
coverageMissing: coverage.missing,
|
|
2042
|
-
});
|
|
2043
|
-
// Recompute strictly AFTER the hook event: clients release held text on
|
|
2044
|
-
// disarm, so the reverse order would paint the draft and then delete it —
|
|
2045
|
-
// the exact flash arming exists to prevent. Arming normally PERSISTS here,
|
|
2046
|
-
// because review starts with every changed file uncovered and a stop while
|
|
2047
|
-
// coverage is outstanding injects again. Disarm lands later, on the read
|
|
2048
|
-
// that closes the last gap (or when the retry budget escalates).
|
|
2049
|
-
this.refreshIdealReviewArmed();
|
|
2050
|
-
// Announce the phase before the reviewer starts, not after its bounded wait.
|
|
2051
|
-
const independentMessages = await this.runIndependentReview(decision);
|
|
2052
|
-
log("INFO", "ideal", "Injecting ideal review before final response", {
|
|
2053
|
-
coverageExpected: coverage.expected,
|
|
2054
|
-
coverageMissing: coverage.missing,
|
|
2055
|
-
lspLowConfidence: lspEvidence.lowConfidence,
|
|
2056
|
-
lspMissing: lspEvidence.missing,
|
|
2057
|
-
});
|
|
2058
|
-
return [
|
|
2059
|
-
...independentMessages,
|
|
2060
|
-
this.withReviewLspEvidence(withReviewCoverageRequirements(buildIdealReviewMessage(decision.reasons, driftedFiles), coverage.missing), lspEvidence),
|
|
2061
|
-
];
|
|
2062
|
-
}
|
|
2063
|
-
reviewLspEvidence(files) {
|
|
2064
|
-
const lowConfidence = [];
|
|
2065
|
-
const missing = [];
|
|
2066
|
-
for (const filePath of files) {
|
|
2067
|
-
const outcome = this.lspManager?.getLatestOutcome(filePath);
|
|
2068
|
-
if (outcome?.kind === "low_confidence")
|
|
2069
|
-
lowConfidence.push(filePath);
|
|
2070
|
-
else if (outcome?.kind !== "clean" && outcome?.kind !== "diagnostics")
|
|
2071
|
-
missing.push(filePath);
|
|
2072
|
-
}
|
|
2073
|
-
return { lowConfidence, missing };
|
|
2074
|
-
}
|
|
2075
|
-
withReviewLspEvidence(message, evidence) {
|
|
2076
|
-
if (evidence.lowConfidence.length === 0 && evidence.missing.length === 0)
|
|
2077
|
-
return message;
|
|
2078
|
-
const notes = [
|
|
2079
|
-
...(evidence.lowConfidence.length > 0
|
|
2080
|
-
? [`Diagnostics are low confidence while indexing: ${evidence.lowConfidence.join(", ")}.`]
|
|
2081
|
-
: []),
|
|
2082
|
-
...(evidence.missing.length > 0
|
|
2083
|
-
? [`Diagnostics evidence is unavailable or missing: ${evidence.missing.join(", ")}.`]
|
|
2084
|
-
: []),
|
|
2085
|
-
"Do not describe those files as compiler-clean without other evidence.",
|
|
2086
|
-
];
|
|
2087
|
-
return {
|
|
2088
|
-
role: "user",
|
|
2089
|
-
provenance: message.provenance,
|
|
2090
|
-
content: `${String(message.content)}\n\n${notes.join(" ")}`,
|
|
2091
|
-
};
|
|
1771
|
+
return null;
|
|
2092
1772
|
}
|
|
2093
1773
|
/** Wraps the real run: cancels an in-flight cache prewarm and tracks run
|
|
2094
1774
|
* activity / last real request time for {@link prewarm}. */
|
|
@@ -3263,21 +2943,6 @@ export class AgentSession {
|
|
|
3263
2943
|
getPlanMode() {
|
|
3264
2944
|
return this.planModeRef.current;
|
|
3265
2945
|
}
|
|
3266
|
-
/**
|
|
3267
|
-
* Suppress only the pre-final Ideal self-review for this live session.
|
|
3268
|
-
* Autopilot uses this while Ken independently owns verification; loop-break
|
|
3269
|
-
* and post-compaction re-grounding remain active.
|
|
3270
|
-
*/
|
|
3271
|
-
setIdealReviewSuppressed(suppressed) {
|
|
3272
|
-
this.idealReviewSuppressed = suppressed;
|
|
3273
|
-
if (suppressed) {
|
|
3274
|
-
this.idealReviewPhase = "idle";
|
|
3275
|
-
this.reviewCoverage.reset();
|
|
3276
|
-
}
|
|
3277
|
-
// Suppression flips mid-run (autopilot takes over verification), so a client
|
|
3278
|
-
// holding a draft under a stale arming must be released.
|
|
3279
|
-
this.refreshHookArming();
|
|
3280
|
-
}
|
|
3281
2946
|
/** Queue a user message (optionally with attachments) to be injected mid-run
|
|
3282
2947
|
* as steering. Returns the new queue length. No-op semantics are the caller's
|
|
3283
2948
|
* concern. */
|
|
@@ -3339,6 +3004,15 @@ export class AgentSession {
|
|
|
3339
3004
|
return `No background process with id "${id}"`;
|
|
3340
3005
|
return this.processManager.stop(id);
|
|
3341
3006
|
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Force-stop every background process tree, synchronously. Background
|
|
3009
|
+
* commands run in their own process group, so the daemon's group kill on
|
|
3010
|
+
* quit never reaches them: this is the only thing that does. Callers on a
|
|
3011
|
+
* shutdown deadline run it before awaiting anything that can hang.
|
|
3012
|
+
*/
|
|
3013
|
+
stopBackgroundProcesses() {
|
|
3014
|
+
this.processManager?.shutdownAll();
|
|
3015
|
+
}
|
|
3342
3016
|
/** Replace a host-owned system prompt in place without resetting conversation history. */
|
|
3343
3017
|
setCustomSystemPrompt(systemPrompt, promptCacheKeyPrefix) {
|
|
3344
3018
|
this.customSystemPrompt = systemPrompt;
|
|
@@ -3754,7 +3428,7 @@ export class AgentSession {
|
|
|
3754
3428
|
? await captureVerificationSnapshot(this.opts.cwd, [...this.hookFileEditCounts.keys()])
|
|
3755
3429
|
: null;
|
|
3756
3430
|
if (after === null || after !== check.sourceSnapshot) {
|
|
3757
|
-
this.verificationGate.requireFreshVerification(true
|
|
3431
|
+
this.verificationGate.requireFreshVerification(true);
|
|
3758
3432
|
this.verificationGate.recordRejectedCheck(check.command, after === null
|
|
3759
3433
|
? "Workspace inputs could not be compared; run a read-only check after the command"
|
|
3760
3434
|
: "Command changed workspace inputs; run checks against the changed source");
|
|
@@ -4151,6 +3825,9 @@ export class AgentSession {
|
|
|
4151
3825
|
return this.getPromptCacheKey();
|
|
4152
3826
|
}
|
|
4153
3827
|
async dispose() {
|
|
3828
|
+
// First and synchronous: nothing below may delay this, or a hung teardown
|
|
3829
|
+
// step leaves background commands running after the app has quit.
|
|
3830
|
+
this.stopBackgroundProcesses();
|
|
4154
3831
|
// Quiesce any in-flight post-turn compaction BEFORE tearing down state:
|
|
4155
3832
|
// the background compact() snapshots and replaces `this.messages`, so
|
|
4156
3833
|
// letting it run past this point would checkpoint a near-empty history
|
|
@@ -4160,8 +3837,10 @@ export class AgentSession {
|
|
|
4160
3837
|
this.cacheDiagnostics.reset();
|
|
4161
3838
|
this.diagnosticsRecorder?.finalize();
|
|
4162
3839
|
this.managerAbortSignal?.removeEventListener("abort", this.managerAbortHandler);
|
|
4163
|
-
|
|
3840
|
+
// Again, in case a turn racing teardown started one while we awaited.
|
|
3841
|
+
this.stopBackgroundProcesses();
|
|
4164
3842
|
this.lspManager?.shutdownAll();
|
|
3843
|
+
this.debugManager?.shutdown();
|
|
4165
3844
|
await Promise.all([this.subAgentManager?.shutdownAll(), this.mcpManager?.dispose()]);
|
|
4166
3845
|
await this.extensionLoader.deactivateAll();
|
|
4167
3846
|
this.setSessionPath("");
|