@kenkaiiii/ggcoder 5.70.0 → 5.72.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 +70 -0
- package/assets/motion/skills/before-after/SKILL.md +57 -0
- package/assets/motion/skills/brand-kit/SKILL.md +3 -3
- package/assets/motion/skills/dev-tool-video/SKILL.md +61 -0
- package/assets/motion/skills/launch-video/SKILL.md +66 -0
- package/assets/motion/skills/match-reference/SKILL.md +62 -0
- package/assets/motion/skills/motion/SKILL.md +148 -98
- package/assets/motion/skills/source-ingest/SKILL.md +17 -6
- package/assets/motion/skills/website-video/SKILL.md +76 -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 +48 -33
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli/thinking-arg.d.ts +6 -0
- package/dist/cli/thinking-arg.js +13 -0
- package/dist/cli/thinking-arg.js.map +1 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +1 -14
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts +25 -71
- package/dist/core/agent-session.js +79 -427
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/codex-request-shape.d.ts +12 -0
- package/dist/core/codex-request-shape.js +31 -0
- package/dist/core/codex-request-shape.js.map +1 -0
- package/dist/core/mcp/content.js +6 -2
- package/dist/core/mcp/content.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 +10 -1
- package/dist/core/settings-manager.js +12 -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/verification-gate.d.ts +8 -100
- package/dist/core/verification-gate.js +26 -260
- package/dist/core/verification-gate.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 +16 -20
- 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 +4 -1
- package/dist/system-prompt.js.map +1 -1
- 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/edit.js +12 -4
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/index.d.ts +7 -1
- package/dist/tools/index.js +5 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/prompt-hints.js +4 -1
- 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/subagent.d.ts +6 -0
- package/dist/tools/subagent.js +13 -2
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/tool-tiers.d.ts +6 -0
- package/dist/tools/tool-tiers.js +7 -1
- 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,11 +24,12 @@ 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 { formatImpactForVerification } from "./test-impact.js";
|
|
28
27
|
import { autoBackgroundedId } from "../tools/bash.js";
|
|
29
28
|
import { buildProcessCompletionFollowUp } from "./process-gate.js";
|
|
30
|
-
import { buildSubAgentCompletionFollowUp
|
|
29
|
+
import { buildSubAgentCompletionFollowUp } from "./subagent-manager.js";
|
|
31
30
|
import { applyAsyncSubagentPolicy } from "./subagent-policy.js";
|
|
31
|
+
import { resolveResponsesLite, resolveStrictTools, } from "./codex-request-shape.js";
|
|
32
|
+
import { subAgentDescription } from "../tools/subagent.js";
|
|
32
33
|
import { z } from "zod";
|
|
33
34
|
import { MCPClientManager, getAllMcpServers } from "./mcp/index.js";
|
|
34
35
|
import { clampMcpToolDescription, DeferredToolCatalog } from "./mcp/deferred-catalog.js";
|
|
@@ -52,15 +53,13 @@ import { pruneStaleToolResults } from "./compaction/tool-result-pruner.js";
|
|
|
52
53
|
import { discoverAgents } from "./agents.js";
|
|
53
54
|
import { enhancePrompt } from "../utils/prompt-enhancer.js";
|
|
54
55
|
import { detectLanguages, detectProjectStack } from "./language-detector.js";
|
|
55
|
-
import { evaluateIdealReview, buildIdealReviewMessage, buildReviewCoverageEscalationMessage, buildReviewCoverageMessage, MAX_REVIEW_COVERAGE_INJECTIONS, withReviewCoverageRequirements, detectTestDrift, ReviewCoverageTracker, } from "./ideal-review.js";
|
|
56
56
|
import { evaluateLoopBreak, buildLoopBreakMessage, CycleDetector, ToolCallProgressTracker, detectTextRepetition, } from "./loop-breaker.js";
|
|
57
57
|
import { buildRegroundingMessage, requestTextForRegrounding } from "./regrounding.js";
|
|
58
58
|
import { buildSemanticLoopJudgePrompt, buildSemanticLoopMessage, MAX_SEMANTIC_LOOP_CALLS, parseSemanticLoopVerdict, shouldRunSemanticLoopCheck, SEMANTIC_LOOP_JUDGE_TIMEOUT_MS, withJudgeTimeout, } from "./semantic-loop-check.js";
|
|
59
|
-
import { buildIndependentReviewMessage, buildReviewerTask, INDEPENDENT_REVIEW_SCORE_THRESHOLD, parseReviewerFindings, REVIEWER_TOOLS, REVIEWER_TURN_TIMEOUT_MS, REVIEWER_WAIT_MS, } from "./ideal-review-subagent.js";
|
|
60
59
|
import { buildEnvDeltaMessage } from "./env-delta.js";
|
|
61
60
|
import { wrapSteeringText, buildNotificationSteeringText, STEERING_PREFIX } from "./steering.js";
|
|
62
61
|
import { AgentNotificationQueue } from "./agent-notifications.js";
|
|
63
|
-
import { VerificationGate,
|
|
62
|
+
import { VerificationGate, isCheckOwnFile, isCodeFilePath, VERIFICATION_STATE_KIND, isVerificationCommand, } from "./verification-gate.js";
|
|
64
63
|
import { classifyVerificationCommand } from "./verification-evidence.js";
|
|
65
64
|
import { captureVerificationSnapshot } from "./verification-snapshot.js";
|
|
66
65
|
import { findUserSessionPrompt, getUserSessionPrompt } from "./session-preview.js";
|
|
@@ -75,14 +74,6 @@ import path from "node:path";
|
|
|
75
74
|
* progressing — refuse to extend its turn budget.
|
|
76
75
|
*/
|
|
77
76
|
const TURN_EXTENSION_MAX_FAILURE_RATIO = 0.5;
|
|
78
|
-
/** Terminal subagent states — mirrors SubAgentManager's private isTerminal. */
|
|
79
|
-
function isTerminalSubAgentState(state) {
|
|
80
|
-
return (state === "completed" ||
|
|
81
|
-
state === "failed" ||
|
|
82
|
-
state === "interrupted" ||
|
|
83
|
-
state === "closed" ||
|
|
84
|
-
state === "reaped");
|
|
85
|
-
}
|
|
86
77
|
// ── Tool-result policy ─────────────────────────────────────
|
|
87
78
|
/** Resolve the per-result cap passed to the agent loop for the active transport. */
|
|
88
79
|
export function resolveSessionToolResultCharLimit(model, provider, accountId) {
|
|
@@ -151,20 +142,13 @@ export class AgentSession {
|
|
|
151
142
|
/** Forgets every file read; called whenever the conversation is replaced or
|
|
152
143
|
* rewound, so the model must re-read a file before changing it. */
|
|
153
144
|
clearReadTracker;
|
|
145
|
+
recordBashReads;
|
|
154
146
|
skills = [];
|
|
155
147
|
cacheKeyLogged = false;
|
|
156
148
|
// ── Self-correction hook state (mirrors the TUI's useAgentLoop refs) ──
|
|
157
149
|
// Reset at the start of every run; observed from the event stream; read by
|
|
158
|
-
// the loop-break (mid-loop)
|
|
159
|
-
hookStats = {
|
|
160
|
-
changedLines: 0,
|
|
161
|
-
toolCalls: 0,
|
|
162
|
-
toolFailures: 0,
|
|
163
|
-
turns: 0,
|
|
164
|
-
writeCalls: 0,
|
|
165
|
-
editCalls: 0,
|
|
166
|
-
bashCalls: 0,
|
|
167
|
-
};
|
|
150
|
+
// the loop-break (mid-loop) callback.
|
|
151
|
+
hookStats = { toolCalls: 0, toolFailures: 0, turns: 0 };
|
|
168
152
|
hookText = "";
|
|
169
153
|
hookConsecutiveFailures = 0;
|
|
170
154
|
hookRepeatedNoProgressCalls = 0;
|
|
@@ -174,20 +158,6 @@ export class AgentSession {
|
|
|
174
158
|
hookFileEditCounts = new Map();
|
|
175
159
|
hookToolCalls = new Map();
|
|
176
160
|
backgroundVerification = new Map();
|
|
177
|
-
idealReviewPhase = "idle";
|
|
178
|
-
/** Runtime-only suppression while Ken owns verification in autopilot mode. */
|
|
179
|
-
idealReviewSuppressed = false;
|
|
180
|
-
/** Mirror of the last `hook_armed` value broadcast this run, so the event
|
|
181
|
-
* fires only on a real edge. */
|
|
182
|
-
idealReviewArmed = false;
|
|
183
|
-
/** Cached test-drift probe, keyed by the size of the edited-file set. Drift
|
|
184
|
-
* depends only on WHICH files were edited and that set only grows, so this
|
|
185
|
-
* keeps the arming check off the filesystem on most tool results — the probe
|
|
186
|
-
* is several sync existsSync calls per edited file. */
|
|
187
|
-
idealDriftProbe = null;
|
|
188
|
-
reviewCoverage;
|
|
189
|
-
/** Coverage follow-ups spent this run, capped by MAX_REVIEW_COVERAGE_INJECTIONS. */
|
|
190
|
-
reviewCoverageInjected = 0;
|
|
191
161
|
/** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */
|
|
192
162
|
loopBreakInjected = 0;
|
|
193
163
|
regroundingInjected = false;
|
|
@@ -197,8 +167,6 @@ export class AgentSession {
|
|
|
197
167
|
* injection at the next steering poll; judge failures fail open (no
|
|
198
168
|
* injection) and still consume budget + cooldown. */
|
|
199
169
|
semanticLoop = { checksUsed: 0, lastCheckTurn: 0, pending: false, verdict: null, injected: false };
|
|
200
|
-
/** Independent Ideal reviewer spawned once per run (score-gated). */
|
|
201
|
-
independentReviewStarted = false;
|
|
202
170
|
/**
|
|
203
171
|
* The environment as the cached system prompt currently describes it.
|
|
204
172
|
* Re-recorded on every prompt build, so a rebuild (e.g. `/add-dir`) needs no
|
|
@@ -209,11 +177,11 @@ export class AgentSession {
|
|
|
209
177
|
runStartedAt = 0;
|
|
210
178
|
/** Gate injections spent this run, capped by MAX_PROCESS_GATE_INJECTIONS. */
|
|
211
179
|
processGateInjected = 0;
|
|
212
|
-
/** Verification
|
|
180
|
+
/** Verification evidence: code edited this run, and what has proved it since.
|
|
181
|
+
* Passive tracking only — it feeds run status and autopilot, never a turn. */
|
|
213
182
|
verificationGate = new VerificationGate();
|
|
214
|
-
/** Mirror of the last
|
|
215
|
-
|
|
216
|
-
verificationArmed = false;
|
|
183
|
+
/** Mirror of the last `hook_armed` value, so the event fires only on an edge. */
|
|
184
|
+
preFinalArmed = false;
|
|
217
185
|
compactionOccurred = false;
|
|
218
186
|
/**
|
|
219
187
|
* Re-grounding carry-over for post-turn compaction. `resetHookState` clears
|
|
@@ -264,7 +232,6 @@ export class AgentSession {
|
|
|
264
232
|
steeringListeners = new Set();
|
|
265
233
|
processManager;
|
|
266
234
|
lspManager;
|
|
267
|
-
testImpact;
|
|
268
235
|
debugManager;
|
|
269
236
|
subAgentManager;
|
|
270
237
|
/**
|
|
@@ -369,7 +336,6 @@ export class AgentSession {
|
|
|
369
336
|
this.provider = options.provider;
|
|
370
337
|
this.model = options.model;
|
|
371
338
|
this.cwd = options.cwd;
|
|
372
|
-
this.reviewCoverage = new ReviewCoverageTracker(this.cwd);
|
|
373
339
|
this.baseUrl = options.baseUrl;
|
|
374
340
|
this.maxTokens = this.resolveMaxTokens(options.model);
|
|
375
341
|
this.thinkingLevel = options.thinkingLevel;
|
|
@@ -435,7 +401,7 @@ export class AgentSession {
|
|
|
435
401
|
: this.opts.globalSubagents
|
|
436
402
|
? await discoverAgents({ globalAgentsDir: paths.agentsDir })
|
|
437
403
|
: [];
|
|
438
|
-
const { tools: builtInTools, processManager, rebuildReadTool, clearReadTracker,
|
|
404
|
+
const { tools: builtInTools, processManager, rebuildReadTool, clearReadTracker, recordBashReads, lspManager, debugManager, subAgentManager, } = await createTools(this.cwd, {
|
|
439
405
|
agents,
|
|
440
406
|
skills: this.skills,
|
|
441
407
|
contextLimits: this.contextLimits,
|
|
@@ -466,11 +432,9 @@ export class AgentSession {
|
|
|
466
432
|
}),
|
|
467
433
|
getUseExternalGrep: () => this.settingsManager.get("grepUseRipgrep"),
|
|
468
434
|
authStorage: this.authStorage,
|
|
469
|
-
onFileRead: (filePath) => this.reviewCoverage.recordRead(filePath),
|
|
470
435
|
onFileMutated: (filePath) => {
|
|
471
436
|
const relative = path.relative(this.cwd, filePath) || path.basename(filePath);
|
|
472
437
|
this.hookFileEditCounts.set(relative, (this.hookFileEditCounts.get(relative) ?? 0) + 1);
|
|
473
|
-
this.reviewCoverage.recordChanged(filePath);
|
|
474
438
|
},
|
|
475
439
|
// Lazy — sessionId/model/provider can change after createTools() runs, so
|
|
476
440
|
// sub-agent spawns read the current parent state at execution time.
|
|
@@ -506,6 +470,11 @@ export class AgentSession {
|
|
|
506
470
|
// a hallucinated call can't mutate the repo — and buildSystemPrompt below is
|
|
507
471
|
// fed the same filtered names so the Tools section matches exactly.
|
|
508
472
|
this.tools = this.opts.allowedTools ? tools.filter((t) => this.isToolAllowed(t.name)) : tools;
|
|
473
|
+
// Both delegation tools carry the agent roster; when both survive the
|
|
474
|
+
// allow-list, `subagent` points at `spawn_agent`'s copy instead.
|
|
475
|
+
if (this.tools.some((t) => t.name === "spawn_agent")) {
|
|
476
|
+
this.tools = this.tools.map((t) => t.name === "subagent" ? { ...t, description: subAgentDescription(agents, true) } : t);
|
|
477
|
+
}
|
|
509
478
|
// Tier the built-ins: rarely reached schemas move into the tool_search
|
|
510
479
|
// catalog and cost one hint line each instead of a full parameter schema on
|
|
511
480
|
// every request. Allow-listed sessions keep the eager path — their fixed
|
|
@@ -526,9 +495,9 @@ export class AgentSession {
|
|
|
526
495
|
}
|
|
527
496
|
this.rebuildReadTool = rebuildReadTool;
|
|
528
497
|
this.clearReadTracker = clearReadTracker;
|
|
498
|
+
this.recordBashReads = recordBashReads;
|
|
529
499
|
this.processManager = processManager;
|
|
530
500
|
this.lspManager = lspManager;
|
|
531
|
-
this.testImpact = testImpact;
|
|
532
501
|
this.debugManager = debugManager;
|
|
533
502
|
this.subAgentManager = subAgentManager;
|
|
534
503
|
this.bindManagerCancellation(this.opts.signal);
|
|
@@ -1116,15 +1085,7 @@ export class AgentSession {
|
|
|
1116
1085
|
resetHookState(originalRequest) {
|
|
1117
1086
|
this.opts.completionReview?.begin(originalRequest);
|
|
1118
1087
|
this.lspManager?.clearPendingDiagnostics();
|
|
1119
|
-
this.hookStats = {
|
|
1120
|
-
changedLines: 0,
|
|
1121
|
-
toolCalls: 0,
|
|
1122
|
-
toolFailures: 0,
|
|
1123
|
-
turns: 0,
|
|
1124
|
-
writeCalls: 0,
|
|
1125
|
-
editCalls: 0,
|
|
1126
|
-
bashCalls: 0,
|
|
1127
|
-
};
|
|
1088
|
+
this.hookStats = { toolCalls: 0, toolFailures: 0, turns: 0 };
|
|
1128
1089
|
this.hookText = "";
|
|
1129
1090
|
this.hookConsecutiveFailures = 0;
|
|
1130
1091
|
this.hookRepeatedNoProgressCalls = 0;
|
|
@@ -1133,13 +1094,6 @@ export class AgentSession {
|
|
|
1133
1094
|
this.hookCyclicPattern = null;
|
|
1134
1095
|
this.hookFileEditCounts.clear();
|
|
1135
1096
|
this.hookToolCalls.clear();
|
|
1136
|
-
this.reviewCoverage.reset();
|
|
1137
|
-
this.reviewCoverageInjected = 0;
|
|
1138
|
-
this.idealReviewPhase = "idle";
|
|
1139
|
-
// No event here: clients reset their own hold on run_start.
|
|
1140
|
-
this.idealReviewArmed = false;
|
|
1141
|
-
this.verificationArmed = false;
|
|
1142
|
-
this.idealDriftProbe = null;
|
|
1143
1097
|
this.loopBreakInjected = 0;
|
|
1144
1098
|
this.regroundingInjected = false;
|
|
1145
1099
|
this.hookRecentCalls = [];
|
|
@@ -1150,7 +1104,6 @@ export class AgentSession {
|
|
|
1150
1104
|
verdict: null,
|
|
1151
1105
|
injected: false,
|
|
1152
1106
|
};
|
|
1153
|
-
this.independentReviewStarted = false;
|
|
1154
1107
|
this.runStartedAt = Date.now();
|
|
1155
1108
|
this.processGateInjected = 0;
|
|
1156
1109
|
this.verificationGate.beginRun();
|
|
@@ -1171,8 +1124,8 @@ export class AgentSession {
|
|
|
1171
1124
|
}
|
|
1172
1125
|
/**
|
|
1173
1126
|
* Fold one agent event into the hook stat accumulators. Pure bookkeeping —
|
|
1174
|
-
* the same signals the TUI's useAgentLoop collects, so
|
|
1175
|
-
*
|
|
1127
|
+
* the same signals the TUI's useAgentLoop collects, so loop-break decisions
|
|
1128
|
+
* match across the CLI and the app.
|
|
1176
1129
|
*/
|
|
1177
1130
|
async trackHookEvent(event) {
|
|
1178
1131
|
if (this.opts.completionReview) {
|
|
@@ -1215,7 +1168,7 @@ export class AgentSession {
|
|
|
1215
1168
|
...this.hookFileEditCounts.keys(),
|
|
1216
1169
|
]);
|
|
1217
1170
|
if (call.sourceSnapshot === null)
|
|
1218
|
-
this.verificationGate.requireFreshVerification(true
|
|
1171
|
+
this.verificationGate.requireFreshVerification(true);
|
|
1219
1172
|
}
|
|
1220
1173
|
else if ((classification.accepted && event.args.persist !== true) ||
|
|
1221
1174
|
(!classification.accepted && classification.mayMutate)) {
|
|
@@ -1225,7 +1178,7 @@ export class AgentSession {
|
|
|
1225
1178
|
// or a persistent-shell run records nothing at the end, so
|
|
1226
1179
|
// flagging it left verified work Unverified forever — and
|
|
1227
1180
|
// autopilot silently refused every later turn.
|
|
1228
|
-
this.verificationGate.requireFreshVerification(!classification.accepted && classification.mayMutate
|
|
1181
|
+
this.verificationGate.requireFreshVerification(!classification.accepted && classification.mayMutate);
|
|
1229
1182
|
}
|
|
1230
1183
|
await this.persistVerificationState();
|
|
1231
1184
|
}
|
|
@@ -1238,12 +1191,6 @@ export class AgentSession {
|
|
|
1238
1191
|
this.hookStats.toolCalls += 1;
|
|
1239
1192
|
if (event.isError)
|
|
1240
1193
|
this.hookStats.toolFailures += 1;
|
|
1241
|
-
if (name === "write")
|
|
1242
|
-
this.hookStats.writeCalls += 1;
|
|
1243
|
-
if (name === "edit")
|
|
1244
|
-
this.hookStats.editCalls += 1;
|
|
1245
|
-
if (name === "bash")
|
|
1246
|
-
this.hookStats.bashCalls += 1;
|
|
1247
1194
|
this.hookConsecutiveFailures = event.isError ? this.hookConsecutiveFailures + 1 : 0;
|
|
1248
1195
|
this.hookRepeatedNoProgressCalls = this.hookProgressTracker.record(name, args, event.result, event.isError);
|
|
1249
1196
|
this.hookCyclicPattern = this.hookCycleDetector.record(name, args, event.result, event.isError);
|
|
@@ -1260,12 +1207,6 @@ export class AgentSession {
|
|
|
1260
1207
|
if (this.hookRecentCalls.length > MAX_SEMANTIC_LOOP_CALLS) {
|
|
1261
1208
|
this.hookRecentCalls.splice(0, this.hookRecentCalls.length - MAX_SEMANTIC_LOOP_CALLS);
|
|
1262
1209
|
}
|
|
1263
|
-
if (name === "edit" && !event.isError) {
|
|
1264
|
-
const diff = event.details?.diff ?? event.result;
|
|
1265
|
-
const added = (diff.match(/^\+[^+]/gm) ?? []).length;
|
|
1266
|
-
const removed = (diff.match(/^-[^-]/gm) ?? []).length;
|
|
1267
|
-
this.hookStats.changedLines += added + removed;
|
|
1268
|
-
}
|
|
1269
1210
|
// Only host-observed successful mutations and trustworthy check results
|
|
1270
1211
|
// affect approval. The model's text is never evidence.
|
|
1271
1212
|
let verificationChanged = false;
|
|
@@ -1273,13 +1214,10 @@ export class AgentSession {
|
|
|
1273
1214
|
if (name === "edit" || name === "write") {
|
|
1274
1215
|
const filePath = String(args.file_path ?? "");
|
|
1275
1216
|
// Check-owning files (tsconfig.json, pytest.ini, vitest.config.ts …)
|
|
1276
|
-
// are tracked even when they are not source code: editing one
|
|
1277
|
-
//
|
|
1217
|
+
// are tracked even when they are not source code: editing one
|
|
1218
|
+
// invalidates earlier check results.
|
|
1278
1219
|
if (filePath && (isCodeFilePath(filePath) || isCheckOwnFile(filePath))) {
|
|
1279
|
-
|
|
1280
|
-
? String(args.content ?? "")
|
|
1281
|
-
: extractAddedLines(event.details?.diff ?? event.result);
|
|
1282
|
-
this.verificationGate.recordMutation(filePath, addedText);
|
|
1220
|
+
this.verificationGate.recordMutation(filePath);
|
|
1283
1221
|
verificationChanged = true;
|
|
1284
1222
|
}
|
|
1285
1223
|
}
|
|
@@ -1307,7 +1245,7 @@ export class AgentSession {
|
|
|
1307
1245
|
: {}),
|
|
1308
1246
|
});
|
|
1309
1247
|
else if (classification.snapshotEligible)
|
|
1310
|
-
this.verificationGate.requireFreshVerification(true
|
|
1248
|
+
this.verificationGate.requireFreshVerification(true);
|
|
1311
1249
|
}
|
|
1312
1250
|
else if (args.persist === true) {
|
|
1313
1251
|
// Persistent-shell checks are not bounded evidence (steering can
|
|
@@ -1365,10 +1303,35 @@ export class AgentSession {
|
|
|
1365
1303
|
// Tool results for this step are in the array and their side effects
|
|
1366
1304
|
// already hit the filesystem. Flushing here is what makes a crash lose
|
|
1367
1305
|
// at most the in-flight step instead of the entire turn.
|
|
1306
|
+
await this.creditBashReads();
|
|
1368
1307
|
await this.flushPendingMessages();
|
|
1369
1308
|
break;
|
|
1370
1309
|
}
|
|
1371
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Count full-file `cat` output from the step that just finished as reads, so
|
|
1313
|
+
* an edit after `cat` does not cost a second read. Uses the step's results as
|
|
1314
|
+
* stored in the transcript (after per-turn trimming): only bytes the model
|
|
1315
|
+
* actually received count.
|
|
1316
|
+
*/
|
|
1317
|
+
async creditBashReads() {
|
|
1318
|
+
if (!this.recordBashReads)
|
|
1319
|
+
return;
|
|
1320
|
+
const messages = this.activeLoopMessages ?? this.messages;
|
|
1321
|
+
const toolMessage = messages.at(-1);
|
|
1322
|
+
const assistant = messages.at(-2);
|
|
1323
|
+
if (toolMessage?.role !== "tool" || assistant?.role !== "assistant")
|
|
1324
|
+
return;
|
|
1325
|
+
if (typeof assistant.content === "string")
|
|
1326
|
+
return;
|
|
1327
|
+
const calls = assistant.content.filter((part) => part.type === "tool_call");
|
|
1328
|
+
try {
|
|
1329
|
+
await this.recordBashReads(calls, toolMessage.content);
|
|
1330
|
+
}
|
|
1331
|
+
catch (err) {
|
|
1332
|
+
log("WARN", "agent-session", "Crediting bash reads failed", { error: String(err) });
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1372
1335
|
/**
|
|
1373
1336
|
* Append every message added since the last flush to the session file.
|
|
1374
1337
|
*
|
|
@@ -1431,7 +1394,7 @@ export class AgentSession {
|
|
|
1431
1394
|
const diagnosticText = this.lspManager?.drainDiagnostics(this.getVerificationProblem() !== null, { deferUnverified: true });
|
|
1432
1395
|
if (diagnosticText)
|
|
1433
1396
|
this.eventBus.emit("diagnostics", { text: diagnosticText });
|
|
1434
|
-
this.
|
|
1397
|
+
this.refreshHookArming();
|
|
1435
1398
|
const notified = this.notifications.drain();
|
|
1436
1399
|
const notificationMessage = notified.length > 0 || diagnosticText
|
|
1437
1400
|
? {
|
|
@@ -1494,6 +1457,7 @@ export class AgentSession {
|
|
|
1494
1457
|
}
|
|
1495
1458
|
if (this.opts.selfCorrectionHooks === false)
|
|
1496
1459
|
return null;
|
|
1460
|
+
// Legacy key: the user-facing switch for loop-break and re-grounding nudges.
|
|
1497
1461
|
if (!this.settingsManager.get("idealReviewEnabled"))
|
|
1498
1462
|
return null;
|
|
1499
1463
|
// Deterministic stuck verdict, computed once and shared: the semantic
|
|
@@ -1693,83 +1657,6 @@ export class AgentSession {
|
|
|
1693
1657
|
.join("\n")
|
|
1694
1658
|
: "";
|
|
1695
1659
|
}
|
|
1696
|
-
/** Independent fresh-context review of the finished work (Codex Guardian
|
|
1697
|
-
* pattern). Spawns a READ-ONLY child on the ACTIVE model, waits bounded,
|
|
1698
|
-
* and returns findings for the acting agent to address — or nothing when
|
|
1699
|
-
* the review passes, is unavailable, or fails (in-thread review remains the
|
|
1700
|
-
* fallback; the feature degrades, never blocks).
|
|
1701
|
-
*
|
|
1702
|
-
* Runs inside the pre-stop poll, so the candidate final answer is already
|
|
1703
|
-
* held by arming and this wait cannot race a streamed answer. */
|
|
1704
|
-
async runIndependentReview(decision) {
|
|
1705
|
-
if (!this.subAgentManager)
|
|
1706
|
-
return [];
|
|
1707
|
-
if (this.independentReviewStarted)
|
|
1708
|
-
return [];
|
|
1709
|
-
// An allow-listed session (a subagent worker itself) must not spawn
|
|
1710
|
-
// harness-owned grandchildren the tool policy never granted.
|
|
1711
|
-
if (this.opts.allowedTools && !this.opts.allowedTools.includes("spawn_agent"))
|
|
1712
|
-
return [];
|
|
1713
|
-
if (decision.score < INDEPENDENT_REVIEW_SCORE_THRESHOLD)
|
|
1714
|
-
return [];
|
|
1715
|
-
this.independentReviewStarted = true;
|
|
1716
|
-
const taskName = `ideal-reviewer-${Math.random().toString(36).slice(2, 8)}`;
|
|
1717
|
-
let agentId;
|
|
1718
|
-
try {
|
|
1719
|
-
const task = buildReviewerTask({
|
|
1720
|
-
originalRequest: this.originalRequest,
|
|
1721
|
-
changedFiles: [...this.hookFileEditCounts.keys()],
|
|
1722
|
-
stats: this.hookStats,
|
|
1723
|
-
triggerReasons: decision.reasons,
|
|
1724
|
-
});
|
|
1725
|
-
// Active model forced at spawn time — never routed to a fast/review model.
|
|
1726
|
-
// The reviewer's own time limit ends it with a verdict on what it read;
|
|
1727
|
-
// the wait below is only a backstop against a hung child.
|
|
1728
|
-
const snapshot = await this.subAgentManager.spawn(taskName, task, undefined, {
|
|
1729
|
-
model: this.model,
|
|
1730
|
-
tools: REVIEWER_TOOLS,
|
|
1731
|
-
turnTimeoutMs: REVIEWER_TURN_TIMEOUT_MS,
|
|
1732
|
-
});
|
|
1733
|
-
agentId = snapshot.agent_id;
|
|
1734
|
-
const waited = await this.subAgentManager.wait([agentId], "all", REVIEWER_WAIT_MS);
|
|
1735
|
-
const agent = waited.agents[0];
|
|
1736
|
-
if (!agent || !isTerminalSubAgentState(agent.state)) {
|
|
1737
|
-
// Timeout: collect the straggler so the completion gate cannot fire on
|
|
1738
|
-
// it later, then fall back to the in-thread review.
|
|
1739
|
-
await this.subAgentManager.interrupt(agentId, true).catch(() => { });
|
|
1740
|
-
log("WARN", "ideal", "Independent reviewer timed out; falling back to in-thread review", {
|
|
1741
|
-
agentId,
|
|
1742
|
-
});
|
|
1743
|
-
return [];
|
|
1744
|
-
}
|
|
1745
|
-
const findings = parseReviewerFindings(agent.output ?? "");
|
|
1746
|
-
if (!findings) {
|
|
1747
|
-
log("WARN", "ideal", "Independent reviewer output unparseable; falling back", {
|
|
1748
|
-
agentId,
|
|
1749
|
-
state: agent.state,
|
|
1750
|
-
...(agent.error ? { error: agent.error } : {}),
|
|
1751
|
-
});
|
|
1752
|
-
return [];
|
|
1753
|
-
}
|
|
1754
|
-
if (findings.clean) {
|
|
1755
|
-
log("INFO", "ideal", "Independent reviewer verdict: clean", { agentId });
|
|
1756
|
-
return [];
|
|
1757
|
-
}
|
|
1758
|
-
log("INFO", "ideal", "Independent reviewer flagged findings", {
|
|
1759
|
-
agentId,
|
|
1760
|
-
count: String(findings.findings.length),
|
|
1761
|
-
});
|
|
1762
|
-
return [buildIndependentReviewMessage(findings.findings)];
|
|
1763
|
-
}
|
|
1764
|
-
catch (error) {
|
|
1765
|
-
if (agentId)
|
|
1766
|
-
await this.subAgentManager.interrupt(agentId, true).catch(() => { });
|
|
1767
|
-
log("WARN", "ideal", "Independent reviewer failed; falling back to in-thread review", {
|
|
1768
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1769
|
-
});
|
|
1770
|
-
return [];
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
1660
|
/**
|
|
1774
1661
|
* Turn-budget extension gate. The loop consults this instead of stopping
|
|
1775
1662
|
* mid-task when it exhausts `maxTurns`. Grant ONLY on evidence of progress —
|
|
@@ -1803,98 +1690,28 @@ export class AgentSession {
|
|
|
1803
1690
|
});
|
|
1804
1691
|
return granted;
|
|
1805
1692
|
}
|
|
1806
|
-
/**
|
|
1807
|
-
*
|
|
1808
|
-
*
|
|
1809
|
-
|
|
1810
|
-
*
|
|
1811
|
-
* The turn count is looked ahead by one on purpose. `hookStats.turns` only
|
|
1812
|
-
* advances at `turn_end`, so while the model is writing the draft the counter
|
|
1813
|
-
* still reads the PREVIOUS turn; the real gate sees one more. Without the
|
|
1814
|
-
* lookahead a run sitting on score 3 crosses to 4 on the draft's own
|
|
1815
|
-
* `turn_end` — after the text already streamed — which is precisely the
|
|
1816
|
-
* appear-then-vanish flash. Over-arming by one turn point costs only live
|
|
1817
|
-
* token streaming on a final answer that then shows whole; under-arming costs
|
|
1818
|
-
* the flash, so this errs toward arming.
|
|
1819
|
-
*/
|
|
1820
|
-
wouldInjectIdealReview() {
|
|
1693
|
+
/** Would a stop right now inject a pre-final follow-up? Only queued LSP
|
|
1694
|
+
* diagnostics (real errors injected below) or a mode-owned completion
|
|
1695
|
+
* review can, so clients hold the candidate answer only then. */
|
|
1696
|
+
wouldInjectBeforeFinal() {
|
|
1821
1697
|
if (this.opts.completionReview?.armed)
|
|
1822
1698
|
return true;
|
|
1823
|
-
|
|
1824
|
-
return false;
|
|
1825
|
-
// Mid-review a stop still injects: the coverage follow-up while files are
|
|
1826
|
-
// unread, or its escalation once the budget is spent. Both make the model
|
|
1827
|
-
// answer again, so the candidate answer is a draft exactly as it is before
|
|
1828
|
-
// the review starts — without arming here it paints and the reviewed answer
|
|
1829
|
-
// lands under it as a duplicate.
|
|
1830
|
-
if (this.idealReviewPhase === "reviewing") {
|
|
1831
|
-
return this.reviewCoverage.evidence().missing.length > 0;
|
|
1832
|
-
}
|
|
1833
|
-
if (this.idealReviewPhase !== "idle")
|
|
1834
|
-
return false;
|
|
1835
|
-
if (!this.settingsManager.get("idealReviewEnabled"))
|
|
1836
|
-
return false;
|
|
1837
|
-
if (evaluateIdealReview({ ...this.hookStats, turns: this.hookStats.turns + 1 }).shouldReview) {
|
|
1838
|
-
return true;
|
|
1839
|
-
}
|
|
1840
|
-
const files = this.hookFileEditCounts.size;
|
|
1841
|
-
if (files === 0)
|
|
1842
|
-
return false;
|
|
1843
|
-
if (this.idealDriftProbe?.files !== files) {
|
|
1844
|
-
this.idealDriftProbe = {
|
|
1845
|
-
files,
|
|
1846
|
-
drifted: detectTestDrift(this.hookFileEditCounts.keys(), this.cwd).length > 0,
|
|
1847
|
-
};
|
|
1848
|
-
}
|
|
1849
|
-
return this.idealDriftProbe.drifted;
|
|
1850
|
-
}
|
|
1851
|
-
/** Would a stop right now inject the verification gate? Same conditions as
|
|
1852
|
-
* the pre-stop branch below, so arming and injection cannot disagree. */
|
|
1853
|
-
wouldInjectVerification() {
|
|
1854
|
-
if (this.lspManager?.hasQueuedDiagnostics())
|
|
1855
|
-
return true;
|
|
1856
|
-
if (this.opts.selfCorrectionHooks === false)
|
|
1857
|
-
return false;
|
|
1858
|
-
if (!this.settingsManager.get("verificationGateEnabled"))
|
|
1859
|
-
return false;
|
|
1860
|
-
if (this.opts.allowedTools && !this.opts.allowedTools.includes("bash"))
|
|
1861
|
-
return false;
|
|
1862
|
-
return this.verificationGate.willInject();
|
|
1699
|
+
return this.lspManager?.hasQueuedDiagnostics() ?? false;
|
|
1863
1700
|
}
|
|
1864
1701
|
/** Broadcast pre-final hook arming on change. Both edges matter: armed=false
|
|
1865
|
-
* after the hook fires is what lets a client stream the
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
* Callable before `initialize()`: the sidecar sets Ken's review suppression
|
|
1869
|
-
* on a freshly constructed session, and every arming predicate below reads
|
|
1870
|
-
* settings that `initialize()` has not loaded yet. Nothing can be armed
|
|
1871
|
-
* before the session can run a turn, and the first `tool_result`/`turn_end`
|
|
1872
|
-
* recomputes both edges — so skipping is the correct answer, not a patch. */
|
|
1702
|
+
* after the hook fires is what lets a client stream the final answer live
|
|
1703
|
+
* again. Callable before `initialize()`, when no manager exists yet. */
|
|
1873
1704
|
refreshHookArming() {
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
this.refreshIdealReviewArmed();
|
|
1877
|
-
this.refreshVerificationArmed();
|
|
1878
|
-
}
|
|
1879
|
-
refreshVerificationArmed() {
|
|
1880
|
-
if (!this.settingsManager)
|
|
1881
|
-
return;
|
|
1882
|
-
const armed = this.wouldInjectVerification();
|
|
1883
|
-
if (armed === this.verificationArmed)
|
|
1705
|
+
const armed = this.wouldInjectBeforeFinal();
|
|
1706
|
+
if (armed === this.preFinalArmed)
|
|
1884
1707
|
return;
|
|
1885
|
-
this.
|
|
1708
|
+
this.preFinalArmed = armed;
|
|
1886
1709
|
this.eventBus.emit("hook_armed", { kind: "verification", armed });
|
|
1887
1710
|
}
|
|
1888
|
-
refreshIdealReviewArmed() {
|
|
1889
|
-
const armed = this.wouldInjectIdealReview();
|
|
1890
|
-
if (armed === this.idealReviewArmed)
|
|
1891
|
-
return;
|
|
1892
|
-
this.idealReviewArmed = armed;
|
|
1893
|
-
this.eventBus.emit("hook_armed", { kind: "ideal", armed });
|
|
1894
|
-
}
|
|
1895
1711
|
/**
|
|
1896
|
-
* Pre-stop
|
|
1897
|
-
*
|
|
1712
|
+
* Pre-stop follow-ups: LSP errors, unread child agents and background
|
|
1713
|
+
* processes, and a mode-owned completion review. Verification evidence is
|
|
1714
|
+
* tracked passively for run status; it never forces another turn.
|
|
1898
1715
|
*/
|
|
1899
1716
|
async getHookFollowUpMessages() {
|
|
1900
1717
|
// Exit notifications and task_output refer to the same host process record.
|
|
@@ -1907,14 +1724,16 @@ export class AgentSession {
|
|
|
1907
1724
|
if (backgroundChanged)
|
|
1908
1725
|
await this.persistVerificationState();
|
|
1909
1726
|
// Edits return immediately; only the completion boundary waits for remaining
|
|
1910
|
-
// checks.
|
|
1727
|
+
// checks. Only real errors cost another turn: a timed-out or unavailable
|
|
1728
|
+
// server proves nothing either way, and run status already reports
|
|
1729
|
+
// unverified changes without making the model answer twice.
|
|
1911
1730
|
await this.lspManager?.flushDiagnostics(this.opts.signal);
|
|
1912
1731
|
if (this.opts.signal?.aborted)
|
|
1913
1732
|
return null;
|
|
1914
|
-
const diagnosticText = this.lspManager?.drainDiagnostics(
|
|
1733
|
+
const diagnosticText = this.lspManager?.drainDiagnostics(false);
|
|
1915
1734
|
if (diagnosticText)
|
|
1916
1735
|
this.eventBus.emit("diagnostics", { text: diagnosticText });
|
|
1917
|
-
this.
|
|
1736
|
+
this.refreshHookArming();
|
|
1918
1737
|
const diagnosticMessages = diagnosticText
|
|
1919
1738
|
? [
|
|
1920
1739
|
{
|
|
@@ -1938,48 +1757,6 @@ export class AgentSession {
|
|
|
1938
1757
|
});
|
|
1939
1758
|
return [...diagnosticMessages, ...processFollowUp];
|
|
1940
1759
|
}
|
|
1941
|
-
// Verification gate: code was edited but nothing verified since the last
|
|
1942
|
-
// edit. Above the Ideal review so checks RUN before the read-based review
|
|
1943
|
-
// starts; off for allow-listed sessions that cannot run commands at all.
|
|
1944
|
-
if (this.opts.selfCorrectionHooks !== false &&
|
|
1945
|
-
this.settingsManager.get("verificationGateEnabled") &&
|
|
1946
|
-
(!this.opts.allowedTools || this.opts.allowedTools.includes("bash"))) {
|
|
1947
|
-
const verificationReason = this.verificationGate.pendingReason();
|
|
1948
|
-
const pendingFiles = this.verificationGate.pendingFiles();
|
|
1949
|
-
const verificationFollowUp = this.verificationGate.followUp();
|
|
1950
|
-
if (verificationFollowUp) {
|
|
1951
|
-
// Name the tests that actually reach the unverified files, with the
|
|
1952
|
-
// command that runs exactly those, so the check is targeted rather
|
|
1953
|
-
// than guessed. Best-effort: no index or no runner leaves it unchanged.
|
|
1954
|
-
if (this.testImpact &&
|
|
1955
|
-
(verificationReason === "initial" || verificationReason === "recheck")) {
|
|
1956
|
-
const impactLine = await this.testImpact
|
|
1957
|
-
.impactFor(pendingFiles)
|
|
1958
|
-
.then(formatImpactForVerification)
|
|
1959
|
-
.catch(() => "");
|
|
1960
|
-
const first = verificationFollowUp[0];
|
|
1961
|
-
if (impactLine && first?.role === "user" && typeof first.content === "string") {
|
|
1962
|
-
verificationFollowUp[0] = { ...first, content: first.content + impactLine };
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
log("INFO", "verification-gate", "Injecting verification follow-up", {});
|
|
1966
|
-
// Announce, THEN disarm: clients release held text on disarm, so the
|
|
1967
|
-
// reverse order paints the draft and immediately deletes it — the exact
|
|
1968
|
-
// flash arming exists to prevent.
|
|
1969
|
-
this.eventBus.emit("hook", {
|
|
1970
|
-
kind: "verification",
|
|
1971
|
-
...(verificationReason === "tamper"
|
|
1972
|
-
? { verificationReason: "check_review" }
|
|
1973
|
-
: verificationReason === "recheck"
|
|
1974
|
-
? { verificationReason }
|
|
1975
|
-
: {}),
|
|
1976
|
-
});
|
|
1977
|
-
this.refreshHookArming();
|
|
1978
|
-
return [...diagnosticMessages, ...verificationFollowUp];
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
// Address real errors before review; unavailable checks share the existing
|
|
1982
|
-
// verification demand above instead of manufacturing a separate hook.
|
|
1983
1760
|
if (diagnosticMessages.length > 0)
|
|
1984
1761
|
return diagnosticMessages;
|
|
1985
1762
|
if (this.opts.completionReview) {
|
|
@@ -1998,122 +1775,7 @@ export class AgentSession {
|
|
|
1998
1775
|
}
|
|
1999
1776
|
this.refreshHookArming();
|
|
2000
1777
|
}
|
|
2001
|
-
|
|
2002
|
-
return null;
|
|
2003
|
-
if (this.idealReviewPhase === "reviewing") {
|
|
2004
|
-
const coverage = this.reviewCoverage.evidence();
|
|
2005
|
-
const lspEvidence = this.reviewLspEvidence(coverage.expected);
|
|
2006
|
-
log("INFO", "ideal", "Ideal review coverage check", {
|
|
2007
|
-
covered: coverage.covered,
|
|
2008
|
-
missing: coverage.missing,
|
|
2009
|
-
lspLowConfidence: lspEvidence.lowConfidence,
|
|
2010
|
-
lspMissing: lspEvidence.missing,
|
|
2011
|
-
});
|
|
2012
|
-
if (coverage.missing.length > 0) {
|
|
2013
|
-
// Announce like any other pre-final injection: this follow-up makes the
|
|
2014
|
-
// model answer again, so the answer it interrupts is a draft and the
|
|
2015
|
-
// hook event is what tells clients to discard it. Injecting silently is
|
|
2016
|
-
// what let the pre-coverage answer paint above the reviewed one.
|
|
2017
|
-
this.eventBus.emit("hook", {
|
|
2018
|
-
kind: "ideal",
|
|
2019
|
-
coverageExpected: coverage.expected,
|
|
2020
|
-
coverageMissing: coverage.missing,
|
|
2021
|
-
});
|
|
2022
|
-
if (this.reviewCoverageInjected < MAX_REVIEW_COVERAGE_INJECTIONS) {
|
|
2023
|
-
this.reviewCoverageInjected += 1;
|
|
2024
|
-
// Stays armed (coverage is still outstanding) — this call is here so a
|
|
2025
|
-
// client that missed the earlier edge is armed before the next draft.
|
|
2026
|
-
this.refreshIdealReviewArmed();
|
|
2027
|
-
return [
|
|
2028
|
-
this.withReviewLspEvidence(buildReviewCoverageMessage(coverage.missing), lspEvidence),
|
|
2029
|
-
];
|
|
2030
|
-
}
|
|
2031
|
-
// Budget spent: close the gate so the run cannot spin on a file that
|
|
2032
|
-
// never becomes readable, and require the gap be reported to the user.
|
|
2033
|
-
this.idealReviewPhase = "complete";
|
|
2034
|
-
// The gate is shut, so this is the real disarm: the answer to the
|
|
2035
|
-
// escalation is final and streams live.
|
|
2036
|
-
this.refreshIdealReviewArmed();
|
|
2037
|
-
log("INFO", "ideal", "Ideal review coverage escalated after retry budget", {
|
|
2038
|
-
injected: String(this.reviewCoverageInjected),
|
|
2039
|
-
missing: coverage.missing,
|
|
2040
|
-
});
|
|
2041
|
-
return [buildReviewCoverageEscalationMessage(coverage.missing)];
|
|
2042
|
-
}
|
|
2043
|
-
this.idealReviewPhase = "complete";
|
|
2044
|
-
return null;
|
|
2045
|
-
}
|
|
2046
|
-
if (this.idealReviewPhase === "complete")
|
|
2047
|
-
return null;
|
|
2048
|
-
if (!this.settingsManager.get("idealReviewEnabled"))
|
|
2049
|
-
return null;
|
|
2050
|
-
const decision = evaluateIdealReview(this.hookStats);
|
|
2051
|
-
// Test drift fires the review even on a small change the score would skip:
|
|
2052
|
-
// a green-but-stale test is exactly what the volume gate sleeps through.
|
|
2053
|
-
const driftedFiles = detectTestDrift(this.hookFileEditCounts.keys(), this.cwd).slice(0, 5);
|
|
2054
|
-
if (!decision.shouldReview && driftedFiles.length === 0)
|
|
2055
|
-
return null;
|
|
2056
|
-
// Independent reviewer first (async, bounded): its findings ride in the
|
|
2057
|
-
// SAME follow-up batch as the in-thread review + coverage requirements, so
|
|
2058
|
-
// addressing everything still costs one extra turn.
|
|
2059
|
-
this.reviewCoverage.start(this.hookFileEditCounts.keys());
|
|
2060
|
-
this.idealReviewPhase = "reviewing";
|
|
2061
|
-
const coverage = this.reviewCoverage.evidence();
|
|
2062
|
-
const lspEvidence = this.reviewLspEvidence(coverage.expected);
|
|
2063
|
-
this.eventBus.emit("hook", {
|
|
2064
|
-
kind: "ideal",
|
|
2065
|
-
coverageExpected: coverage.expected,
|
|
2066
|
-
coverageMissing: coverage.missing,
|
|
2067
|
-
});
|
|
2068
|
-
// Recompute strictly AFTER the hook event: clients release held text on
|
|
2069
|
-
// disarm, so the reverse order would paint the draft and then delete it —
|
|
2070
|
-
// the exact flash arming exists to prevent. Arming normally PERSISTS here,
|
|
2071
|
-
// because review starts with every changed file uncovered and a stop while
|
|
2072
|
-
// coverage is outstanding injects again. Disarm lands later, on the read
|
|
2073
|
-
// that closes the last gap (or when the retry budget escalates).
|
|
2074
|
-
this.refreshIdealReviewArmed();
|
|
2075
|
-
// Announce the phase before the reviewer starts, not after its bounded wait.
|
|
2076
|
-
const independentMessages = await this.runIndependentReview(decision);
|
|
2077
|
-
log("INFO", "ideal", "Injecting ideal review before final response", {
|
|
2078
|
-
coverageExpected: coverage.expected,
|
|
2079
|
-
coverageMissing: coverage.missing,
|
|
2080
|
-
lspLowConfidence: lspEvidence.lowConfidence,
|
|
2081
|
-
lspMissing: lspEvidence.missing,
|
|
2082
|
-
});
|
|
2083
|
-
return [
|
|
2084
|
-
...independentMessages,
|
|
2085
|
-
this.withReviewLspEvidence(withReviewCoverageRequirements(buildIdealReviewMessage(decision.reasons, driftedFiles), coverage.missing), lspEvidence),
|
|
2086
|
-
];
|
|
2087
|
-
}
|
|
2088
|
-
reviewLspEvidence(files) {
|
|
2089
|
-
const lowConfidence = [];
|
|
2090
|
-
const missing = [];
|
|
2091
|
-
for (const filePath of files) {
|
|
2092
|
-
const outcome = this.lspManager?.getLatestOutcome(filePath);
|
|
2093
|
-
if (outcome?.kind === "low_confidence")
|
|
2094
|
-
lowConfidence.push(filePath);
|
|
2095
|
-
else if (outcome?.kind !== "clean" && outcome?.kind !== "diagnostics")
|
|
2096
|
-
missing.push(filePath);
|
|
2097
|
-
}
|
|
2098
|
-
return { lowConfidence, missing };
|
|
2099
|
-
}
|
|
2100
|
-
withReviewLspEvidence(message, evidence) {
|
|
2101
|
-
if (evidence.lowConfidence.length === 0 && evidence.missing.length === 0)
|
|
2102
|
-
return message;
|
|
2103
|
-
const notes = [
|
|
2104
|
-
...(evidence.lowConfidence.length > 0
|
|
2105
|
-
? [`Diagnostics are low confidence while indexing: ${evidence.lowConfidence.join(", ")}.`]
|
|
2106
|
-
: []),
|
|
2107
|
-
...(evidence.missing.length > 0
|
|
2108
|
-
? [`Diagnostics evidence is unavailable or missing: ${evidence.missing.join(", ")}.`]
|
|
2109
|
-
: []),
|
|
2110
|
-
"Do not describe those files as compiler-clean without other evidence.",
|
|
2111
|
-
];
|
|
2112
|
-
return {
|
|
2113
|
-
role: "user",
|
|
2114
|
-
provenance: message.provenance,
|
|
2115
|
-
content: `${String(message.content)}\n\n${notes.join(" ")}`,
|
|
2116
|
-
};
|
|
1778
|
+
return null;
|
|
2117
1779
|
}
|
|
2118
1780
|
/** Wraps the real run: cancels an in-flight cache prewarm and tracks run
|
|
2119
1781
|
* activity / last real request time for {@link prewarm}. */
|
|
@@ -2280,6 +1942,8 @@ export class AgentSession {
|
|
|
2280
1942
|
// + pre-warm before the first turn. "baseline": current 5-min default.
|
|
2281
1943
|
cacheRetention: this.isSpeedOptimized() ? "long" : "short",
|
|
2282
1944
|
promptCacheKey: this.getPromptCacheKey(),
|
|
1945
|
+
responsesLite: resolveResponsesLite(this.codexShapeSetting("codexResponsesLite"), this.provider, this.model),
|
|
1946
|
+
strictTools: resolveStrictTools(this.codexShapeSetting("codexStrictTools"), this.provider),
|
|
2283
1947
|
onContextPrepared: (context) => {
|
|
2284
1948
|
const report = this.cacheDiagnostics.prepare(context, {
|
|
2285
1949
|
provider: this.provider,
|
|
@@ -3288,21 +2952,6 @@ export class AgentSession {
|
|
|
3288
2952
|
getPlanMode() {
|
|
3289
2953
|
return this.planModeRef.current;
|
|
3290
2954
|
}
|
|
3291
|
-
/**
|
|
3292
|
-
* Suppress only the pre-final Ideal self-review for this live session.
|
|
3293
|
-
* Autopilot uses this while Ken independently owns verification; loop-break
|
|
3294
|
-
* and post-compaction re-grounding remain active.
|
|
3295
|
-
*/
|
|
3296
|
-
setIdealReviewSuppressed(suppressed) {
|
|
3297
|
-
this.idealReviewSuppressed = suppressed;
|
|
3298
|
-
if (suppressed) {
|
|
3299
|
-
this.idealReviewPhase = "idle";
|
|
3300
|
-
this.reviewCoverage.reset();
|
|
3301
|
-
}
|
|
3302
|
-
// Suppression flips mid-run (autopilot takes over verification), so a client
|
|
3303
|
-
// holding a draft under a stale arming must be released.
|
|
3304
|
-
this.refreshHookArming();
|
|
3305
|
-
}
|
|
3306
2955
|
/** Queue a user message (optionally with attachments) to be injected mid-run
|
|
3307
2956
|
* as steering. Returns the new queue length. No-op semantics are the caller's
|
|
3308
2957
|
* concern. */
|
|
@@ -3788,7 +3437,7 @@ export class AgentSession {
|
|
|
3788
3437
|
? await captureVerificationSnapshot(this.opts.cwd, [...this.hookFileEditCounts.keys()])
|
|
3789
3438
|
: null;
|
|
3790
3439
|
if (after === null || after !== check.sourceSnapshot) {
|
|
3791
|
-
this.verificationGate.requireFreshVerification(true
|
|
3440
|
+
this.verificationGate.requireFreshVerification(true);
|
|
3792
3441
|
this.verificationGate.recordRejectedCheck(check.command, after === null
|
|
3793
3442
|
? "Workspace inputs could not be compared; run a read-only check after the command"
|
|
3794
3443
|
: "Command changed workspace inputs; run checks against the changed source");
|
|
@@ -4157,6 +3806,9 @@ export class AgentSession {
|
|
|
4157
3806
|
this.prewarmController = null;
|
|
4158
3807
|
}
|
|
4159
3808
|
}
|
|
3809
|
+
codexShapeSetting(key) {
|
|
3810
|
+
return this.settingsManager?.get(key) ?? "auto";
|
|
3811
|
+
}
|
|
4160
3812
|
/** True when speedProfile is "optimized" (1-h cache TTL + pre-warm), or the
|
|
4161
3813
|
* session was constructed with `forceLongCacheRetention` (Ken sessions). */
|
|
4162
3814
|
isSpeedOptimized() {
|