@kernlang/agon 0.1.8 → 0.2.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/dist/{chunk-GMVFKWQA.js → chunk-24EWX243.js} +1393 -126
- package/dist/chunk-24EWX243.js.map +1 -0
- package/dist/{chunk-SUT2HDOY.js → chunk-47QQZGXP.js} +11 -1
- package/dist/{chunk-SUT2HDOY.js.map → chunk-47QQZGXP.js.map} +1 -1
- package/dist/{chunk-45YTXJWJ.js → chunk-4ZDVR5XR.js} +3 -1
- package/dist/{chunk-45YTXJWJ.js.map → chunk-4ZDVR5XR.js.map} +1 -1
- package/dist/{chunk-I2PMSXJ3.js → chunk-6GENPQFW.js} +2 -2
- package/dist/{chunk-KPU23NS2.js → chunk-6SOOHJZQ.js} +75 -13
- package/dist/chunk-6SOOHJZQ.js.map +1 -0
- package/dist/{chunk-GHAMYNRC.js → chunk-FORBHCTM.js} +41 -7
- package/dist/chunk-FORBHCTM.js.map +1 -0
- package/dist/{chunk-CQBQPSE4.js → chunk-MMPLPYSK.js} +219 -29
- package/dist/chunk-MMPLPYSK.js.map +1 -0
- package/dist/{chunk-BPKY4OF2.js → chunk-MWF4RHRU.js} +3072 -1011
- package/dist/chunk-MWF4RHRU.js.map +1 -0
- package/dist/{dispatch-J4RSWLXM.js → dispatch-FQQWL2YW.js} +2 -2
- package/dist/engines/agy.json +6 -0
- package/dist/engines/claude.json +4 -0
- package/dist/engines/codex.json +4 -0
- package/dist/engines/minimax-coding-plan-minimax-m3.json +4 -0
- package/dist/{forge-5QSRUNW6.js → forge-ZFCSXC3Z.js} +6 -6
- package/dist/index.js +16712 -14634
- package/dist/index.js.map +1 -1
- package/dist/mcp/engines/agy.json +6 -0
- package/dist/mcp/engines/claude.json +4 -0
- package/dist/mcp/engines/codex.json +4 -0
- package/dist/mcp/engines/minimax-coding-plan-minimax-m3.json +4 -0
- package/dist/mcp/index.js +34 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/plan-mode-I3BZOBFB.js +17 -0
- package/dist/{src-253BUXEF.js → src-WMV62WO7.js} +171 -9
- package/dist/{update-WLRTYR77.js → update-H3JQXPGO.js} +6 -6
- package/package.json +2 -2
- package/dist/chunk-BPKY4OF2.js.map +0 -1
- package/dist/chunk-CQBQPSE4.js.map +0 -1
- package/dist/chunk-GHAMYNRC.js.map +0 -1
- package/dist/chunk-GMVFKWQA.js.map +0 -1
- package/dist/chunk-KPU23NS2.js.map +0 -1
- package/dist/plan-mode-5IQ2SKIS.js +0 -17
- /package/dist/{chunk-I2PMSXJ3.js.map → chunk-6GENPQFW.js.map} +0 -0
- /package/dist/{dispatch-J4RSWLXM.js.map → dispatch-FQQWL2YW.js.map} +0 -0
- /package/dist/{forge-5QSRUNW6.js.map → forge-ZFCSXC3Z.js.map} +0 -0
- /package/dist/{plan-mode-5IQ2SKIS.js.map → plan-mode-I3BZOBFB.js.map} +0 -0
- /package/dist/{src-253BUXEF.js.map → src-WMV62WO7.js.map} +0 -0
- /package/dist/{update-WLRTYR77.js.map → update-H3JQXPGO.js.map} +0 -0
|
@@ -4,28 +4,34 @@ import {
|
|
|
4
4
|
cleanEngineOutput,
|
|
5
5
|
icons,
|
|
6
6
|
parseMarkdownBlocks
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-6GENPQFW.js";
|
|
8
8
|
import {
|
|
9
9
|
AGON_MODE_NAMES,
|
|
10
10
|
CORPUS_PATH,
|
|
11
11
|
FitnessError,
|
|
12
|
+
PERMISSION_DENIED_MESSAGE,
|
|
12
13
|
RUNS_DIR,
|
|
13
14
|
Semaphore,
|
|
14
15
|
ToolRegistry,
|
|
15
16
|
agonPath,
|
|
17
|
+
appendAttribution,
|
|
16
18
|
appendMessage,
|
|
17
19
|
appendUserTurnIfAbsent,
|
|
18
20
|
applyPatch,
|
|
19
21
|
assignForgeRoles,
|
|
22
|
+
bashRanGate,
|
|
23
|
+
budgetRatioPct,
|
|
20
24
|
buildCodebaseMap,
|
|
21
25
|
buildCritiquePrompt,
|
|
22
26
|
buildForgePrompt,
|
|
23
27
|
buildHistoryPrimedPrompt,
|
|
24
28
|
buildKernContextSpine,
|
|
29
|
+
buildProjectMemoryBlock,
|
|
25
30
|
buildSpecializedPrompt,
|
|
26
31
|
buildStageContext,
|
|
27
32
|
buildSynthesisPrompt,
|
|
28
33
|
buildToolSystemPrompt,
|
|
34
|
+
checkSessionBudget,
|
|
29
35
|
classifyDispatchFailure,
|
|
30
36
|
classifyTask,
|
|
31
37
|
composeTeams,
|
|
@@ -44,6 +50,7 @@ import {
|
|
|
44
50
|
createGoalTool,
|
|
45
51
|
createGrepTool,
|
|
46
52
|
createListPlansTool,
|
|
53
|
+
createMultiEditTool,
|
|
47
54
|
createPersistentSession,
|
|
48
55
|
createPipelineTool,
|
|
49
56
|
createProposePlanTool,
|
|
@@ -52,6 +59,7 @@ import {
|
|
|
52
59
|
createReportConfidenceTool,
|
|
53
60
|
createRetrieveResultTool,
|
|
54
61
|
createReviewTool,
|
|
62
|
+
createSaveMemoryTool,
|
|
55
63
|
createSidechainLogger,
|
|
56
64
|
createStreamBridge,
|
|
57
65
|
createTodoWriteTool,
|
|
@@ -62,11 +70,14 @@ import {
|
|
|
62
70
|
determineWinner,
|
|
63
71
|
diffFileCount,
|
|
64
72
|
diffLineCount,
|
|
73
|
+
discoverGate,
|
|
65
74
|
discoverMcpServers,
|
|
66
75
|
engineHealth,
|
|
67
76
|
ensureAgonHome,
|
|
68
77
|
estimateCost,
|
|
78
|
+
estimateSessionTokens,
|
|
69
79
|
estimateTokens,
|
|
80
|
+
evaluateToolRules,
|
|
70
81
|
executeToolCall,
|
|
71
82
|
extractPatchFilePatterns,
|
|
72
83
|
formatChatContextForPrompt,
|
|
@@ -77,6 +88,7 @@ import {
|
|
|
77
88
|
getRatings,
|
|
78
89
|
gitChangedFiles,
|
|
79
90
|
hasProjectBrief,
|
|
91
|
+
isGateSkipSignal,
|
|
80
92
|
isReadOnlyCommand,
|
|
81
93
|
listCesarPlans,
|
|
82
94
|
loadConfig,
|
|
@@ -84,7 +96,9 @@ import {
|
|
|
84
96
|
makeFormat,
|
|
85
97
|
mcpDiscoveryFingerprint,
|
|
86
98
|
mcpServersToWireFormat,
|
|
99
|
+
parsePermissionRuleSet,
|
|
87
100
|
parseToolCalls,
|
|
101
|
+
parseToolHooks,
|
|
88
102
|
pickTopRatedEngine,
|
|
89
103
|
planCostEstimator,
|
|
90
104
|
rankByTaskClass,
|
|
@@ -102,6 +116,7 @@ import {
|
|
|
102
116
|
stashSnapshot,
|
|
103
117
|
toolsToOpenAIFormat,
|
|
104
118
|
tracker,
|
|
119
|
+
updateChatSummary,
|
|
105
120
|
updateGlickoRanked,
|
|
106
121
|
updateTeamElo,
|
|
107
122
|
validateSyntax,
|
|
@@ -110,7 +125,7 @@ import {
|
|
|
110
125
|
worktreeDiff,
|
|
111
126
|
worktreePruneOrphaned,
|
|
112
127
|
worktreeRemoveBestEffort
|
|
113
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-MWF4RHRU.js";
|
|
114
129
|
|
|
115
130
|
// ../forge/src/generated/forge.ts
|
|
116
131
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -166,7 +181,7 @@ async function healthCheckEngine(engineId, registry, adapter, _cwd, timeoutSec,
|
|
|
166
181
|
}
|
|
167
182
|
const message = err instanceof Error ? err.message : String(err);
|
|
168
183
|
const status = classifyDispatchFailure({ errorMessage: message });
|
|
169
|
-
if (status === "auth-failed" || status === "unreachable") {
|
|
184
|
+
if (status === "auth-failed" || status === "unreachable" || status === "binary-missing") {
|
|
170
185
|
engineHealth.mark(engineId, status, message);
|
|
171
186
|
}
|
|
172
187
|
return {
|
|
@@ -190,7 +205,7 @@ async function healthCheckEngine(engineId, registry, adapter, _cwd, timeoutSec,
|
|
|
190
205
|
}
|
|
191
206
|
if (exitCode !== 0) {
|
|
192
207
|
const status = classifyDispatchFailure({ stderr, exitCode, errorMessage: stderr });
|
|
193
|
-
if (status === "auth-failed" || status === "unreachable") {
|
|
208
|
+
if (status === "auth-failed" || status === "unreachable" || status === "binary-missing") {
|
|
194
209
|
engineHealth.mark(engineId, status, stderr || `exit-${exitCode}`);
|
|
195
210
|
}
|
|
196
211
|
return {
|
|
@@ -260,7 +275,7 @@ async function preflightHealthFilter(opts) {
|
|
|
260
275
|
const afterQuarantine = [];
|
|
261
276
|
for (const id of engineIds) {
|
|
262
277
|
const health = engineHealth.get(id);
|
|
263
|
-
if (health && (health.status === "auth-failed" || health.status === "unreachable")) {
|
|
278
|
+
if (health && (health.status === "auth-failed" || health.status === "unreachable" || health.status === "binary-missing")) {
|
|
264
279
|
skipped.push({ engineId: id, status: health.status, reason: health.reason || "quarantined this session" });
|
|
265
280
|
} else {
|
|
266
281
|
afterQuarantine.push(id);
|
|
@@ -1720,7 +1735,7 @@ async function runForge(options, registry, adapter, onEvent) {
|
|
|
1720
1735
|
return false;
|
|
1721
1736
|
}
|
|
1722
1737
|
const health = engineHealth.get(id);
|
|
1723
|
-
if (health && (health.status === "auth-failed" || health.status === "unreachable")) {
|
|
1738
|
+
if (health && (health.status === "auth-failed" || health.status === "unreachable" || health.status === "binary-missing")) {
|
|
1724
1739
|
skippedQuarantine.push({ id, reason: health.reason, status: health.status });
|
|
1725
1740
|
return false;
|
|
1726
1741
|
}
|
|
@@ -3435,7 +3450,7 @@ async function runCouncil(opts) {
|
|
|
3435
3450
|
}
|
|
3436
3451
|
if (cesarId && !engines.includes(cesarId)) {
|
|
3437
3452
|
const __ch = engineHealth.get(cesarId);
|
|
3438
|
-
if (__ch && (__ch.status === "auth-failed" || __ch.status === "unreachable")) {
|
|
3453
|
+
if (__ch && (__ch.status === "auth-failed" || __ch.status === "unreachable" || __ch.status === "binary-missing")) {
|
|
3439
3454
|
warnings.push(`Configured Cesar chair '${cesarId}' is quarantined this session (${__ch.status}); falling back to ${engines[0]} as chair.`);
|
|
3440
3455
|
cesarId = "";
|
|
3441
3456
|
}
|
|
@@ -5225,6 +5240,75 @@ async function runThinkChain(opts) {
|
|
|
5225
5240
|
};
|
|
5226
5241
|
}
|
|
5227
5242
|
|
|
5243
|
+
// ../forge/src/generated/pr-text.ts
|
|
5244
|
+
function buildPrTextPrompt(opts) {
|
|
5245
|
+
const lines = [];
|
|
5246
|
+
lines.push("Write the pull-request text for a change that was just pushed. You are writing for the human reviewer who will merge it.");
|
|
5247
|
+
lines.push("");
|
|
5248
|
+
lines.push("TASK / INTENT:");
|
|
5249
|
+
lines.push(opts.intent.trim());
|
|
5250
|
+
if (opts.context && opts.context.trim()) {
|
|
5251
|
+
lines.push("");
|
|
5252
|
+
lines.push("RUN FACTS (verified \u2014 you may state these):");
|
|
5253
|
+
lines.push(opts.context.trim());
|
|
5254
|
+
}
|
|
5255
|
+
if (opts.commits.trim()) {
|
|
5256
|
+
lines.push("");
|
|
5257
|
+
lines.push("COMMITS ON THIS BRANCH:");
|
|
5258
|
+
lines.push(opts.commits.trim());
|
|
5259
|
+
}
|
|
5260
|
+
lines.push("");
|
|
5261
|
+
lines.push("DIFF (may be truncated):");
|
|
5262
|
+
lines.push(opts.diff.trim() || "(diff unavailable \u2014 describe from the commits and run facts only)");
|
|
5263
|
+
lines.push("");
|
|
5264
|
+
lines.push("Requirements:");
|
|
5265
|
+
lines.push("- Title: ONE imperative line, max 72 chars, conventional-commit style when natural (feat:/fix:/refactor:/\u2026). No trailing period.");
|
|
5266
|
+
lines.push("- Body: GitHub markdown with exactly these sections:");
|
|
5267
|
+
lines.push(" ## Summary \u2014 2-4 sentences: what changed and why.");
|
|
5268
|
+
lines.push(" ## Changes \u2014 bullets of the concrete changes, grouped by area, naming key files.");
|
|
5269
|
+
lines.push(" ## Verification \u2014 how it was verified. ONLY state what the run facts/diff show (gate command, tests in the diff); never invent test runs or results.");
|
|
5270
|
+
lines.push("- No preamble, no sign-off, no emojis, no flattery, nothing the diff does not support.");
|
|
5271
|
+
lines.push("");
|
|
5272
|
+
lines.push('Reply EXACTLY in this format (first line literally starts with "TITLE: "):');
|
|
5273
|
+
lines.push("TITLE: <the title>");
|
|
5274
|
+
lines.push("");
|
|
5275
|
+
lines.push("<the body markdown>");
|
|
5276
|
+
return lines.join("\n");
|
|
5277
|
+
}
|
|
5278
|
+
function parsePrText(raw) {
|
|
5279
|
+
let cleaned = raw.replace(/<think>[\s\S]*?<\/think>\s*/gi, "").trim();
|
|
5280
|
+
const fence = cleaned.match(/^```[a-z]*\s*\n([\s\S]*?)\n```\s*$/i);
|
|
5281
|
+
if (fence) cleaned = fence[1].trim();
|
|
5282
|
+
if (!cleaned) return null;
|
|
5283
|
+
const lines = cleaned.split("\n");
|
|
5284
|
+
const i = lines.findIndex((l) => /^\s*TITLE:\s*\S/.test(l));
|
|
5285
|
+
if (i < 0) return null;
|
|
5286
|
+
const title = lines[i].replace(/^\s*TITLE:\s*/, "").trim();
|
|
5287
|
+
let body = lines.slice(i + 1).join("\n").trim();
|
|
5288
|
+
body = body.replace(/^BODY:\s*/i, "").trim();
|
|
5289
|
+
if (!title || !body) return null;
|
|
5290
|
+
return { title, body };
|
|
5291
|
+
}
|
|
5292
|
+
async function runPrText(opts) {
|
|
5293
|
+
try {
|
|
5294
|
+
const r = await runDelegate({
|
|
5295
|
+
engineId: opts.engineId,
|
|
5296
|
+
task: buildPrTextPrompt(opts),
|
|
5297
|
+
cwd: opts.cwd,
|
|
5298
|
+
registry: opts.registry,
|
|
5299
|
+
adapter: opts.adapter,
|
|
5300
|
+
timeout: opts.timeout,
|
|
5301
|
+
outputDir: opts.outputDir,
|
|
5302
|
+
signal: opts.signal
|
|
5303
|
+
});
|
|
5304
|
+
const parsed = parsePrText(r.response ?? "");
|
|
5305
|
+
if (!parsed) return { ok: false, title: "", body: "", engineId: opts.engineId };
|
|
5306
|
+
return { ok: true, title: parsed.title, body: parsed.body, engineId: opts.engineId };
|
|
5307
|
+
} catch {
|
|
5308
|
+
return { ok: false, title: "", body: "", engineId: opts.engineId };
|
|
5309
|
+
}
|
|
5310
|
+
}
|
|
5311
|
+
|
|
5228
5312
|
// ../forge/src/generated/goal/journal.ts
|
|
5229
5313
|
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, renameSync as renameSync4 } from "fs";
|
|
5230
5314
|
import { dirname as dirname2 } from "path";
|
|
@@ -6078,7 +6162,7 @@ ${gate2.stdout || ""}`), detail: `gate failed after fix pass${logPath ? ` \u2014
|
|
|
6078
6162
|
if (rev.blocking) throw { kind: "review", detail: rev.summary };
|
|
6079
6163
|
}
|
|
6080
6164
|
await git(["add", "-A"], wt);
|
|
6081
|
-
const commit = await git(["commit", "-m", `goal(${spec.goalId}): ${task.source}`, "--no-verify"], wt);
|
|
6165
|
+
const commit = await git(["commit", "-m", appendAttribution(`goal(${spec.goalId}): ${task.source}`, loadConfig(repoRoot2)), "--no-verify"], wt);
|
|
6082
6166
|
if (commit.exitCode !== 0) throw { kind: "error", detail: `commit failed: ${commit.stderr.trim()}` };
|
|
6083
6167
|
const headRes = await git(["rev-parse", "HEAD"], wt);
|
|
6084
6168
|
const newSha = headRes.stdout.trim();
|
|
@@ -6695,10 +6779,11 @@ function filterDefaultOrchestrationEngines(engineIds) {
|
|
|
6695
6779
|
|
|
6696
6780
|
// src/generated/cesar/brain.ts
|
|
6697
6781
|
import { join as join14 } from "path";
|
|
6698
|
-
import { mkdirSync as mkdirSync13, appendFileSync as appendFileSync2, existsSync as
|
|
6782
|
+
import { mkdirSync as mkdirSync13, appendFileSync as appendFileSync2, existsSync as existsSync14, readFileSync as readFileSync14, unlinkSync, readdirSync, writeFileSync as writeFileSync11 } from "fs";
|
|
6699
6783
|
|
|
6700
6784
|
// src/generated/cesar/confidence.ts
|
|
6701
6785
|
var CONFIDENCE_TIERS = { direct: 96, quickNero: 93, nero: 88, brainstorm: 72, advisor: 72 };
|
|
6786
|
+
var ESCALATION_SUGGESTION_THRESHOLD = 85;
|
|
6702
6787
|
function parseConfidence(response) {
|
|
6703
6788
|
const tildeMatch = response.match(/^~(\d{1,3})%\s*/);
|
|
6704
6789
|
if (tildeMatch) {
|
|
@@ -6716,6 +6801,26 @@ function parseConfidence(response) {
|
|
|
6716
6801
|
}
|
|
6717
6802
|
return { value: null, rest: response };
|
|
6718
6803
|
}
|
|
6804
|
+
function extractStrictConfidence(text) {
|
|
6805
|
+
if (!text) {
|
|
6806
|
+
return null;
|
|
6807
|
+
}
|
|
6808
|
+
const all = Array.from(text.matchAll(/\bCONFIDENCE:\s*~?(\d{1,3})\s*%/gi));
|
|
6809
|
+
const m = all.length > 0 ? all[all.length - 1] : null;
|
|
6810
|
+
if (!m) {
|
|
6811
|
+
return null;
|
|
6812
|
+
}
|
|
6813
|
+
const n = parseInt(m[1], 10);
|
|
6814
|
+
if (!Number.isFinite(n) || n < 0 || n > 100) {
|
|
6815
|
+
return null;
|
|
6816
|
+
}
|
|
6817
|
+
return n;
|
|
6818
|
+
}
|
|
6819
|
+
function buildEscalationSuggestionLine(value) {
|
|
6820
|
+
const dim = "\x1B[2m";
|
|
6821
|
+
const reset = "\x1B[0m";
|
|
6822
|
+
return `${dim}${value}% \u2014 want a nero/tribunal on this?${reset}`;
|
|
6823
|
+
}
|
|
6719
6824
|
function confidenceColor(value) {
|
|
6720
6825
|
if (value >= 96) return "\x1B[32m";
|
|
6721
6826
|
if (value >= 93) return "\x1B[33m";
|
|
@@ -6762,8 +6867,61 @@ function parseSuggestion(response) {
|
|
|
6762
6867
|
return { action, rest, hardened, tribunalMode, team };
|
|
6763
6868
|
}
|
|
6764
6869
|
|
|
6870
|
+
// src/generated/cesar/todos-marker.ts
|
|
6871
|
+
function parseLiveTodos(response) {
|
|
6872
|
+
const blockRe = /\[TODOS\]([\s\S]*?)\[\/TODOS\]/gi;
|
|
6873
|
+
let found = false;
|
|
6874
|
+
let lastBody = null;
|
|
6875
|
+
let rest = response;
|
|
6876
|
+
const matches = response.match(blockRe);
|
|
6877
|
+
if (!matches || matches.length === 0) {
|
|
6878
|
+
return { todos: [], found: false, rest: response };
|
|
6879
|
+
}
|
|
6880
|
+
found = true;
|
|
6881
|
+
rest = response.replace(blockRe, "").trim();
|
|
6882
|
+
let m;
|
|
6883
|
+
const re2 = /\[TODOS\]([\s\S]*?)\[\/TODOS\]/gi;
|
|
6884
|
+
while ((m = re2.exec(response)) !== null) {
|
|
6885
|
+
lastBody = m[1];
|
|
6886
|
+
}
|
|
6887
|
+
if (lastBody === null) return { todos: [], found, rest };
|
|
6888
|
+
const allowed = /* @__PURE__ */ new Set(["pending", "running", "done", "failed", "cancelled"]);
|
|
6889
|
+
let parsed;
|
|
6890
|
+
try {
|
|
6891
|
+
parsed = JSON.parse(lastBody.trim());
|
|
6892
|
+
} catch {
|
|
6893
|
+
return { todos: [], found, rest };
|
|
6894
|
+
}
|
|
6895
|
+
if (!Array.isArray(parsed)) return { todos: [], found, rest };
|
|
6896
|
+
const MAX_LIVE_TODOS = 50;
|
|
6897
|
+
const order = [];
|
|
6898
|
+
const byId = /* @__PURE__ */ new Map();
|
|
6899
|
+
for (const raw of parsed.slice(0, MAX_LIVE_TODOS)) {
|
|
6900
|
+
if (!raw || typeof raw !== "object") continue;
|
|
6901
|
+
const r = raw;
|
|
6902
|
+
const id = r.id !== void 0 && r.id !== null ? String(r.id) : "";
|
|
6903
|
+
const text = r.text !== void 0 && r.text !== null ? String(r.text) : "";
|
|
6904
|
+
if (!id || !text) continue;
|
|
6905
|
+
const state = allowed.has(String(r.state)) ? String(r.state) : "pending";
|
|
6906
|
+
const note = r.note !== void 0 && r.note !== null ? String(r.note) : void 0;
|
|
6907
|
+
if (!byId.has(id)) order.push(id);
|
|
6908
|
+
byId.set(id, { id, text, state, note, source: "live" });
|
|
6909
|
+
}
|
|
6910
|
+
const todos = order.map((id) => byId.get(id)).filter(Boolean);
|
|
6911
|
+
return { todos, found, rest };
|
|
6912
|
+
}
|
|
6913
|
+
function parsePreamble(response) {
|
|
6914
|
+
const text = String(response ?? "");
|
|
6915
|
+
const m = text.match(/^(\s*)\[INTENT\][ \t]*([^\r\n]*)(\r?\n)?/i);
|
|
6916
|
+
if (!m) return { intent: null, found: false, rest: text };
|
|
6917
|
+
const intent = String(m[2] ?? "").trim();
|
|
6918
|
+
if (!intent) return { intent: null, found: false, rest: text };
|
|
6919
|
+
const rest = text.slice(m[0].length).replace(/^\r?\n/, "");
|
|
6920
|
+
return { intent, found: true, rest };
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6765
6923
|
// src/generated/cesar/session.ts
|
|
6766
|
-
import { readFileSync as
|
|
6924
|
+
import { readFileSync as readFileSync13, statSync as statSync5, existsSync as existsSync13 } from "fs";
|
|
6767
6925
|
import { isAbsolute as isAbsolute2, resolve as resolve3, dirname as dirname5 } from "path";
|
|
6768
6926
|
import { join as join12 } from "path";
|
|
6769
6927
|
import { mkdirSync as mkdirSync11 } from "fs";
|
|
@@ -6775,6 +6933,7 @@ function createCesarToolRegistry(engineId) {
|
|
|
6775
6933
|
const toolRegistry = new ToolRegistry();
|
|
6776
6934
|
toolRegistry.register(createReadTool());
|
|
6777
6935
|
toolRegistry.register(createEditTool());
|
|
6936
|
+
toolRegistry.register(createMultiEditTool());
|
|
6778
6937
|
toolRegistry.register(createWriteTool());
|
|
6779
6938
|
toolRegistry.register(createBashTool());
|
|
6780
6939
|
toolRegistry.register(createGrepTool());
|
|
@@ -6792,6 +6951,7 @@ function createCesarToolRegistry(engineId) {
|
|
|
6792
6951
|
toolRegistry.register(createReportConfidenceTool());
|
|
6793
6952
|
toolRegistry.register(createQuickNeroTool());
|
|
6794
6953
|
toolRegistry.register(createTodoWriteTool());
|
|
6954
|
+
toolRegistry.register(createSaveMemoryTool());
|
|
6795
6955
|
toolRegistry.register(createProposePlanTool());
|
|
6796
6956
|
toolRegistry.register(createExitPlanModeTool());
|
|
6797
6957
|
toolRegistry.register(createListPlansTool());
|
|
@@ -6802,7 +6962,7 @@ function createEagerToolContext(ctx, config, signal, dispatch) {
|
|
|
6802
6962
|
const cwd = resolveWorkingDir();
|
|
6803
6963
|
const fsc = getProjectFileStateCache(cwd);
|
|
6804
6964
|
const explorationMode = ctx.explorationMode ?? false;
|
|
6805
|
-
return { cwd, readFileState: fsc.cache, abortSignal: signal, permissionMode: config.permissionMode ?? "ask", explorationMode, allowedCommands: config.allowedCommands ?? [], toolPermissions: config.toolPermissions ?? {}, source: "orchestrator", onProgress: (msg) => dispatch({ type: "spinner-update", message: `Cesar: ${msg}` }) };
|
|
6965
|
+
return { cwd, readFileState: fsc.cache, abortSignal: signal, permissionMode: config.permissionMode ?? "ask", explorationMode, allowedCommands: config.allowedCommands ?? [], toolPermissions: config.toolPermissions ?? {}, source: "orchestrator", permissionRules: parsePermissionRuleSet(config.permissions), toolHooks: parseToolHooks(config.hooks), onProgress: (msg) => dispatch({ type: "spinner-update", message: `Cesar: ${msg}` }) };
|
|
6806
6966
|
}
|
|
6807
6967
|
function parseEagerToolInput(toolName, input) {
|
|
6808
6968
|
const raw = typeof input === "string" ? input : input === void 0 ? "" : (() => {
|
|
@@ -6860,7 +7020,7 @@ async function executeEagerTool(toolName, meta, toolRegistry, toolCtx, dispatch,
|
|
|
6860
7020
|
result: { ok: false, content: "", error },
|
|
6861
7021
|
durationMs: 0
|
|
6862
7022
|
};
|
|
6863
|
-
dispatch({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: error });
|
|
7023
|
+
dispatch({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: error, durationMs: result2.durationMs });
|
|
6864
7024
|
return result2;
|
|
6865
7025
|
}
|
|
6866
7026
|
const parsedInput = parsed.input ?? {};
|
|
@@ -6891,9 +7051,9 @@ async function executeEagerTool(toolName, meta, toolRegistry, toolCtx, dispatch,
|
|
|
6891
7051
|
const out = result.result.ok ? result.result.content : result.result.error;
|
|
6892
7052
|
const status = result.result.ok ? "done" : "error";
|
|
6893
7053
|
if (streamStarted) {
|
|
6894
|
-
dispatch({ type: "tool-stream-end", streamId, engineId: cesarEngineId, tool: toolName, input: toolInput, status, output: out });
|
|
7054
|
+
dispatch({ type: "tool-stream-end", streamId, engineId: cesarEngineId, tool: toolName, input: toolInput, status, output: out, durationMs: result.durationMs });
|
|
6895
7055
|
} else {
|
|
6896
|
-
dispatch({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status, output: out });
|
|
7056
|
+
dispatch({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status, output: out, durationMs: result.durationMs });
|
|
6897
7057
|
}
|
|
6898
7058
|
return result;
|
|
6899
7059
|
}
|
|
@@ -7042,6 +7202,9 @@ function updateTodoState(todos, id, state, note) {
|
|
|
7042
7202
|
function clearTodos() {
|
|
7043
7203
|
return [];
|
|
7044
7204
|
}
|
|
7205
|
+
function clearLiveTodos(todos) {
|
|
7206
|
+
return todos.filter((t) => t.source !== "live");
|
|
7207
|
+
}
|
|
7045
7208
|
function todosFromPlanSteps(steps) {
|
|
7046
7209
|
const allowed = /* @__PURE__ */ new Set(["pending", "running", "done", "failed", "cancelled"]);
|
|
7047
7210
|
return steps.map((s) => ({
|
|
@@ -7117,7 +7280,7 @@ function _showNextPermission(actions) {
|
|
|
7117
7280
|
_drainAutoApproved(actions);
|
|
7118
7281
|
if (_permissionQueue.length === 0) return;
|
|
7119
7282
|
const next = _permissionQueue[0];
|
|
7120
|
-
actions.addBlock({ type: "permission-ask", tool: next.tool, command: next.command, description: next.description, reason: next.reason, resolve: next.resolve });
|
|
7283
|
+
actions.addBlock({ type: "permission-ask", tool: next.tool, command: next.command, description: next.description, reason: next.reason, diffPreview: next.diffPreview, fallbackNote: next.fallbackNote, resolve: next.resolve });
|
|
7121
7284
|
const permResolve = next.resolve;
|
|
7122
7285
|
const permCommand = next.command;
|
|
7123
7286
|
actions.setQuestionState({
|
|
@@ -7127,6 +7290,8 @@ function _showNextPermission(actions) {
|
|
|
7127
7290
|
command: permCommand,
|
|
7128
7291
|
description: next.description,
|
|
7129
7292
|
reason: next.reason,
|
|
7293
|
+
diffPreview: next.diffPreview,
|
|
7294
|
+
fallbackNote: next.fallbackNote,
|
|
7130
7295
|
choices: [
|
|
7131
7296
|
{ key: "y", label: "Yes", color: "#4ade80" },
|
|
7132
7297
|
{ key: "n", label: "No", color: "#ef4444" },
|
|
@@ -7192,6 +7357,7 @@ function handleOutputEvent(event, state, actions, mode, chatStartTime) {
|
|
|
7192
7357
|
"context-usage",
|
|
7193
7358
|
"streaming-chunk",
|
|
7194
7359
|
"streaming-start",
|
|
7360
|
+
"streaming-preview",
|
|
7195
7361
|
"thinking-chunk",
|
|
7196
7362
|
"thinking-start",
|
|
7197
7363
|
"thinking-stop",
|
|
@@ -7238,16 +7404,35 @@ function handleOutputEvent(event, state, actions, mode, chatStartTime) {
|
|
|
7238
7404
|
}
|
|
7239
7405
|
return;
|
|
7240
7406
|
}
|
|
7407
|
+
case "streaming-preview": {
|
|
7408
|
+
const eid = event.engineId;
|
|
7409
|
+
const draftText = event.content;
|
|
7410
|
+
actions.setStreamingText((prev) => {
|
|
7411
|
+
const existing = prev[eid];
|
|
7412
|
+
if (existing && !existing.draft) return prev;
|
|
7413
|
+
return {
|
|
7414
|
+
...prev,
|
|
7415
|
+
[eid]: {
|
|
7416
|
+
engineId: eid,
|
|
7417
|
+
content: draftText,
|
|
7418
|
+
startedAt: existing ? existing.startedAt : Date.now(),
|
|
7419
|
+
draft: true
|
|
7420
|
+
}
|
|
7421
|
+
};
|
|
7422
|
+
});
|
|
7423
|
+
return;
|
|
7424
|
+
}
|
|
7241
7425
|
case "streaming-chunk": {
|
|
7242
7426
|
const eid = event.engineId;
|
|
7243
7427
|
const chunk = event.chunk;
|
|
7244
7428
|
actions.setStreamingText((prev) => {
|
|
7245
7429
|
const existing = prev[eid];
|
|
7430
|
+
const base = existing && !existing.draft ? existing.content : "";
|
|
7246
7431
|
return {
|
|
7247
7432
|
...prev,
|
|
7248
7433
|
[eid]: {
|
|
7249
7434
|
engineId: eid,
|
|
7250
|
-
content:
|
|
7435
|
+
content: base + chunk,
|
|
7251
7436
|
startedAt: existing ? existing.startedAt : Date.now()
|
|
7252
7437
|
}
|
|
7253
7438
|
};
|
|
@@ -7258,6 +7443,14 @@ function handleOutputEvent(event, state, actions, mode, chatStartTime) {
|
|
|
7258
7443
|
const eid = event.engineId;
|
|
7259
7444
|
const st = state.streamingText[eid];
|
|
7260
7445
|
if (st) {
|
|
7446
|
+
if (st.draft) {
|
|
7447
|
+
actions.setStreamingText((prev) => {
|
|
7448
|
+
const next = { ...prev };
|
|
7449
|
+
delete next[eid];
|
|
7450
|
+
return next;
|
|
7451
|
+
});
|
|
7452
|
+
return;
|
|
7453
|
+
}
|
|
7261
7454
|
const color = actions.getEngineColor(st.engineId);
|
|
7262
7455
|
const cleaned = cleanEngineOutput(st.content);
|
|
7263
7456
|
actions.setStreamingText((prev) => {
|
|
@@ -7291,7 +7484,12 @@ function handleOutputEvent(event, state, actions, mode, chatStartTime) {
|
|
|
7291
7484
|
return;
|
|
7292
7485
|
}
|
|
7293
7486
|
case "todos-clear": {
|
|
7294
|
-
|
|
7487
|
+
const scope = event.scope;
|
|
7488
|
+
if (scope === "live") {
|
|
7489
|
+
actions.setTodos((prev) => clearLiveTodos(prev));
|
|
7490
|
+
} else {
|
|
7491
|
+
actions.setTodos(clearTodos());
|
|
7492
|
+
}
|
|
7295
7493
|
return;
|
|
7296
7494
|
}
|
|
7297
7495
|
case "clear":
|
|
@@ -7345,6 +7543,8 @@ function handleOutputEvent(event, state, actions, mode, chatStartTime) {
|
|
|
7345
7543
|
command: event.command,
|
|
7346
7544
|
description: event.description,
|
|
7347
7545
|
reason: event.reason,
|
|
7546
|
+
diffPreview: event.diffPreview,
|
|
7547
|
+
fallbackNote: event.fallbackNote,
|
|
7348
7548
|
resolve: event.resolve
|
|
7349
7549
|
};
|
|
7350
7550
|
_permissionQueue.push(entry);
|
|
@@ -7389,7 +7589,7 @@ function handleOutputEvent(event, state, actions, mode, chatStartTime) {
|
|
|
7389
7589
|
}
|
|
7390
7590
|
case "context-usage": {
|
|
7391
7591
|
const e = event;
|
|
7392
|
-
actions.setCesarContext({ pct: e.pct, used: e.used, limit: e.limit, compacted: e.compacted ?? 0, cached: e.cached ?? 0 });
|
|
7592
|
+
actions.setCesarContext({ pct: e.pct, used: e.used, limit: e.limit, compacted: e.compacted ?? 0, cached: e.cached ?? 0, source: e.source ?? "estimate" });
|
|
7393
7593
|
return;
|
|
7394
7594
|
}
|
|
7395
7595
|
case "agent-step-start": {
|
|
@@ -7568,7 +7768,8 @@ function handleOutputEvent(event, state, actions, mode, chatStartTime) {
|
|
|
7568
7768
|
tool: e.tool ?? existing.tool ?? "",
|
|
7569
7769
|
input: e.input ?? existing.input ?? "",
|
|
7570
7770
|
status: e.status,
|
|
7571
|
-
output
|
|
7771
|
+
output,
|
|
7772
|
+
...e.durationMs !== void 0 ? { durationMs: e.durationMs } : {}
|
|
7572
7773
|
};
|
|
7573
7774
|
const key = toolCallKey(toolCallEvent);
|
|
7574
7775
|
if (e.status === "done" || e.status === "error") {
|
|
@@ -8226,8 +8427,22 @@ function shouldSpeculate(hints, config) {
|
|
|
8226
8427
|
|
|
8227
8428
|
// src/generated/cesar/brain-helpers.ts
|
|
8228
8429
|
var yieldToInk = () => new Promise((resolve4) => setImmediate(resolve4));
|
|
8430
|
+
function recordCesarTurn(ctx, cesarEngineId, input, response) {
|
|
8431
|
+
try {
|
|
8432
|
+
const s = ctx.cesarSession;
|
|
8433
|
+
const u = s && typeof s.getTurnUsage === "function" ? s.getTurnUsage() : null;
|
|
8434
|
+
if (u && u.totalTokens > 0) {
|
|
8435
|
+
return tracker.record(cesarEngineId, {
|
|
8436
|
+
usage: { promptTokens: u.promptTokens, completionTokens: u.completionTokens, totalTokens: u.totalTokens, source: u.source ?? "sdk" },
|
|
8437
|
+
model: u.model
|
|
8438
|
+
});
|
|
8439
|
+
}
|
|
8440
|
+
} catch {
|
|
8441
|
+
}
|
|
8442
|
+
return tracker.record(cesarEngineId, { prompt: input, response });
|
|
8443
|
+
}
|
|
8229
8444
|
var splitBeforeToolMarkup = (text) => {
|
|
8230
|
-
const markers = ["<tool ", "<invoke ", "<tool_call", "<toolcall", "<tool_call_tool>", "<function=", "[TOOL_CALLS]"];
|
|
8445
|
+
const markers = ["<tool ", "<invoke ", "<tool_call", "<toolcall", "<tool_call_tool>", "<function=", "[TOOL_CALLS]", "[TODOS]"];
|
|
8231
8446
|
const lower = text.toLowerCase();
|
|
8232
8447
|
let idx = -1;
|
|
8233
8448
|
for (const marker of markers) {
|
|
@@ -8238,6 +8453,109 @@ var splitBeforeToolMarkup = (text) => {
|
|
|
8238
8453
|
return { visible: text.slice(0, idx), hasToolMarkup: true };
|
|
8239
8454
|
};
|
|
8240
8455
|
var XML_TOOL_MARKUP_HOLD_CHARS = 24;
|
|
8456
|
+
var createTodosDisplayStripper = () => {
|
|
8457
|
+
let insideBlock = false;
|
|
8458
|
+
let hold = "";
|
|
8459
|
+
const OPEN = "[todos]";
|
|
8460
|
+
const CLOSE = "[/todos]";
|
|
8461
|
+
const trailingPrefixLen = (combined, marker) => {
|
|
8462
|
+
const lower = combined.toLowerCase();
|
|
8463
|
+
for (let n = Math.min(marker.length - 1, combined.length); n > 0; n--) {
|
|
8464
|
+
if (lower.slice(combined.length - n) === marker.slice(0, n)) return n;
|
|
8465
|
+
}
|
|
8466
|
+
return 0;
|
|
8467
|
+
};
|
|
8468
|
+
return (chunk, force = false) => {
|
|
8469
|
+
let combined = hold + chunk;
|
|
8470
|
+
hold = "";
|
|
8471
|
+
if (force) {
|
|
8472
|
+
insideBlock = false;
|
|
8473
|
+
return combined;
|
|
8474
|
+
}
|
|
8475
|
+
let out = "";
|
|
8476
|
+
while (combined.length > 0) {
|
|
8477
|
+
if (insideBlock) {
|
|
8478
|
+
const end = combined.toLowerCase().indexOf(CLOSE);
|
|
8479
|
+
if (end < 0) {
|
|
8480
|
+
const p = trailingPrefixLen(combined, CLOSE);
|
|
8481
|
+
if (p > 0) hold = combined.slice(combined.length - p);
|
|
8482
|
+
combined = "";
|
|
8483
|
+
break;
|
|
8484
|
+
}
|
|
8485
|
+
insideBlock = false;
|
|
8486
|
+
combined = combined.slice(end + CLOSE.length);
|
|
8487
|
+
continue;
|
|
8488
|
+
}
|
|
8489
|
+
const start = combined.toLowerCase().indexOf(OPEN);
|
|
8490
|
+
if (start < 0) break;
|
|
8491
|
+
out += combined.slice(0, start);
|
|
8492
|
+
insideBlock = true;
|
|
8493
|
+
combined = combined.slice(start + OPEN.length);
|
|
8494
|
+
}
|
|
8495
|
+
if (!insideBlock) {
|
|
8496
|
+
const p = trailingPrefixLen(combined, OPEN);
|
|
8497
|
+
if (p > 0) {
|
|
8498
|
+
hold = combined.slice(combined.length - p);
|
|
8499
|
+
combined = combined.slice(0, combined.length - p);
|
|
8500
|
+
}
|
|
8501
|
+
out += combined;
|
|
8502
|
+
}
|
|
8503
|
+
return out;
|
|
8504
|
+
};
|
|
8505
|
+
};
|
|
8506
|
+
var createPreambleStripper = () => {
|
|
8507
|
+
const MARKER = "[intent]";
|
|
8508
|
+
let decided = false;
|
|
8509
|
+
let hold = "";
|
|
8510
|
+
const couldBeMarker = (lowerTrimmed) => {
|
|
8511
|
+
const n = Math.min(lowerTrimmed.length, MARKER.length);
|
|
8512
|
+
return lowerTrimmed.slice(0, n) === MARKER.slice(0, n);
|
|
8513
|
+
};
|
|
8514
|
+
const stripLeadingPreamble = (text) => {
|
|
8515
|
+
const m = text.match(/^(\s*)\[intent\][ \t]*([^\r\n]*)(\r?\n)?/i);
|
|
8516
|
+
if (!m) return text;
|
|
8517
|
+
if (!String(m[2] ?? "").trim()) return text;
|
|
8518
|
+
return text.slice(m[0].length).replace(/^\r?\n/, "");
|
|
8519
|
+
};
|
|
8520
|
+
return (chunk, force = false) => {
|
|
8521
|
+
if (decided) return chunk;
|
|
8522
|
+
hold += chunk;
|
|
8523
|
+
if (force) {
|
|
8524
|
+
decided = true;
|
|
8525
|
+
const afterWs2 = hold.replace(/^\s*/, "");
|
|
8526
|
+
const out = couldBeMarker(afterWs2.toLowerCase()) && afterWs2.toLowerCase().startsWith(MARKER) ? stripLeadingPreamble(hold) : hold;
|
|
8527
|
+
hold = "";
|
|
8528
|
+
return out;
|
|
8529
|
+
}
|
|
8530
|
+
const leadingWsMatch = hold.match(/^\s*/);
|
|
8531
|
+
const leadingWs = leadingWsMatch ? leadingWsMatch[0] : "";
|
|
8532
|
+
const afterWs = hold.slice(leadingWs.length);
|
|
8533
|
+
const lower = afterWs.toLowerCase();
|
|
8534
|
+
if (!couldBeMarker(lower)) {
|
|
8535
|
+
decided = true;
|
|
8536
|
+
const out = hold;
|
|
8537
|
+
hold = "";
|
|
8538
|
+
return out;
|
|
8539
|
+
}
|
|
8540
|
+
if (lower.length < MARKER.length) return "";
|
|
8541
|
+
const nlIdx = afterWs.search(/\r?\n/);
|
|
8542
|
+
if (nlIdx < 0) {
|
|
8543
|
+
return "";
|
|
8544
|
+
}
|
|
8545
|
+
if (!afterWs.slice(MARKER.length, nlIdx).trim()) {
|
|
8546
|
+
decided = true;
|
|
8547
|
+
const out = hold;
|
|
8548
|
+
hold = "";
|
|
8549
|
+
return out;
|
|
8550
|
+
}
|
|
8551
|
+
const nlMatch = afterWs.slice(nlIdx).match(/^\r?\n/);
|
|
8552
|
+
const nlLen = nlMatch ? nlMatch[0].length : 0;
|
|
8553
|
+
const rest = afterWs.slice(nlIdx + nlLen);
|
|
8554
|
+
decided = true;
|
|
8555
|
+
hold = "";
|
|
8556
|
+
return rest;
|
|
8557
|
+
};
|
|
8558
|
+
};
|
|
8241
8559
|
function isUserDirectedQuestion(lastLine) {
|
|
8242
8560
|
const last = String(lastLine ?? "").trim();
|
|
8243
8561
|
if (!/\?\s*$/.test(last)) return false;
|
|
@@ -8281,20 +8599,50 @@ function detectNarratedToolStall(text) {
|
|
|
8281
8599
|
const STALL_RE = /\b(?:let me (?:check|look|examine|read|search|find|see|review|explore|investigate|understand|get|grab|continue)|i (?:need|want|should|will) (?:to )?(?:check|look|examine|read|search|find|see|review|explore|investigate|understand|get|grab|continue)|now (?:let me|i'll)|continu(?:e|ing)|keep (?:reading|investigating|looking)|next[,.]?\s*(?:i|let)|before (?:i can|proceeding|implementing|deciding))\b/i;
|
|
8282
8600
|
return FAKE_GATE_RE.test(tail) || READ_INTENT_RE.test(tail) || SEARCH_INTENT_RE.test(tail) || DIR_INTENT_RE.test(tail) || STALL_RE.test(tail);
|
|
8283
8601
|
}
|
|
8602
|
+
function stripNonAssertionSpans(text) {
|
|
8603
|
+
let body = String(text ?? "");
|
|
8604
|
+
const KEEP_CLAIM_RE = /^(?:i(?:'ll|'m|\s+will|\s+have|\s+need\s+to|\s+want\s+to|\s+already)\b|let\s+me\b|please\s+\w+)/i;
|
|
8605
|
+
body = body.replace(/```[\s\S]*?(?:```|$)/g, " ");
|
|
8606
|
+
body = body.replace(/`([^`\n]{1,300})`/g, (_m, inner) => KEEP_CLAIM_RE.test(inner.trim()) ? ` ${inner} ` : " ");
|
|
8607
|
+
body = body.replace(/["“”]([^"“”\n]{1,300})["“”]/g, (_m, inner) => KEEP_CLAIM_RE.test(inner.trim()) ? ` ${inner} ` : " ");
|
|
8608
|
+
body = body.replace(/\([A-Z][A-Za-z]*(?:\s*[,/&]\s*[A-Z][A-Za-z]*)+\)/g, " ");
|
|
8609
|
+
return body;
|
|
8610
|
+
}
|
|
8284
8611
|
function detectMutationIntentStall(text) {
|
|
8285
|
-
const body = String(text ?? "").trim();
|
|
8612
|
+
const body = stripNonAssertionSpans(String(text ?? "")).trim();
|
|
8286
8613
|
if (!body) return false;
|
|
8287
|
-
const
|
|
8288
|
-
const
|
|
8289
|
-
|
|
8614
|
+
const CANNOT_RE = /\b(?:i\s+(?:cannot|can'?t|am\s+unable\s+to)\s+(?:write|edit|apply|mutate|touch)|i'?m\s+read-?only|(?:this|the)\s+(?:session|environment|context|sandbox)\s+is\s+read-?only|i\s+have\s+no\s+(?:write|edit|bash)\s+tools?|no\s+(?:write|edit)\s+access|(?:edit|write|bash)(?:\s+tool)?\s+is\s+(?:not\s+(?:enabled|available|wired)|disabled|unavailable)|not\s+(?:enabled|wired|reachable)\s+in\s+this\s+(?:context|session|turn))\b/i;
|
|
8615
|
+
const USER_APPLY_RE = /\b(?:paste\s+(?:it|this|the)\b|git\s+apply\b|apply\s+(?:it|this)\b|in\s+your\s+terminal|you\s+(?:can\s+)?(?:run|apply|paste)\b|copy[- ]?paste|hand\s+you\s+the\s+(?:patch|diff|commands?))/i;
|
|
8616
|
+
const AGENT_ESCAPE_RE = /\b(?:spawn|dispatch)\s+(?:an?\s+)?agent\b/i;
|
|
8617
|
+
const INTENT_RE = /\b(?:i(?:'ll|\s+will|\s+need\s+to|\s+want\s+to|'?m\s+going\s+to)|let\s+me)\s+(?:\w+\s+){0,3}?(?:edit|write|apply|commit|patch|fix|update|implement|modify|create|make)\b/i;
|
|
8618
|
+
const RESULT_FIRST_RE = /\b(?:patch|fix|change|diff|edit|update)e?s?\s+(?:is|are)?\s*(?:ready|done|complete|prepared|below|attached)\b|\bready\s+to\s+(?:paste|apply)\b/i;
|
|
8619
|
+
const PAST_DONE_RE = /\bi(?:'ve)?\s+(?:already\s+)?(?:made|updated|edited|wrote|written|applied|changed|fixed|patched|implemented|created|prepared)\b/i;
|
|
8620
|
+
const PRESENTATION_RE = /\bhere(?:'s|\s+is|\s+are)\s+(?:the\s+|a\s+)?(?:full\s+)?(?:diff|patch|change|fix|edit|code)\b|\b(?:diff|patch)\s+(?:is\s+)?(?:below|above)\b/i;
|
|
8621
|
+
const intentish = INTENT_RE.test(body) || RESULT_FIRST_RE.test(body) || PAST_DONE_RE.test(body) || PRESENTATION_RE.test(body);
|
|
8622
|
+
if (CANNOT_RE.test(body) && (intentish || USER_APPLY_RE.test(body) || AGENT_ESCAPE_RE.test(body))) return true;
|
|
8623
|
+
if (USER_APPLY_RE.test(body) && intentish) return true;
|
|
8624
|
+
if (AGENT_ESCAPE_RE.test(body) && (intentish || CANNOT_RE.test(body))) return true;
|
|
8625
|
+
return false;
|
|
8290
8626
|
}
|
|
8291
8627
|
function detectFabricatedDelegation(text) {
|
|
8292
|
-
const body = String(text ?? "").trim();
|
|
8628
|
+
const body = stripNonAssertionSpans(String(text ?? "")).trim();
|
|
8293
8629
|
if (!body) return false;
|
|
8294
8630
|
const TARGET_RE = /\b(?:review(?:er)?s?|forg(?:e|ing)|tribunal|brainstorm|campfire|agents?|engines?|jobs?)\b/i;
|
|
8295
8631
|
if (!TARGET_RE.test(body)) return false;
|
|
8296
|
-
const
|
|
8297
|
-
|
|
8632
|
+
const FP_DISPATCH_RE = /\b(?:i(?:'ve)?\s+(?:already\s+)?(?:kick(?:ed)?\s*(?:it|them|that|the\s+\w+)?\s*off|fired\s+(?:it|them|off)|dispatch(?:ed)?|delegat(?:ed)?|started|launched|spun\s+up|queued)|i'?m\s+(?:running|dispatching|launching|starting))\b/i;
|
|
8633
|
+
const DELEGATED_TO_RE = /\b(?:review|forge|tribunal|brainstorm|campfire|jobs?|agents?)\s+(?:was|were|has\s+been|have\s+been)?\s*delegated\s+to\b/i;
|
|
8634
|
+
const RUNNING_STATE_RE = /\b(?:review(?:er)?s?|forge|tribunal|brainstorm|campfire|agents?|engines?|jobs?)\b[^.!?\n]{0,60}?(?:\b(?:is|are)\s+(?:still\s+|currently\s+|each\s+|now\s+)?(?:running|working|reading|evaluating|analyzing|reviewing|on\s+it)\b|\bin\s+progress\b|\bunder\s*way\b)/i;
|
|
8635
|
+
const PASSIVE_DISPATCH_RE = /\b(?:review(?:er)?s?|forge|tribunal|brainstorm|campfire|agents?|engines?|jobs?)\b[^.!?\n]{0,60}?\b(?:was|were|has\s+been|have\s+been|is|are)\s+(?:queued|dispatched|launched|kicked\s+off|spun\s+up|fired\s+off)\b/i;
|
|
8636
|
+
const REPORT_BACK_RE = /\bi'?ll\s+(?:get\s+back|report\s+back|let\s+you\s+know|surface|update)\b|\bwhen\s+they\s+(?:report|land|return|finish|come\s+back)\b/i;
|
|
8637
|
+
return FP_DISPATCH_RE.test(body) || DELEGATED_TO_RE.test(body) || RUNNING_STATE_RE.test(body) || PASSIVE_DISPATCH_RE.test(body) || REPORT_BACK_RE.test(body);
|
|
8638
|
+
}
|
|
8639
|
+
function shouldDeescalateGuard(opts) {
|
|
8640
|
+
const kind = String(opts.intakeKind ?? "");
|
|
8641
|
+
const flow = String(opts.recommendedFlow ?? "");
|
|
8642
|
+
const conversational = kind === "chat" && flow === "answer" || kind === "exploration" && (flow === "campfire" || flow === "brainstorm" || flow === "answer");
|
|
8643
|
+
if (!conversational) return false;
|
|
8644
|
+
if (opts.usedMutatingTool !== false) return false;
|
|
8645
|
+
return true;
|
|
8298
8646
|
}
|
|
8299
8647
|
function eagerFailedToolNames(results) {
|
|
8300
8648
|
const names = [];
|
|
@@ -8323,6 +8671,17 @@ function shouldStopAfterXmlToolCall(toolName) {
|
|
|
8323
8671
|
const HANDOFF_TOOLS = /* @__PURE__ */ new Set(["Forge", "Brainstorm", "Tribunal", "Campfire", "Pipeline", "Review", "Agent", "Goal", "ProposePlan", "ExitPlanMode"]);
|
|
8324
8672
|
return HANDOFF_TOOLS.has(String(toolName ?? ""));
|
|
8325
8673
|
}
|
|
8674
|
+
function stripAgonToolPrefix(name) {
|
|
8675
|
+
const n = String(name ?? "");
|
|
8676
|
+
return n.toLowerCase().startsWith("agon") ? n.slice(4) : n;
|
|
8677
|
+
}
|
|
8678
|
+
function isBashToolName(name) {
|
|
8679
|
+
return stripAgonToolPrefix(name).toLowerCase() === "bash";
|
|
8680
|
+
}
|
|
8681
|
+
var WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "multiedit", "notebookedit"]);
|
|
8682
|
+
function isWriteToolName(name) {
|
|
8683
|
+
return WRITE_TOOL_NAMES.has(stripAgonToolPrefix(name).toLowerCase());
|
|
8684
|
+
}
|
|
8326
8685
|
function buildReviewFollowupPrompt(input, ctx) {
|
|
8327
8686
|
const trimmed = input.trim();
|
|
8328
8687
|
const match = trimmed.match(/^fix it(?:\s+with\s+([a-z0-9._-]+))?[\s?!.,;:]*$/i);
|
|
@@ -8383,6 +8742,9 @@ function canonicalToolName(tool) {
|
|
|
8383
8742
|
if (lower === "agonedit" || lower === "edit" || lower === "fileedit" || lower === "filechange") {
|
|
8384
8743
|
return "Edit";
|
|
8385
8744
|
}
|
|
8745
|
+
if (lower === "agonmultiedit" || lower === "multiedit" || lower === "multi_edit") {
|
|
8746
|
+
return "MultiEdit";
|
|
8747
|
+
}
|
|
8386
8748
|
if (lower === "agonwrite" || lower === "write") {
|
|
8387
8749
|
return "Write";
|
|
8388
8750
|
}
|
|
@@ -8391,6 +8753,32 @@ function canonicalToolName(tool) {
|
|
|
8391
8753
|
}
|
|
8392
8754
|
return tool;
|
|
8393
8755
|
}
|
|
8756
|
+
function estimateMultiEditApproval(cachedContent, edits) {
|
|
8757
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
8758
|
+
return { ok: false, reason: "missing edits array", nextContent: "", tokens: 0 };
|
|
8759
|
+
}
|
|
8760
|
+
let working = cachedContent;
|
|
8761
|
+
let tokens = 0;
|
|
8762
|
+
for (let i = 0; i < edits.length; i++) {
|
|
8763
|
+
const e = edits[i] ?? {};
|
|
8764
|
+
const oldS = e.old_string;
|
|
8765
|
+
const newS = e.new_string;
|
|
8766
|
+
if (typeof oldS !== "string" || typeof newS !== "string" || oldS === "") {
|
|
8767
|
+
return { ok: false, reason: `edits[${i}] missing/empty old_string or new_string`, nextContent: "", tokens: 0 };
|
|
8768
|
+
}
|
|
8769
|
+
const occ = countOccurrences(working, oldS);
|
|
8770
|
+
if (occ <= 0) {
|
|
8771
|
+
return { ok: false, reason: `edits[${i}] old_string not found in cached content`, nextContent: "", tokens: 0 };
|
|
8772
|
+
}
|
|
8773
|
+
const replaceAll = e.replace_all === true;
|
|
8774
|
+
if (!replaceAll && occ > 1) {
|
|
8775
|
+
return { ok: false, reason: `edits[${i}] old_string not unique and replace_all not set`, nextContent: "", tokens: 0 };
|
|
8776
|
+
}
|
|
8777
|
+
tokens += estimateChangedTokens(oldS, newS) * (replaceAll ? occ : 1);
|
|
8778
|
+
working = replaceAll ? working.split(oldS).join(newS) : working.replace(oldS, newS);
|
|
8779
|
+
}
|
|
8780
|
+
return { ok: true, reason: "ok", nextContent: working, tokens };
|
|
8781
|
+
}
|
|
8394
8782
|
function approvalArgsFromCommand(tool, command) {
|
|
8395
8783
|
const raw = String(command ?? "").trim();
|
|
8396
8784
|
if (!raw) return {};
|
|
@@ -8512,7 +8900,7 @@ function applyCesarSelfTurnApproval(tool, args, toolCtx, config) {
|
|
|
8512
8900
|
return { approve: false, reason: "read-only mode active" };
|
|
8513
8901
|
}
|
|
8514
8902
|
const t = canonicalToolName(tool);
|
|
8515
|
-
if (t !== "Edit" && t !== "Write") {
|
|
8903
|
+
if (t !== "Edit" && t !== "Write" && t !== "MultiEdit") {
|
|
8516
8904
|
return { approve: false, reason: "not a file edit/write" };
|
|
8517
8905
|
}
|
|
8518
8906
|
const configured = toolCtx.toolPermissions?.[t];
|
|
@@ -8560,6 +8948,13 @@ function applyCesarSelfTurnApproval(tool, args, toolCtx, config) {
|
|
|
8560
8948
|
const editMultiplier = normalizedArgs.replace_all === true ? occurrences : 1;
|
|
8561
8949
|
diffTokens = perEditTokens * editMultiplier;
|
|
8562
8950
|
nextContent = normalizedArgs.replace_all === true ? cached.content.split(oldString).join(newString) : cached.content.replace(oldString, newString);
|
|
8951
|
+
} else if (t === "MultiEdit") {
|
|
8952
|
+
const meRes = estimateMultiEditApproval(cached.content, normalizedArgs.edits);
|
|
8953
|
+
if (!meRes.ok) {
|
|
8954
|
+
return { approve: false, reason: `MultiEdit ${meRes.reason}`, tool: t, path: filePath };
|
|
8955
|
+
}
|
|
8956
|
+
diffTokens = meRes.tokens;
|
|
8957
|
+
nextContent = meRes.nextContent;
|
|
8563
8958
|
} else {
|
|
8564
8959
|
const content = normalizedArgs.content;
|
|
8565
8960
|
if (typeof content !== "string") {
|
|
@@ -8577,8 +8972,264 @@ function applyCesarSelfTurnApproval(tool, args, toolCtx, config) {
|
|
|
8577
8972
|
return { approve: true, reason: `bounded ${t} on previously read file (${diffTokens} tokens)`, tool: t, path: filePath, diffTokens };
|
|
8578
8973
|
}
|
|
8579
8974
|
|
|
8975
|
+
// src/generated/cesar/approval-diff.ts
|
|
8976
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
|
|
8977
|
+
var APPROVAL_DIFF_MAX_LINES_PER_FILE = 8;
|
|
8978
|
+
var APPROVAL_DIFF_MAX_TOTAL_LINES = 24;
|
|
8979
|
+
var APPROVAL_DIFF_MAX_FILE_BYTES = 262144;
|
|
8980
|
+
var APPROVAL_DIFF_MAX_CONTENT_CHARS = 262144;
|
|
8981
|
+
function approvalToolIsFileMutating(tool) {
|
|
8982
|
+
const key = String(tool ?? "").toLowerCase();
|
|
8983
|
+
return key === "edit" || key === "write" || key === "agonedit" || key === "agonwrite" || key === "multiedit" || key === "agonmultiedit";
|
|
8984
|
+
}
|
|
8985
|
+
function isProbablyBinary(text) {
|
|
8986
|
+
const sample = text.slice(0, 8192);
|
|
8987
|
+
return sample.indexOf("\0") !== -1;
|
|
8988
|
+
}
|
|
8989
|
+
function normalizeCurlyQuotes(text) {
|
|
8990
|
+
return text.replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"');
|
|
8991
|
+
}
|
|
8992
|
+
function countOccurrences2(haystack, needle) {
|
|
8993
|
+
if (!needle) return 0;
|
|
8994
|
+
let count = 0;
|
|
8995
|
+
let pos = 0;
|
|
8996
|
+
while (true) {
|
|
8997
|
+
pos = haystack.indexOf(needle, pos);
|
|
8998
|
+
if (pos === -1) break;
|
|
8999
|
+
count++;
|
|
9000
|
+
pos += needle.length;
|
|
9001
|
+
}
|
|
9002
|
+
return count;
|
|
9003
|
+
}
|
|
9004
|
+
function buildApprovalDiffPreview(tool, args) {
|
|
9005
|
+
const key = String(tool ?? "").toLowerCase();
|
|
9006
|
+
const filePath = typeof args?.file_path === "string" ? String(args.file_path) : "";
|
|
9007
|
+
if (!filePath) return null;
|
|
9008
|
+
const cwd = process.cwd();
|
|
9009
|
+
const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length).replace(/^[/\\]/, "") : filePath;
|
|
9010
|
+
let oldContent = "";
|
|
9011
|
+
let newContent = "";
|
|
9012
|
+
let status = "edited";
|
|
9013
|
+
if (key === "write" || key === "agonwrite") {
|
|
9014
|
+
if (typeof args?.content !== "string") return null;
|
|
9015
|
+
newContent = String(args.content);
|
|
9016
|
+
const exists = existsSync11(filePath);
|
|
9017
|
+
if (!exists) {
|
|
9018
|
+
status = "created";
|
|
9019
|
+
oldContent = "";
|
|
9020
|
+
} else {
|
|
9021
|
+
status = "edited";
|
|
9022
|
+
try {
|
|
9023
|
+
const st = statSync4(filePath);
|
|
9024
|
+
if (st.size > APPROVAL_DIFF_MAX_FILE_BYTES) {
|
|
9025
|
+
return { fallback: `file is ${Math.round(st.size / 1024)}KB \u2014 diff hidden` };
|
|
9026
|
+
}
|
|
9027
|
+
oldContent = readFileSync11(filePath, "utf-8");
|
|
9028
|
+
} catch {
|
|
9029
|
+
oldContent = "";
|
|
9030
|
+
}
|
|
9031
|
+
}
|
|
9032
|
+
if (newContent.length > APPROVAL_DIFF_MAX_CONTENT_CHARS) {
|
|
9033
|
+
return { fallback: `new content is ${Math.round(newContent.length / 1024)}KB \u2014 diff hidden` };
|
|
9034
|
+
}
|
|
9035
|
+
} else if (key === "edit" || key === "agonedit") {
|
|
9036
|
+
const oldStr = typeof args?.old_string === "string" ? String(args.old_string) : "";
|
|
9037
|
+
const newStr = typeof args?.new_string === "string" ? String(args.new_string) : "";
|
|
9038
|
+
const replaceAll = args?.replace_all === true;
|
|
9039
|
+
if (oldStr === "") return { fallback: "empty old_string \u2014 edit will fail" };
|
|
9040
|
+
if (!existsSync11(filePath)) return null;
|
|
9041
|
+
try {
|
|
9042
|
+
const st = statSync4(filePath);
|
|
9043
|
+
if (st.size > APPROVAL_DIFF_MAX_FILE_BYTES) {
|
|
9044
|
+
return { fallback: `file is ${Math.round(st.size / 1024)}KB \u2014 diff hidden` };
|
|
9045
|
+
}
|
|
9046
|
+
oldContent = readFileSync11(filePath, "utf-8");
|
|
9047
|
+
} catch {
|
|
9048
|
+
return null;
|
|
9049
|
+
}
|
|
9050
|
+
let searchStr = oldStr;
|
|
9051
|
+
let baseContent = oldContent;
|
|
9052
|
+
if (oldStr && !baseContent.includes(searchStr)) {
|
|
9053
|
+
const normContent = normalizeCurlyQuotes(baseContent);
|
|
9054
|
+
const normSearch = normalizeCurlyQuotes(oldStr);
|
|
9055
|
+
if (normContent.includes(normSearch)) {
|
|
9056
|
+
baseContent = normContent;
|
|
9057
|
+
searchStr = normSearch;
|
|
9058
|
+
} else {
|
|
9059
|
+
return null;
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
if (searchStr && !replaceAll) {
|
|
9063
|
+
const occurrences = countOccurrences2(baseContent, searchStr);
|
|
9064
|
+
if (occurrences > 1) {
|
|
9065
|
+
return { fallback: `old_string matches ${occurrences} locations \u2014 edit will fail` };
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
oldContent = baseContent;
|
|
9069
|
+
newContent = searchStr ? replaceAll ? baseContent.split(searchStr).join(newStr) : baseContent.replace(searchStr, newStr) : baseContent;
|
|
9070
|
+
} else if (key === "multiedit" || key === "agonmultiedit") {
|
|
9071
|
+
const edits = Array.isArray(args?.edits) ? args.edits : null;
|
|
9072
|
+
if (!edits || edits.length === 0) return null;
|
|
9073
|
+
if (!existsSync11(filePath)) return null;
|
|
9074
|
+
try {
|
|
9075
|
+
const st = statSync4(filePath);
|
|
9076
|
+
if (st.size > APPROVAL_DIFF_MAX_FILE_BYTES) {
|
|
9077
|
+
return { fallback: `file is ${Math.round(st.size / 1024)}KB \u2014 diff hidden` };
|
|
9078
|
+
}
|
|
9079
|
+
oldContent = readFileSync11(filePath, "utf-8");
|
|
9080
|
+
} catch {
|
|
9081
|
+
return null;
|
|
9082
|
+
}
|
|
9083
|
+
let working = oldContent;
|
|
9084
|
+
let failed = false;
|
|
9085
|
+
for (let i = 0; i < edits.length; i++) {
|
|
9086
|
+
const e = edits[i] ?? {};
|
|
9087
|
+
const oldStr2 = typeof e.old_string === "string" ? e.old_string : "";
|
|
9088
|
+
const newStr2 = typeof e.new_string === "string" ? e.new_string : "";
|
|
9089
|
+
const replaceAll2 = e.replace_all === true;
|
|
9090
|
+
if (oldStr2 === "") {
|
|
9091
|
+
failed = true;
|
|
9092
|
+
break;
|
|
9093
|
+
}
|
|
9094
|
+
let search2 = oldStr2;
|
|
9095
|
+
let base2 = working;
|
|
9096
|
+
if (!base2.includes(search2)) {
|
|
9097
|
+
const nb = normalizeCurlyQuotes(base2);
|
|
9098
|
+
const ns = normalizeCurlyQuotes(oldStr2);
|
|
9099
|
+
if (nb.includes(ns)) {
|
|
9100
|
+
base2 = nb;
|
|
9101
|
+
search2 = ns;
|
|
9102
|
+
} else {
|
|
9103
|
+
failed = true;
|
|
9104
|
+
break;
|
|
9105
|
+
}
|
|
9106
|
+
}
|
|
9107
|
+
if (!replaceAll2 && countOccurrences2(base2, search2) > 1) {
|
|
9108
|
+
failed = true;
|
|
9109
|
+
break;
|
|
9110
|
+
}
|
|
9111
|
+
working = replaceAll2 ? base2.split(search2).join(newStr2) : base2.replace(search2, newStr2);
|
|
9112
|
+
}
|
|
9113
|
+
if (failed) return { fallback: "one or more edits will not apply \u2014 see prompt" };
|
|
9114
|
+
status = "edited";
|
|
9115
|
+
newContent = working;
|
|
9116
|
+
} else {
|
|
9117
|
+
return null;
|
|
9118
|
+
}
|
|
9119
|
+
if (isProbablyBinary(oldContent) || isProbablyBinary(newContent)) {
|
|
9120
|
+
return { fallback: "binary file \u2014 diff hidden" };
|
|
9121
|
+
}
|
|
9122
|
+
if (oldContent === newContent) {
|
|
9123
|
+
return null;
|
|
9124
|
+
}
|
|
9125
|
+
const file = computeFileDiffPreview(
|
|
9126
|
+
filePath,
|
|
9127
|
+
relPath,
|
|
9128
|
+
status,
|
|
9129
|
+
oldContent,
|
|
9130
|
+
newContent,
|
|
9131
|
+
APPROVAL_DIFF_MAX_LINES_PER_FILE,
|
|
9132
|
+
APPROVAL_DIFF_MAX_TOTAL_LINES
|
|
9133
|
+
);
|
|
9134
|
+
if (!file) return null;
|
|
9135
|
+
return { files: [file], totalFiles: 1 };
|
|
9136
|
+
}
|
|
9137
|
+
function computeFileDiffPreview(path, relPath, status, oldContent, newContent, maxLines, maxTotal) {
|
|
9138
|
+
const oldAll = oldContent.length === 0 ? [] : oldContent.replace(/\n$/, "").split("\n");
|
|
9139
|
+
const newAll = newContent.length === 0 ? [] : newContent.replace(/\n$/, "").split("\n");
|
|
9140
|
+
const APPROVAL_DIFF_MAX_LCS_LINES = 1200;
|
|
9141
|
+
let pre = 0;
|
|
9142
|
+
const oldLenAll = oldAll.length;
|
|
9143
|
+
const newLenAll = newAll.length;
|
|
9144
|
+
while (pre < oldLenAll && pre < newLenAll && oldAll[pre] === newAll[pre]) pre++;
|
|
9145
|
+
let suf = 0;
|
|
9146
|
+
while (suf < oldLenAll - pre && suf < newLenAll - pre && oldAll[oldLenAll - 1 - suf] === newAll[newLenAll - 1 - suf]) suf++;
|
|
9147
|
+
const oldLines = oldAll.slice(pre, oldLenAll - suf);
|
|
9148
|
+
const newLines = newAll.slice(pre, newLenAll - suf);
|
|
9149
|
+
const n = oldLines.length;
|
|
9150
|
+
const m = newLines.length;
|
|
9151
|
+
if (n + m > APPROVAL_DIFF_MAX_LCS_LINES) {
|
|
9152
|
+
const addCount = m;
|
|
9153
|
+
const delCount = n;
|
|
9154
|
+
return {
|
|
9155
|
+
path,
|
|
9156
|
+
relPath,
|
|
9157
|
+
status,
|
|
9158
|
+
additions: addCount,
|
|
9159
|
+
deletions: delCount,
|
|
9160
|
+
lines: [`@@ +${addCount}/-${delCount} lines \u2014 diff too large to preview @@`],
|
|
9161
|
+
omitted: addCount + delCount
|
|
9162
|
+
};
|
|
9163
|
+
}
|
|
9164
|
+
const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
9165
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
9166
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
9167
|
+
lcs[i2][j2] = oldLines[i2] === newLines[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
|
|
9168
|
+
}
|
|
9169
|
+
}
|
|
9170
|
+
const ops = [];
|
|
9171
|
+
let i = 0;
|
|
9172
|
+
let j = 0;
|
|
9173
|
+
while (i < n && j < m) {
|
|
9174
|
+
if (oldLines[i] === newLines[j]) {
|
|
9175
|
+
ops.push({ kind: " ", text: oldLines[i] });
|
|
9176
|
+
i++;
|
|
9177
|
+
j++;
|
|
9178
|
+
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
9179
|
+
ops.push({ kind: "-", text: oldLines[i] });
|
|
9180
|
+
i++;
|
|
9181
|
+
} else {
|
|
9182
|
+
ops.push({ kind: "+", text: newLines[j] });
|
|
9183
|
+
j++;
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
while (i < n) {
|
|
9187
|
+
ops.push({ kind: "-", text: oldLines[i] });
|
|
9188
|
+
i++;
|
|
9189
|
+
}
|
|
9190
|
+
while (j < m) {
|
|
9191
|
+
ops.push({ kind: "+", text: newLines[j] });
|
|
9192
|
+
j++;
|
|
9193
|
+
}
|
|
9194
|
+
let additions = 0;
|
|
9195
|
+
let deletions = 0;
|
|
9196
|
+
for (const op of ops) {
|
|
9197
|
+
if (op.kind === "+") additions++;
|
|
9198
|
+
else if (op.kind === "-") deletions++;
|
|
9199
|
+
}
|
|
9200
|
+
if (additions === 0 && deletions === 0) return null;
|
|
9201
|
+
const perFileCap = Math.max(1, Math.min(maxLines, maxTotal));
|
|
9202
|
+
const visibleLines = [];
|
|
9203
|
+
let interesting = 0;
|
|
9204
|
+
for (const op of ops) {
|
|
9205
|
+
if (op.kind === " ") continue;
|
|
9206
|
+
interesting++;
|
|
9207
|
+
if (visibleLines.length < perFileCap) {
|
|
9208
|
+
visibleLines.push(`${op.kind}${truncateDiffLine(op.text, 120)}`);
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
const omitted = Math.max(0, interesting - visibleLines.length);
|
|
9212
|
+
const headerLine = `@@ ${status === "created" ? "new file" : `-${deletions} +${additions}`} @@`;
|
|
9213
|
+
return {
|
|
9214
|
+
path,
|
|
9215
|
+
relPath,
|
|
9216
|
+
status,
|
|
9217
|
+
additions,
|
|
9218
|
+
deletions,
|
|
9219
|
+
lines: [headerLine, ...visibleLines],
|
|
9220
|
+
omitted
|
|
9221
|
+
};
|
|
9222
|
+
}
|
|
9223
|
+
function truncateDiffLine(line, max) {
|
|
9224
|
+
const text = String(line ?? "").replace(/\t/g, " ");
|
|
9225
|
+
if (text.length <= max) {
|
|
9226
|
+
return text;
|
|
9227
|
+
}
|
|
9228
|
+
return text.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
9229
|
+
}
|
|
9230
|
+
|
|
8580
9231
|
// src/generated/cesar/tool-observability.ts
|
|
8581
|
-
import { appendFileSync, mkdirSync as mkdirSync10, existsSync as
|
|
9232
|
+
import { appendFileSync, mkdirSync as mkdirSync10, existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
8582
9233
|
import { join as join11 } from "path";
|
|
8583
9234
|
function createCesarTurnId() {
|
|
8584
9235
|
return `cesar-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -8726,11 +9377,11 @@ function recordCesarConfidence(record) {
|
|
|
8726
9377
|
}
|
|
8727
9378
|
}
|
|
8728
9379
|
function readJsonlRecords(filePath) {
|
|
8729
|
-
if (!
|
|
9380
|
+
if (!existsSync12(filePath)) {
|
|
8730
9381
|
return [];
|
|
8731
9382
|
}
|
|
8732
9383
|
const out = [];
|
|
8733
|
-
const text =
|
|
9384
|
+
const text = readFileSync12(filePath, "utf-8");
|
|
8734
9385
|
for (const line of text.split("\n")) {
|
|
8735
9386
|
const trimmed = line.trim();
|
|
8736
9387
|
if (!trimmed) {
|
|
@@ -8957,8 +9608,15 @@ RULE 4b \u2014 MODE PURPOSE (don't mix them up \u2014 and don't always default t
|
|
|
8957
9608
|
Your code will be auto-reviewed after implementation \u2014 write carefully the first time.
|
|
8958
9609
|
RULE 4c \u2014 REVIEW: Use Review(target?, engine?, engines?) to delegate read-only code review. Default target is "uncommitted". Targets: "uncommitted", "branch:NAME", "commit:SHA". Specify engine ONLY when the user explicitly names one via "with <engine>"; if they name multiple reviewers, specify engines:["codex","claude"] or the named set and Agon will run them in parallel. Otherwise choose two diverse engines for high-stakes reviews or omit engine/engines and let Agon auto-select. If the user asks to review AND fix, do NOT stop at Review; call Pipeline(task, fitnessCmd?, engines?) so the findings are fixed immediately through Agon's agent harness.
|
|
8959
9610
|
RULE 5 \u2014 WORKSPACE: Use Read for files. Use Grep for search. NEVER use cat/head/tail/grep via Bash. For shell commands use AgonBash (MCP tool), for edits use AgonEdit, for new files use AgonWrite. The user will see a Y/N/Always prompt for write operations. If they choose "Always", that command is auto-approved going forward. When the user says "commit", call AgonBash with the git commands. Don't say "I can't" or "I need permission" \u2014 call the tool and the permission system handles it.
|
|
9611
|
+
RULE 5b \u2014 DURABLE MEMORY: Call SaveMemory(memory, section) to record a fact that should survive into FUTURE sessions \u2014 and ONLY for that. section is one of Decisions / Constraints / Conventions / Session Notes. Use it when a real, durable fact is established: an architectural decision ("we chose session tokens over JWT"), a hard constraint ("Node 20 floor", "no top-level await"), or a project convention ("commits use conventional format"). Do NOT save transient/session state, the current TODO, things obvious from the code, or speculation \u2014 that is noise next session. One fact per call, no date (it is added automatically); the user confirms each memory. Saved memory comes back as a [PROJECT MEMORY] block at the top of your context in later sessions, so don't re-save what is already there.
|
|
8960
9612
|
RULE 6 \u2014 AFTER DELEGATION: After calling Forge/Brainstorm/Tribunal/Campfire/Pipeline/Review/Agent, STOP. Do not continue responding. The orchestrator handles the rest. After calling Delegate, WAIT for the result \u2014 do NOT stop. Incorporate the delegated result into your response.
|
|
8961
9613
|
RULE 7 \u2014 NO NARRATION: NEVER narrate your research process. Do not write "Reading the file...", "I'm checking...", "Let me look at...", "I've confirmed...". The user sees your text output \u2014 if you narrate exploration it looks like you have no clue. Instead: call tools SILENTLY, then speak ONLY when you have the answer or decision. Your visible output should be conclusions, answers, and actions \u2014 never a play-by-play of your investigation. If you need to read files or search code, call Read/Grep/Glob directly without announcing it.
|
|
9614
|
+
RULE 7b \u2014 LIVE TODO CHECKLIST (the sanctioned exception to RULE 7): For multi-step work \u2014 more than 2 distinct steps \u2014 pin a live checklist so the user can watch progress without you narrating it. Emit a [TODOS] block: one JSON array, each item {"id","text","state"} where state is pending|running|done|failed. It is INVISIBLE to the user (the runtime strips it and renders a pinned checklist instead) \u2014 never describe it in prose. Re-emit the WHOLE block (latest snapshot) whenever a step's state changes \u2014 typically right after a tool finishes \u2014 so items tick from running to done as you work. OPTIONAL: skip it for single-step or chat turns. Example for "refactor the parser and add a test":
|
|
9615
|
+
[TODOS]
|
|
9616
|
+
[{"id":"1","text":"Read the parser module","state":"done"},{"id":"2","text":"Refactor the parse loop","state":"running"},{"id":"3","text":"Add a unit test","state":"pending"}]
|
|
9617
|
+
[/TODOS]
|
|
9618
|
+
Keep texts short (\u22646 words). Do NOT use [TODOS] in plan mode \u2014 there ProposePlan owns the checklist.
|
|
9619
|
+
RULE 7c \u2014 INTENT PREAMBLE (the second sanctioned exception to RULE 7): Before multi-step work, you MAY open your response with ONE short intent line as the VERY FIRST line: [INTENT] <one line>. It is INVISIBLE prose to the user (the runtime strips it and renders a distinct dim line BEFORE your tool work) \u2014 never describe it. Only at the start of the response counts; keep it to one line, e.g. [INTENT] Tracing the dispatch path before changing timeouts. OPTIONAL: skip it for single-step or chat turns.
|
|
8962
9620
|
|
|
8963
9621
|
RULE 8 \u2014 AUTONOMOUS PLANS: Plan mode is optional, not the default. Stay live unless staged execution is genuinely useful. Switch to planning when the task needs multiple dependent steps, expensive orchestration, resumability, explicit approval, or cost visibility. When you call ProposePlan, decide whether to set autoApprove=true. Set it ONLY when (a) the user clearly described a multi-stage workflow ("plan it, build it, review it"; "investigate then forge it"; "do the whole thing autonomously") AND (b) you have HIGH confidence in the steps after investigation (not before). The runtime applies a layered policy and may still ask the user \u2014 your autoApprove=true is permission, not a guarantee. Default to autoApprove=false (or omit it) whenever you are uncertain or when the plan touches mutating steps and the user did not explicitly invite autonomous execution. selfReview defaults to true for mutating plans \u2014 only set selfReview=false for purely advisory plans (brainstorm/tribunal/research only) where a code-review gate would have nothing to review.
|
|
8964
9622
|
RULE 8b \u2014 AUTONOMOUS BUILD TOOLS (Goal / Conquer): These run a build to completion in the BACKGROUND. Goal(intent, queue?, gate?) drives a finite, machine-verifiable task QUEUE (forge \u2192 witness \u2192 review \u2192 commit per task on a goal/* branch). Conquer(task, gate, builder?, engines?) is for an OPEN-ENDED build you'd otherwise babysit: it drives an external builder CLI as the user (builder defaults to codex \u2014 pass builder:"codex" / "claude" / "agy") unattended until the gate passes, convening nero/tribunal/council on forks, then STOPS at a human merge gate (NEVER auto-merges). Fire EITHER ONLY when the user explicitly asks to build it unattended ("conquer this with codex", "build it autonomously") \u2014 NEVER on a vague request; both are long, real-spend, multi-hour runs. Conquer REQUIRES a discriminating gate (the done-spec, e.g. gate:"pnpm test"); if the user did not give one, ASK for the command that proves the build is done before calling. After calling either, STOP and wait \u2014 the background job and the merge gate handle the rest.
|
|
@@ -8976,7 +9634,12 @@ RULE 10 \u2014 TURN CLOSURE: End every turn with one clear closing line so the u
|
|
|
8976
9634
|
|
|
8977
9635
|
FORBIDDEN closure shapes: "Standing by \u2014 awaiting your call on: 1. Commit? 2. Tests? 3. Both?" \u2014 that reads as still-thinking, not a closure. "Or move on to something else." trailing line \u2014 that's filler. A menu of next-steps you could simply do yourself \u2014 that is not a genuine fork. Stopping after only a recap with no done/asking/fork line \u2014 forbidden, always.
|
|
8978
9636
|
|
|
8979
|
-
|
|
9637
|
+
LEAD WITH FINDINGS: Your FIRST sentence answers what happened or what you found \u2014 the conclusion, not the preamble. Supporting detail comes after. Use prose for a simple answer; reach for bullets only when the content is genuinely a list. No filler openers ("Great question", "Let me explain", "Sure!"). Don't bury the lede under a recap of what you did.
|
|
9638
|
+
GOOD: "The leak is in brain.kern \u2014 the drain timer never clears on early return. Added the cleanup at line 793; typecheck green."
|
|
9639
|
+
BAD: "Great question! So I went and looked into this. There are a few things going on here. First, let me walk through the architecture. The session has a drain timer..."
|
|
9640
|
+
|
|
9641
|
+
CONFIDENCE REFRESH: If your confidence changed materially during the turn (e.g. started at 40% "need to verify" and verified successfully \u2192 real confidence is now 95%), call ReportConfidence again with the new number BEFORE the closing line. Stale initial confidence in the recap misleads the user about what state you're in.
|
|
9642
|
+
If you END the turn still below ~85%, write CONFIDENCE: NN% once near your closing line \u2014 the UI uses that exact shape to offer the user an escalation (nero/tribunal).`;
|
|
8980
9643
|
function buildCesarSystemPrompt(ctx) {
|
|
8981
9644
|
const config = ctx.config;
|
|
8982
9645
|
const cesarCwd = resolveWorkingDir();
|
|
@@ -9008,6 +9671,18 @@ MODES vs ENGINES: the names above are ENGINES \u2014 runnable backends you deleg
|
|
|
9008
9671
|
systemParts.push(`## OPERATING MODE
|
|
9009
9672
|
Exploration mode is ON. Stay read-only: inspect files, search, and use read-only shell commands only. Do not call Edit or Write. Do not run non-read-only Bash commands.`);
|
|
9010
9673
|
}
|
|
9674
|
+
const commitCoAuthor = (config.commitCoAuthor || "").replace(/[`\r\n]+/g, " ").replace(/\s+/g, " ").trim();
|
|
9675
|
+
if (commitCoAuthor) {
|
|
9676
|
+
systemParts.push(`COMMIT ATTRIBUTION: When you create a git commit yourself via Bash, end the commit message with a trailer line \`Co-Authored-By: ${commitCoAuthor}\`. This attribution is configurable (config.commitCoAuthor) and is ON by default.
|
|
9677
|
+
- If the user asks to REMOVE/DISABLE the commit logo/attribution, first ask whether to disable it for THIS PROJECT or MACHINE-WIDE, then disable it: machine-wide, run \`agon config set commitCoAuthor ""\` (writes ~/.agon/config.json); per-project, safely edit ./.agon.json with your file tools (read the JSON if it exists, set/merge the key \`"commitCoAuthor": ""\`, write it back \u2014 never clobber other keys). Confirm once done.
|
|
9678
|
+
- If the user asks to CHANGE the identity instead, set the new value the same way (\`agon config set commitCoAuthor "<value>"\` machine-wide, or merge it into ./.agon.json per-project).`);
|
|
9679
|
+
}
|
|
9680
|
+
try {
|
|
9681
|
+
const projectMemory = buildProjectMemoryBlock(cesarCwd);
|
|
9682
|
+
if (projectMemory) systemParts.push(projectMemory);
|
|
9683
|
+
} catch (err) {
|
|
9684
|
+
console.warn(`[agon] project memory block skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
9685
|
+
}
|
|
9011
9686
|
if (ctx.cesarMemory) {
|
|
9012
9687
|
const memoryCtx = ctx.cesarMemory.toPromptContext();
|
|
9013
9688
|
if (memoryCtx) systemParts.push(memoryCtx);
|
|
@@ -9034,14 +9709,14 @@ ${fragments.join("\n")}`);
|
|
|
9034
9709
|
} else {
|
|
9035
9710
|
const stats = tracker.getStats();
|
|
9036
9711
|
let budgetWarning = "";
|
|
9037
|
-
if (stats.
|
|
9712
|
+
if (stats.meteredCostUsd > 0.5) {
|
|
9038
9713
|
budgetWarning = `
|
|
9039
9714
|
|
|
9040
|
-
URGENT: Planning has spent $${stats.
|
|
9041
|
-
} else if (stats.
|
|
9715
|
+
URGENT: Planning has spent $${stats.meteredCostUsd.toFixed(2)}. Stop investigating and decide NOW \u2014 call ProposePlan or ExitPlanMode.`;
|
|
9716
|
+
} else if (stats.meteredCostUsd > 0.25) {
|
|
9042
9717
|
budgetWarning = `
|
|
9043
9718
|
|
|
9044
|
-
WARNING: Planning has spent $${stats.
|
|
9719
|
+
WARNING: Planning has spent $${stats.meteredCostUsd.toFixed(2)}. Wrap up and decide \u2014 call ProposePlan or ExitPlanMode.`;
|
|
9045
9720
|
}
|
|
9046
9721
|
systemParts.push(`RULE 9 \u2014 PLAN MODE: You are in PLAN MODE because the user asked for planning, so proposing a plan is usually the right call here. But you are NEVER trapped and never forced: if you decide a plan is not the right approach \u2014 the task is simple enough to do live, or planning is blocking progress \u2014 call ExitPlanMode with a one-line reason and work live instead. You decide.
|
|
9047
9722
|
|
|
@@ -9147,6 +9822,16 @@ function capSnapshotMessageContent(content) {
|
|
|
9147
9822
|
return `${content.slice(0, CESAR_SNAPSHOT_MSG_CHAR_CAP)}
|
|
9148
9823
|
\u2026 [${content.length - CESAR_SNAPSHOT_MSG_CHAR_CAP} chars truncated for Cesar context]`;
|
|
9149
9824
|
}
|
|
9825
|
+
function renderToolPermissionCommand(tool, args) {
|
|
9826
|
+
const a = args && typeof args === "object" ? args : {};
|
|
9827
|
+
if (tool === "SaveMemory") {
|
|
9828
|
+
return `[${String(a.section ?? "")}] ${String(a.memory ?? "")}`.trim();
|
|
9829
|
+
}
|
|
9830
|
+
if (tool === "MultiEdit") {
|
|
9831
|
+
return JSON.stringify(args ?? {});
|
|
9832
|
+
}
|
|
9833
|
+
return String(a.command ?? a.file_path ?? JSON.stringify(args ?? {}));
|
|
9834
|
+
}
|
|
9150
9835
|
function buildCesarConversationSnapshot(session, chatSession) {
|
|
9151
9836
|
const directHistory = session?.getMessageHistory?.() ?? [];
|
|
9152
9837
|
if (directHistory.length > 0) {
|
|
@@ -9197,12 +9882,20 @@ function buildOnToolCall(ctx, toolRegistry, config) {
|
|
|
9197
9882
|
allowedCommands: config.allowedCommands ?? [],
|
|
9198
9883
|
toolPermissions: config.toolPermissions ?? {},
|
|
9199
9884
|
sessionAllowList: getSessionAllowList(),
|
|
9200
|
-
source: "orchestrator"
|
|
9885
|
+
source: "orchestrator",
|
|
9886
|
+
// CC-parity allow/deny rules reach the API tool-execution path here:
|
|
9887
|
+
// executeToolCall → handler.checkPermission consults ctx.permissionRules
|
|
9888
|
+
// (deny-first, before any mode-based auto-allow). Without this, a
|
|
9889
|
+
// Bash(rm:*) deny rule never fires for API engines under 'smart' mode.
|
|
9890
|
+
permissionRules: parsePermissionRuleSet(config.permissions),
|
|
9891
|
+
// CC-parity PreToolUse/PostToolUse hooks reach the API tool-execution
|
|
9892
|
+
// path here: executeToolCall fires them around handler.execute.
|
|
9893
|
+
toolHooks: parseToolHooks(config.hooks)
|
|
9201
9894
|
};
|
|
9202
9895
|
return async (name, args, callId) => {
|
|
9203
9896
|
const activePlan = ctx.activePlan;
|
|
9204
9897
|
if (activePlan && ["planning", "awaiting_approval"].includes(activePlan.state)) {
|
|
9205
|
-
const BLOCKED_IN_PLAN = ["Forge", "Pipeline", "Agent", "Goal", "Edit", "Write"];
|
|
9898
|
+
const BLOCKED_IN_PLAN = ["Forge", "Pipeline", "Agent", "Goal", "Edit", "Write", "MultiEdit"];
|
|
9206
9899
|
if (BLOCKED_IN_PLAN.includes(name)) {
|
|
9207
9900
|
return `[BLOCKED] Tool "${name}" is not available in plan mode. Use ProposePlan to propose your execution strategy.`;
|
|
9208
9901
|
}
|
|
@@ -9269,7 +9962,7 @@ ${cleaned}`;
|
|
|
9269
9962
|
}
|
|
9270
9963
|
}
|
|
9271
9964
|
if (name === "ExitPlanMode") {
|
|
9272
|
-
const { handleExitPlanMode } = await import("./plan-mode-
|
|
9965
|
+
const { handleExitPlanMode } = await import("./plan-mode-I3BZOBFB.js");
|
|
9273
9966
|
return "[DELEGATION_BREAK] " + handleExitPlanMode(String(args.reason ?? ""), ctx.cesar?.planDispatch ?? null, ctx);
|
|
9274
9967
|
}
|
|
9275
9968
|
if (name === "ProposePlan") {
|
|
@@ -9292,7 +9985,7 @@ ${cleaned}`;
|
|
|
9292
9985
|
}
|
|
9293
9986
|
}
|
|
9294
9987
|
}
|
|
9295
|
-
const { handleProposePlan } = await import("./plan-mode-
|
|
9988
|
+
const { handleProposePlan } = await import("./plan-mode-I3BZOBFB.js");
|
|
9296
9989
|
const dispatch = ctx.cesar.planDispatch;
|
|
9297
9990
|
if (!dispatch) {
|
|
9298
9991
|
return "[PLAN_ERROR] Internal plan display dispatch unavailable. Retry the plan request so Agon can render the approval panel.";
|
|
@@ -9358,8 +10051,15 @@ ${cleaned}`;
|
|
|
9358
10051
|
return new Promise((resolve4) => {
|
|
9359
10052
|
const d = ctx.cesar.lastDispatch;
|
|
9360
10053
|
if (d) {
|
|
9361
|
-
const cmd =
|
|
9362
|
-
|
|
10054
|
+
const cmd = renderToolPermissionCommand(tool, args);
|
|
10055
|
+
let permDiffPreview = void 0;
|
|
10056
|
+
if (approvalToolIsFileMutating(tool)) {
|
|
10057
|
+
try {
|
|
10058
|
+
permDiffPreview = buildApprovalDiffPreview(String(tool), args);
|
|
10059
|
+
} catch {
|
|
10060
|
+
}
|
|
10061
|
+
}
|
|
10062
|
+
d({ type: "permission-ask", tool, command: cmd, reason: message, diffPreview: permDiffPreview && Array.isArray(permDiffPreview.files) ? permDiffPreview : void 0, fallbackNote: permDiffPreview && typeof permDiffPreview.fallback === "string" ? permDiffPreview.fallback : void 0, resolve: resolve4 });
|
|
9363
10063
|
} else {
|
|
9364
10064
|
resolve4(true);
|
|
9365
10065
|
}
|
|
@@ -9367,11 +10067,15 @@ ${cleaned}`;
|
|
|
9367
10067
|
}
|
|
9368
10068
|
);
|
|
9369
10069
|
let output = result.result.ok ? result.result.content : result.result.error ?? "Tool execution failed";
|
|
9370
|
-
|
|
10070
|
+
const isPermissionDenial = !result.result.ok && typeof result.result.error === "string" && (result.result.error.includes(PERMISSION_DENIED_MESSAGE) || result.result.error.includes("User denied permission") || result.result.error.startsWith("DENIED:"));
|
|
10071
|
+
if (!result.result.ok && !isPermissionDenial) {
|
|
9371
10072
|
const diag = buildToolErrorDiagnostic(name, args, result.result.error);
|
|
9372
10073
|
const retryKey = `${name}:${JSON.stringify(args)}`;
|
|
9373
10074
|
const used = nativeToolErrorRetries.get(retryKey) ?? 0;
|
|
9374
|
-
|
|
10075
|
+
const isPermissionDenial2 = typeof result.result.error === "string" && result.result.error.includes(PERMISSION_DENIED_MESSAGE);
|
|
10076
|
+
if (isPermissionDenial2) {
|
|
10077
|
+
output = result.result.error;
|
|
10078
|
+
} else if (used <= 0) {
|
|
9375
10079
|
nativeToolErrorRetries.set(retryKey, 1);
|
|
9376
10080
|
output = `[RETRYABLE_TOOL_ERROR] ${diag}
|
|
9377
10081
|
Retry this ${name} call ONCE with corrected input that matches the tool's schema. Do not narrate before retrying.`;
|
|
@@ -9384,7 +10088,7 @@ Repair retry already used for this exact ${name} input in this turn. Stop retryi
|
|
|
9384
10088
|
}
|
|
9385
10089
|
if (CACHEABLE_TOOLS.has(name)) {
|
|
9386
10090
|
toolResultCache.set(cacheKey, output);
|
|
9387
|
-
} else if (["Edit", "Write", "Bash"].includes(name)) {
|
|
10091
|
+
} else if (["Edit", "Write", "MultiEdit", "Bash"].includes(name)) {
|
|
9388
10092
|
toolResultCache.clear();
|
|
9389
10093
|
ctx.cesar.blockedOnConfidence = null;
|
|
9390
10094
|
}
|
|
@@ -9408,9 +10112,12 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9408
10112
|
const perms = cfg.toolPermissions ?? {};
|
|
9409
10113
|
const allowed = cfg.allowedCommands ?? [];
|
|
9410
10114
|
const mode = cfg.permissionMode ?? "ask";
|
|
9411
|
-
const toolMap = { shell: "Bash", bash: "Bash", edit: "Edit", write: "Write", read: "Read", grep: "Grep", glob: "Glob" };
|
|
10115
|
+
const toolMap = { shell: "Bash", bash: "Bash", edit: "Edit", write: "Write", multiedit: "MultiEdit", read: "Read", grep: "Grep", glob: "Glob" };
|
|
9412
10116
|
const agonTool = toolMap[tool.toLowerCase()] ?? tool;
|
|
9413
10117
|
const perm = perms[agonTool];
|
|
10118
|
+
const ruleSet = parsePermissionRuleSet(cfg.permissions);
|
|
10119
|
+
const ruleArg = agonTool === "Bash" ? command : String(approvalArgsFromCommand(agonTool, command)?.file_path ?? "");
|
|
10120
|
+
const ruleDecision = evaluateToolRules(agonTool, ruleArg, resolveWorkingDir(), ruleSet);
|
|
9414
10121
|
const turnId = ctx.cesar?.turnId;
|
|
9415
10122
|
const cwd = resolveWorkingDir();
|
|
9416
10123
|
const logApproval = (decision, source, reason, args) => {
|
|
@@ -9445,7 +10152,7 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9445
10152
|
}
|
|
9446
10153
|
};
|
|
9447
10154
|
if (ctx.explorationMode) {
|
|
9448
|
-
const WRITE_TOOLS = ["Edit", "Write", "Bash"];
|
|
10155
|
+
const WRITE_TOOLS = ["Edit", "Write", "MultiEdit", "Bash"];
|
|
9449
10156
|
if (WRITE_TOOLS.includes(agonTool)) {
|
|
9450
10157
|
logApproval("blocked", "policy.exploration", "exploration mode is read-only");
|
|
9451
10158
|
return "BLOCKED: Exploration mode is read-only. Use Read, Grep, Glob tools only. Do not narrate around this. Either keep investigating, or wait for the user to disable exploration mode before retrying the same tool.";
|
|
@@ -9461,7 +10168,7 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9461
10168
|
logApproval("blocked", "policy.plan-mode", "plan mode blocks mutating Bash");
|
|
9462
10169
|
return "BLOCKED: Plan mode \u2014 mutating Bash is not allowed before approval. Use Read/Grep/Glob or read-only Bash for investigation, call ProposePlan, then wait for the user to type go/yes before retrying this command.";
|
|
9463
10170
|
}
|
|
9464
|
-
const WRITE_TOOLS = ["Edit", "Write"];
|
|
10171
|
+
const WRITE_TOOLS = ["Edit", "Write", "MultiEdit"];
|
|
9465
10172
|
if (WRITE_TOOLS.includes(agonTool)) {
|
|
9466
10173
|
logApproval("blocked", "policy.plan-mode", "plan mode blocks file writes");
|
|
9467
10174
|
return "BLOCKED: Plan mode \u2014 no code changes allowed. Call ProposePlan with the execution plan now, then wait for approval before retrying the same tool. Do not narrate instead of acting.";
|
|
@@ -9487,7 +10194,7 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9487
10194
|
}
|
|
9488
10195
|
}
|
|
9489
10196
|
if (!ctx.cesar.confidenceSatisfied) {
|
|
9490
|
-
const WRITE_TOOLS = ["Edit", "Write"];
|
|
10197
|
+
const WRITE_TOOLS = ["Edit", "Write", "MultiEdit"];
|
|
9491
10198
|
if (WRITE_TOOLS.includes(agonTool)) {
|
|
9492
10199
|
const blocks = (ctx.cesar.confidenceBlockCount ?? 0) + 1;
|
|
9493
10200
|
ctx.cesar.confidenceBlockCount = blocks;
|
|
@@ -9500,11 +10207,19 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9500
10207
|
ctx.cesar.blockedOnConfidence = null;
|
|
9501
10208
|
}
|
|
9502
10209
|
}
|
|
10210
|
+
if (ruleDecision === "deny") {
|
|
10211
|
+
logApproval("denied", "settings.permissions", `${agonTool} denied by permissions rule`);
|
|
10212
|
+
return `DENIED: ${agonTool}${agonTool === "Bash" ? ` (${command})` : ""} is blocked by a deny rule in .agon.json permissions. Do not retry this \u2014 choose a different approach or ask the user to amend the rule.`;
|
|
10213
|
+
}
|
|
9503
10214
|
if (perm === "deny" || mode === "deny-all") {
|
|
9504
10215
|
logApproval("denied", perm === "deny" ? "settings.toolPermissions" : "settings.permissionMode", perm === "deny" ? `${agonTool} denied in settings` : "permissionMode=deny-all");
|
|
9505
10216
|
return false;
|
|
9506
10217
|
}
|
|
9507
|
-
if (
|
|
10218
|
+
if (ruleDecision === "allow") {
|
|
10219
|
+
logApproval("approved", "settings.permissions", `${agonTool} allowed by permissions rule`);
|
|
10220
|
+
return true;
|
|
10221
|
+
}
|
|
10222
|
+
if (agonTool === "Edit" || agonTool === "Write" || agonTool === "MultiEdit") {
|
|
9508
10223
|
const approvalCwd = resolveWorkingDir();
|
|
9509
10224
|
const approvalCache = getProjectFileStateCache(approvalCwd);
|
|
9510
10225
|
const approvalArgs = approvalArgsFromCommand(agonTool, command);
|
|
@@ -9552,7 +10267,14 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9552
10267
|
const dispatch = ctx.cesar.lastDispatch;
|
|
9553
10268
|
if (dispatch) {
|
|
9554
10269
|
logApproval("prompted", "user-prompt", `Cesar (${engineId}) wants to execute`);
|
|
9555
|
-
|
|
10270
|
+
let permDiffPreview = void 0;
|
|
10271
|
+
if (approvalToolIsFileMutating(agonTool)) {
|
|
10272
|
+
try {
|
|
10273
|
+
permDiffPreview = buildApprovalDiffPreview(agonTool, approvalArgsFromCommand(agonTool, command));
|
|
10274
|
+
} catch {
|
|
10275
|
+
}
|
|
10276
|
+
}
|
|
10277
|
+
dispatch({ type: "permission-ask", tool: agonTool, command, reason: `Cesar (${engineId}) wants to execute`, diffPreview: permDiffPreview && Array.isArray(permDiffPreview.files) ? permDiffPreview : void 0, fallbackNote: permDiffPreview && typeof permDiffPreview.fallback === "string" ? permDiffPreview.fallback : void 0, resolve: (approved) => {
|
|
9556
10278
|
const wasApproved = typeof approved === "string" ? approved === "y" || approved === "a" : !!approved;
|
|
9557
10279
|
logApproval(wasApproved ? "approved" : "denied", "user-prompt", wasApproved ? "user approved" : "user denied");
|
|
9558
10280
|
resolve4(wasApproved);
|
|
@@ -9596,7 +10318,7 @@ function loadCesarMcpServers(config, cwd) {
|
|
|
9596
10318
|
const resolvedPath = isAbsolute2(rawPath) ? rawPath : resolve3(cwd, rawPath);
|
|
9597
10319
|
let parsed;
|
|
9598
10320
|
try {
|
|
9599
|
-
parsed = JSON.parse(
|
|
10321
|
+
parsed = JSON.parse(readFileSync13(resolvedPath, "utf-8"));
|
|
9600
10322
|
} catch (err) {
|
|
9601
10323
|
throw new Error(`Failed to load Cesar MCP config at ${resolvedPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9602
10324
|
}
|
|
@@ -9620,7 +10342,7 @@ function mcpConfigFingerprint(config) {
|
|
|
9620
10342
|
if (enabled && configPath) {
|
|
9621
10343
|
try {
|
|
9622
10344
|
const resolvedPath = isAbsolute2(configPath) ? configPath : resolve3(resolveWorkingDir(), configPath);
|
|
9623
|
-
mtime = String(
|
|
10345
|
+
mtime = String(statSync5(resolvedPath).mtimeMs);
|
|
9624
10346
|
} catch {
|
|
9625
10347
|
}
|
|
9626
10348
|
}
|
|
@@ -9631,17 +10353,17 @@ function resolveAgonMcpServerPath(fromUrl) {
|
|
|
9631
10353
|
const raw = fromUrl ?? import.meta.url;
|
|
9632
10354
|
const url = raw.startsWith("file:") ? raw : pathToFileURL(raw).href;
|
|
9633
10355
|
const bundledSibling = join12(dirname5(fileURLToPath(url)), "mcp", "index.js");
|
|
9634
|
-
if (
|
|
10356
|
+
if (existsSync13(bundledSibling)) return bundledSibling;
|
|
9635
10357
|
try {
|
|
9636
10358
|
const req = createRequire(url);
|
|
9637
10359
|
const resolved = req.resolve("@kernlang/agon-mcp");
|
|
9638
|
-
if (
|
|
10360
|
+
if (existsSync13(resolved)) return resolved;
|
|
9639
10361
|
} catch {
|
|
9640
10362
|
}
|
|
9641
10363
|
let dir = dirname5(fileURLToPath(url));
|
|
9642
10364
|
for (let i = 0; i < 12; i++) {
|
|
9643
10365
|
const cand = join12(dir, "packages", "mcp", "dist", "index.js");
|
|
9644
|
-
if (
|
|
10366
|
+
if (existsSync13(cand)) return cand;
|
|
9645
10367
|
const parent = dirname5(dir);
|
|
9646
10368
|
if (parent === dir) break;
|
|
9647
10369
|
dir = parent;
|
|
@@ -9760,7 +10482,7 @@ async function ensureCesarSession(ctx) {
|
|
|
9760
10482
|
mkdirSync11(signalDir, { recursive: true });
|
|
9761
10483
|
const sessionSignalId = `cesar-${Date.now()}`;
|
|
9762
10484
|
const mcpServerPath = resolveAgonMcpServerPath();
|
|
9763
|
-
if (!
|
|
10485
|
+
if (!existsSync13(mcpServerPath)) {
|
|
9764
10486
|
console.error(`[agon] cesar: agon-orchestration MCP server not found at ${mcpServerPath} \u2014 orchestration tools (Forge/Tribunal/AgonBash/DeliverAnswer) will be UNAVAILABLE and Cesar will fall back to slow scraping.`);
|
|
9765
10487
|
}
|
|
9766
10488
|
const mcpEnv = { AGON_SIGNAL_DIR: signalDir, AGON_SESSION_ID: sessionSignalId };
|
|
@@ -9811,9 +10533,177 @@ async function ensureCesarSession(ctx) {
|
|
|
9811
10533
|
await session.start();
|
|
9812
10534
|
ctx.setCesarSession(session);
|
|
9813
10535
|
ctx.cesar.mcpFingerprint = currentMcpFp;
|
|
10536
|
+
ctx.cesar.budgetWarned = false;
|
|
9814
10537
|
return session;
|
|
9815
10538
|
}
|
|
9816
10539
|
|
|
10540
|
+
// src/generated/cesar/context-budget.ts
|
|
10541
|
+
var TOOL_PROMPT_OVERHEAD_TOKENS = 2500;
|
|
10542
|
+
function estimateBrainTokens(ctx, session, backend, budget, pendingInput) {
|
|
10543
|
+
if (backend === "api" && session && typeof session.getContextUsage === "function") {
|
|
10544
|
+
try {
|
|
10545
|
+
const live = session.getContextUsage();
|
|
10546
|
+
if (live && Number.isFinite(live.tokens) && live.tokens > 0 && live.source !== "estimate") {
|
|
10547
|
+
const pendingTokens = pendingInput ? estimateSessionTokens({ messageHistory: [{ role: "user", content: pendingInput }] }, budget) : 0;
|
|
10548
|
+
return live.tokens + pendingTokens;
|
|
10549
|
+
}
|
|
10550
|
+
} catch {
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
10553
|
+
if (backend === "api" && session && typeof session.getMessageHistory === "function") {
|
|
10554
|
+
try {
|
|
10555
|
+
const history = session.getMessageHistory() ?? [];
|
|
10556
|
+
if (Array.isArray(history) && history.length > 0) {
|
|
10557
|
+
const withPending = pendingInput ? [...history, { role: "user", content: pendingInput }] : history;
|
|
10558
|
+
return estimateSessionTokens({ messageHistory: withPending }, budget);
|
|
10559
|
+
}
|
|
10560
|
+
} catch {
|
|
10561
|
+
}
|
|
10562
|
+
}
|
|
10563
|
+
let systemPromptChars = 0;
|
|
10564
|
+
try {
|
|
10565
|
+
systemPromptChars = (buildCesarSystemPrompt(ctx) ?? "").length;
|
|
10566
|
+
} catch {
|
|
10567
|
+
}
|
|
10568
|
+
const chat = ctx.chatSession;
|
|
10569
|
+
let continuityChars = 0;
|
|
10570
|
+
let userTurnsChars = 0;
|
|
10571
|
+
let toolResultsChars = 0;
|
|
10572
|
+
if (chat) {
|
|
10573
|
+
continuityChars += (chat.summary ?? "").length;
|
|
10574
|
+
const msgs = Array.isArray(chat.messages) ? chat.messages : [];
|
|
10575
|
+
for (const m of msgs) {
|
|
10576
|
+
const len = typeof m?.content === "string" ? m.content.length : 0;
|
|
10577
|
+
if (m?.role === "user") userTurnsChars += len;
|
|
10578
|
+
else toolResultsChars += len;
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
try {
|
|
10582
|
+
const digest = ctx.cesarMemory?.toPromptContext?.();
|
|
10583
|
+
if (typeof digest === "string") continuityChars += digest.length;
|
|
10584
|
+
} catch {
|
|
10585
|
+
}
|
|
10586
|
+
const base = estimateSessionTokens({
|
|
10587
|
+
pty: {
|
|
10588
|
+
systemPromptChars,
|
|
10589
|
+
userTurnsChars,
|
|
10590
|
+
toolResultsChars,
|
|
10591
|
+
continuityChars,
|
|
10592
|
+
pendingInputChars: (pendingInput ?? "").length
|
|
10593
|
+
}
|
|
10594
|
+
}, budget);
|
|
10595
|
+
return base + TOOL_PROMPT_OVERHEAD_TOKENS;
|
|
10596
|
+
}
|
|
10597
|
+
async function enforceContextBudget(ctx, session, engine, backend, dispatch, pendingInput) {
|
|
10598
|
+
const budget = engine?.sessionBudget;
|
|
10599
|
+
if (!budget || typeof budget.contextWindow !== "number" || budget.contextWindow <= 0) {
|
|
10600
|
+
return { proceed: true, compacted: false };
|
|
10601
|
+
}
|
|
10602
|
+
let estimated = 0;
|
|
10603
|
+
try {
|
|
10604
|
+
estimated = estimateBrainTokens(ctx, session, backend, budget, pendingInput);
|
|
10605
|
+
} catch {
|
|
10606
|
+
return { proceed: true, compacted: false };
|
|
10607
|
+
}
|
|
10608
|
+
const engineId = engine && typeof engine.id === "string" ? engine.id : "this engine";
|
|
10609
|
+
const check = checkSessionBudget(estimated, budget);
|
|
10610
|
+
const pct = budgetRatioPct(check.ratio);
|
|
10611
|
+
const autoMode = ctx.autoModeQueued === true || ctx.cesar?.autoModeQueued === true;
|
|
10612
|
+
const doCompactReboot = () => {
|
|
10613
|
+
let folded = false;
|
|
10614
|
+
try {
|
|
10615
|
+
if (ctx.chatSession) folded = updateChatSummary(ctx.chatSession);
|
|
10616
|
+
} catch {
|
|
10617
|
+
}
|
|
10618
|
+
try {
|
|
10619
|
+
session?.close?.();
|
|
10620
|
+
} catch {
|
|
10621
|
+
}
|
|
10622
|
+
try {
|
|
10623
|
+
ctx.setCesarSession(null);
|
|
10624
|
+
} catch {
|
|
10625
|
+
}
|
|
10626
|
+
try {
|
|
10627
|
+
ctx.cesarMemory?.clearSession?.();
|
|
10628
|
+
} catch {
|
|
10629
|
+
}
|
|
10630
|
+
return folded;
|
|
10631
|
+
};
|
|
10632
|
+
const live = session && typeof session.getContextUsage === "function" ? session.getContextUsage() : null;
|
|
10633
|
+
const canCompactInPlace = !!(backend === "api" && session && typeof session.compact === "function" && live && Number(live.limit) > 0 && live.source !== "estimate");
|
|
10634
|
+
if (canCompactInPlace) {
|
|
10635
|
+
const limit = Number(live.limit);
|
|
10636
|
+
const warnLimit = Math.floor(limit * 0.7);
|
|
10637
|
+
const softLimit = Math.max(warnLimit + 1, Number.isFinite(Number(live.softLimit)) && Number(live.softLimit) > 0 ? Number(live.softLimit) : Math.floor(limit * 0.85));
|
|
10638
|
+
const hardLimit = Math.floor(limit * 0.95);
|
|
10639
|
+
const pendingTokens = pendingInput ? estimateSessionTokens({ messageHistory: [{ role: "user", content: pendingInput }] }, budget) : 0;
|
|
10640
|
+
const projected = Number(live.tokens) + pendingTokens;
|
|
10641
|
+
const pctOf = (n) => Math.max(0, Math.round(n / limit * 100));
|
|
10642
|
+
if (projected < warnLimit) return { proceed: true, compacted: false };
|
|
10643
|
+
if (projected < softLimit) {
|
|
10644
|
+
if (!autoMode && ctx.cesar && !ctx.cesar.budgetWarned) {
|
|
10645
|
+
ctx.cesar.budgetWarned = true;
|
|
10646
|
+
dispatch({ type: "info", message: `Context at ${pctOf(projected)}% of ${engineId}'s window \u2014 Cesar will auto-compact in place around ${pctOf(softLimit)}%. Run /compact to fold older turns now.` });
|
|
10647
|
+
}
|
|
10648
|
+
return { proceed: true, compacted: false };
|
|
10649
|
+
}
|
|
10650
|
+
let res = null;
|
|
10651
|
+
try {
|
|
10652
|
+
res = await session.compact();
|
|
10653
|
+
} catch {
|
|
10654
|
+
res = null;
|
|
10655
|
+
}
|
|
10656
|
+
if (res && res.ok) {
|
|
10657
|
+
dispatch({ type: "info", message: `Compacted context in place: ${pctOf(res.beforeTokens)}% \u2192 ${pctOf(res.afterTokens)}% of ${engineId}'s window (${res.method === "llm" ? "older turns summarized by the engine" : "older turns folded into a structured summary"}). The session continues.` });
|
|
10658
|
+
dispatch({ type: "context-usage", pct: pctOf(res.afterTokens), used: res.afterTokens, limit, compacted: 1, cached: 0, source: "projected" });
|
|
10659
|
+
if (res.afterTokens + pendingTokens < hardLimit) {
|
|
10660
|
+
return { proceed: true, compacted: true };
|
|
10661
|
+
}
|
|
10662
|
+
}
|
|
10663
|
+
const foldedIp = doCompactReboot();
|
|
10664
|
+
let postEstimateIp = projected;
|
|
10665
|
+
try {
|
|
10666
|
+
postEstimateIp = estimateBrainTokens(ctx, null, backend, budget, pendingInput);
|
|
10667
|
+
} catch {
|
|
10668
|
+
}
|
|
10669
|
+
const postCheckIp = checkSessionBudget(postEstimateIp, budget);
|
|
10670
|
+
if (postCheckIp.level !== "hard-stop") {
|
|
10671
|
+
dispatch({ type: "info", message: `In-place compaction couldn't free enough at ${pctOf(projected)}% \u2014 auto-compacted Cesar context${foldedIp ? " (folded older turns into a summary)" : ""} and rebooted the brain with fresh context. The transcript is preserved.` });
|
|
10672
|
+
return { proceed: true, compacted: true };
|
|
10673
|
+
}
|
|
10674
|
+
dispatch({ type: "error", message: `Cesar context is still at ${budgetRatioPct(postCheckIp.ratio)}% of ${engineId}'s window after compaction \u2014 too full to safely send this turn. Trim the message, or run /clear to reset the session, then resend.` });
|
|
10675
|
+
return { proceed: false, compacted: true };
|
|
10676
|
+
}
|
|
10677
|
+
if (check.level === "ok") {
|
|
10678
|
+
return { proceed: true, compacted: false };
|
|
10679
|
+
}
|
|
10680
|
+
if (check.level === "warn") {
|
|
10681
|
+
if (!autoMode && ctx.cesar && !ctx.cesar.budgetWarned) {
|
|
10682
|
+
ctx.cesar.budgetWarned = true;
|
|
10683
|
+
dispatch({ type: "info", message: `Context at ${pct}% of ${engineId}'s window \u2014 Cesar will auto-compact soon. Run /compact now to fold older turns, or /clear to reset.` });
|
|
10684
|
+
}
|
|
10685
|
+
return { proceed: true, compacted: false };
|
|
10686
|
+
}
|
|
10687
|
+
if (check.level === "compact") {
|
|
10688
|
+
const folded = doCompactReboot();
|
|
10689
|
+
dispatch({ type: "info", message: `Context reached ${pct}% \u2014 auto-compacted Cesar context${folded ? " (folded older turns into a summary)" : ""} and rebooted the brain with fresh context. The transcript is preserved.` });
|
|
10690
|
+
return { proceed: true, compacted: true };
|
|
10691
|
+
}
|
|
10692
|
+
const foldedHs = doCompactReboot();
|
|
10693
|
+
let postEstimate = estimated;
|
|
10694
|
+
try {
|
|
10695
|
+
postEstimate = estimateBrainTokens(ctx, null, backend, budget, pendingInput);
|
|
10696
|
+
} catch {
|
|
10697
|
+
}
|
|
10698
|
+
const postCheck = checkSessionBudget(postEstimate, budget);
|
|
10699
|
+
if (postCheck.level !== "hard-stop") {
|
|
10700
|
+
dispatch({ type: "info", message: `Context hit ${pct}% \u2014 auto-compacted Cesar context${foldedHs ? " (folded older turns into a summary)" : ""} and rebooted the brain (now ~${budgetRatioPct(postCheck.ratio)}%). The transcript is preserved.` });
|
|
10701
|
+
return { proceed: true, compacted: true };
|
|
10702
|
+
}
|
|
10703
|
+
dispatch({ type: "error", message: `Cesar context is still at ${budgetRatioPct(postCheck.ratio)}% of ${engineId}'s window after auto-compaction \u2014 too full to safely send this turn. Trim the message, or run /clear to reset the session, then resend.` });
|
|
10704
|
+
return { proceed: false, compacted: true };
|
|
10705
|
+
}
|
|
10706
|
+
|
|
9817
10707
|
// src/generated/cesar/escalation.ts
|
|
9818
10708
|
import { join as join13 } from "path";
|
|
9819
10709
|
import { mkdirSync as mkdirSync12 } from "fs";
|
|
@@ -9883,18 +10773,94 @@ async function promptDelegation(action, dispatch, hardened, tribunalMode, team)
|
|
|
9883
10773
|
return { approved: answer === "y" };
|
|
9884
10774
|
}
|
|
9885
10775
|
|
|
10776
|
+
// src/generated/cesar/steering.ts
|
|
10777
|
+
var _queue = [];
|
|
10778
|
+
var _activeTurnId = { value: null };
|
|
10779
|
+
var _listeners = [];
|
|
10780
|
+
function _notify() {
|
|
10781
|
+
const active = _activeTurnId.value;
|
|
10782
|
+
let n = 0;
|
|
10783
|
+
if (active) {
|
|
10784
|
+
for (const entry of _queue) if (entry.turnId === active) n++;
|
|
10785
|
+
}
|
|
10786
|
+
for (const cb of _listeners) {
|
|
10787
|
+
try {
|
|
10788
|
+
cb(n);
|
|
10789
|
+
} catch {
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
}
|
|
10793
|
+
function onSteeringChange(cb) {
|
|
10794
|
+
_listeners.push(cb);
|
|
10795
|
+
return () => {
|
|
10796
|
+
const i = _listeners.indexOf(cb);
|
|
10797
|
+
if (i >= 0) _listeners.splice(i, 1);
|
|
10798
|
+
};
|
|
10799
|
+
}
|
|
10800
|
+
function markSteeringTurn(turnId) {
|
|
10801
|
+
_activeTurnId.value = turnId;
|
|
10802
|
+
_queue.length = 0;
|
|
10803
|
+
_notify();
|
|
10804
|
+
}
|
|
10805
|
+
function pushSteering(input, images) {
|
|
10806
|
+
const active = _activeTurnId.value;
|
|
10807
|
+
if (!active) {
|
|
10808
|
+
return false;
|
|
10809
|
+
}
|
|
10810
|
+
_queue.push({ turnId: active, input, images });
|
|
10811
|
+
_notify();
|
|
10812
|
+
return true;
|
|
10813
|
+
}
|
|
10814
|
+
function drainSteering(turnId) {
|
|
10815
|
+
const mine = [];
|
|
10816
|
+
const rest = [];
|
|
10817
|
+
for (const entry of _queue) {
|
|
10818
|
+
if (entry.turnId === turnId) mine.push({ input: entry.input, images: entry.images });
|
|
10819
|
+
else rest.push(entry);
|
|
10820
|
+
}
|
|
10821
|
+
_queue.length = 0;
|
|
10822
|
+
for (const entry of rest) _queue.push(entry);
|
|
10823
|
+
_notify();
|
|
10824
|
+
return mine;
|
|
10825
|
+
}
|
|
10826
|
+
function peekSteeringCount() {
|
|
10827
|
+
const active = _activeTurnId.value;
|
|
10828
|
+
if (!active) return 0;
|
|
10829
|
+
let n = 0;
|
|
10830
|
+
for (const entry of _queue) if (entry.turnId === active) n++;
|
|
10831
|
+
return n;
|
|
10832
|
+
}
|
|
10833
|
+
function releaseSteeringTurn(turnId) {
|
|
10834
|
+
if (_activeTurnId.value === turnId) {
|
|
10835
|
+
_activeTurnId.value = null;
|
|
10836
|
+
_notify();
|
|
10837
|
+
}
|
|
10838
|
+
}
|
|
10839
|
+
function drainLeftoverSteering() {
|
|
10840
|
+
const all = _queue.map((e) => ({ input: e.input, images: e.images }));
|
|
10841
|
+
_queue.length = 0;
|
|
10842
|
+
_notify();
|
|
10843
|
+
return all;
|
|
10844
|
+
}
|
|
10845
|
+
function clearSteering() {
|
|
10846
|
+
_queue.length = 0;
|
|
10847
|
+
_activeTurnId.value = null;
|
|
10848
|
+
_notify();
|
|
10849
|
+
}
|
|
10850
|
+
|
|
9886
10851
|
// src/generated/cesar/brain.ts
|
|
9887
10852
|
async function commitTurnAndDelegate(pendingDel, input, response, cesarEngineId, streaming, dispatch, ctx, telemetry) {
|
|
9888
10853
|
if (streaming) {
|
|
9889
10854
|
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
9890
10855
|
}
|
|
9891
10856
|
if (!streaming) {
|
|
10857
|
+
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
9892
10858
|
dispatch({ type: "spinner-stop" });
|
|
9893
10859
|
}
|
|
9894
10860
|
await yieldToInk();
|
|
9895
10861
|
appendMessage(ctx.chatSession, { role: "user", content: input, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9896
10862
|
appendMessage(ctx.chatSession, { role: "engine", engineId: cesarEngineId, content: response, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9897
|
-
|
|
10863
|
+
recordCesarTurn(ctx, cesarEngineId, input, response);
|
|
9898
10864
|
const delResult = await promptDelegation(pendingDel.action, dispatch, pendingDel.hardened, pendingDel.tribunalMode, pendingDel.team);
|
|
9899
10865
|
const happened = buildWhatHappenedSummary(telemetry ?? {});
|
|
9900
10866
|
if (happened) {
|
|
@@ -9916,12 +10882,13 @@ async function commitTurnAndSuggest(suggestion, input, response, cesarEngineId,
|
|
|
9916
10882
|
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
9917
10883
|
}
|
|
9918
10884
|
if (!streaming) {
|
|
10885
|
+
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
9919
10886
|
dispatch({ type: "spinner-stop" });
|
|
9920
10887
|
}
|
|
9921
10888
|
await yieldToInk();
|
|
9922
10889
|
appendMessage(ctx.chatSession, { role: "user", content: input, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9923
10890
|
appendMessage(ctx.chatSession, { role: "engine", engineId: cesarEngineId, content: response, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9924
|
-
|
|
10891
|
+
recordCesarTurn(ctx, cesarEngineId, input, response);
|
|
9925
10892
|
if (suggestion.rest) {
|
|
9926
10893
|
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: suggestion.rest });
|
|
9927
10894
|
}
|
|
@@ -9958,6 +10925,22 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
9958
10925
|
}
|
|
9959
10926
|
const _toolsUsed = [];
|
|
9960
10927
|
const _toolUseKeys = /* @__PURE__ */ new Set();
|
|
10928
|
+
const _gate = discoverGate(_turnCwd);
|
|
10929
|
+
if (ctx.cesar) ctx.cesar.discoveredGate = _gate;
|
|
10930
|
+
if (ctx.cesar?.gateNudgedClaim && isGateSkipSignal(input)) ctx.cesar.gateWaived = true;
|
|
10931
|
+
if (ctx.cesar) ctx.cesar.gateNudgedClaim = void 0;
|
|
10932
|
+
let _ranGate = false;
|
|
10933
|
+
const _noteBashForGate = (toolName, rawInput) => {
|
|
10934
|
+
if (_ranGate || !_gate.matchers.length) return;
|
|
10935
|
+
if (!isBashToolName(toolName)) return;
|
|
10936
|
+
let cmd = String(rawInput ?? "");
|
|
10937
|
+
try {
|
|
10938
|
+
const parsed = JSON.parse(cmd);
|
|
10939
|
+
if (parsed && typeof parsed.command === "string") cmd = parsed.command;
|
|
10940
|
+
} catch {
|
|
10941
|
+
}
|
|
10942
|
+
if (bashRanGate(cmd, _gate.matchers)) _ranGate = true;
|
|
10943
|
+
};
|
|
9961
10944
|
let _toolEventCount = 0;
|
|
9962
10945
|
let _readToolEventCount = 0;
|
|
9963
10946
|
let _toolCallTurns = 0;
|
|
@@ -9967,6 +10950,7 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
9967
10950
|
let _narratedToolStalls = 0;
|
|
9968
10951
|
let _autoToolExecutions = 0;
|
|
9969
10952
|
let _confidenceToolUsed = false;
|
|
10953
|
+
let _liveTodosEmitted = false;
|
|
9970
10954
|
let _actualCesarEngineId = "";
|
|
9971
10955
|
let _actualCesarBackend = "unknown";
|
|
9972
10956
|
let _actualHasNativeTools = false;
|
|
@@ -9979,6 +10963,7 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
9979
10963
|
};
|
|
9980
10964
|
const recordToolUse = (name, source, input2, status) => {
|
|
9981
10965
|
const toolName = String(name || "tool");
|
|
10966
|
+
_noteBashForGate(toolName, input2);
|
|
9982
10967
|
const normalizedSource = source === "eager" ? "xml" : source === "auto" ? "native" : source;
|
|
9983
10968
|
const key = `${normalizedSource}:${toolName}:${String(input2 ?? "").slice(0, 500)}`;
|
|
9984
10969
|
_toolEventCount++;
|
|
@@ -10003,6 +10988,45 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10003
10988
|
input: input2
|
|
10004
10989
|
});
|
|
10005
10990
|
};
|
|
10991
|
+
const _toolStartMs = /* @__PURE__ */ new Map();
|
|
10992
|
+
const dispatchToolCall = (evt, opts) => {
|
|
10993
|
+
const key = String(opts?.toolCallId || evt.tool || "tool");
|
|
10994
|
+
let durationMs = evt.durationMs;
|
|
10995
|
+
if (evt.status === "running") {
|
|
10996
|
+
if (!opts?.eager && !opts?.standalone) _toolStartMs.set(key, Date.now());
|
|
10997
|
+
} else if (opts?.standalone) {
|
|
10998
|
+
} else {
|
|
10999
|
+
const startedAt = _toolStartMs.get(key);
|
|
11000
|
+
if (startedAt !== void 0) {
|
|
11001
|
+
if (durationMs === void 0) durationMs = Date.now() - startedAt;
|
|
11002
|
+
_toolStartMs.delete(key);
|
|
11003
|
+
}
|
|
11004
|
+
}
|
|
11005
|
+
dispatch({ ...evt, ...durationMs !== void 0 ? { durationMs } : {} });
|
|
11006
|
+
};
|
|
11007
|
+
let _preambleEmitted = false;
|
|
11008
|
+
const emitPreamble = (text) => {
|
|
11009
|
+
const parsed = parsePreamble(text);
|
|
11010
|
+
if (!parsed.found || !parsed.intent) return text;
|
|
11011
|
+
if (!_preambleEmitted) {
|
|
11012
|
+
_preambleEmitted = true;
|
|
11013
|
+
dispatch({ type: "cesar-preamble", engineId: _actualCesarEngineId || void 0, intent: parsed.intent });
|
|
11014
|
+
}
|
|
11015
|
+
return parsed.rest;
|
|
11016
|
+
};
|
|
11017
|
+
const emitLiveTodos = (text) => {
|
|
11018
|
+
const planActive = !!(ctx.activePlan && ["planning", "awaiting_approval", "running", "paused"].includes(ctx.activePlan.state));
|
|
11019
|
+
if (planActive) return text;
|
|
11020
|
+
const parsed = parseLiveTodos(text);
|
|
11021
|
+
if (!parsed.found) return text;
|
|
11022
|
+
if (parsed.todos.length > 0) {
|
|
11023
|
+
_liveTodosEmitted = true;
|
|
11024
|
+
dispatch({ type: "todos-set", todos: parsed.todos });
|
|
11025
|
+
} else {
|
|
11026
|
+
dispatch({ type: "todos-clear", scope: "live" });
|
|
11027
|
+
}
|
|
11028
|
+
return parsed.rest;
|
|
11029
|
+
};
|
|
10006
11030
|
const normalizeConfidenceReasoning = (value) => {
|
|
10007
11031
|
return String(value ?? "").replace(/\s+/g, " ").trim();
|
|
10008
11032
|
};
|
|
@@ -10029,7 +11053,8 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10029
11053
|
xmlToolCalls: _xmlToolCalls,
|
|
10030
11054
|
narratedToolStalls: _narratedToolStalls,
|
|
10031
11055
|
autoToolExecutions: _autoToolExecutions,
|
|
10032
|
-
confidenceToolUsed: _confidenceToolUsed
|
|
11056
|
+
confidenceToolUsed: _confidenceToolUsed,
|
|
11057
|
+
liveTodosEmitted: _liveTodosEmitted
|
|
10033
11058
|
});
|
|
10034
11059
|
const FOLLOWUP_RE = /^(still\??|and\??|go on|continue|yes|no|ok|why\??|how\??|what\??|really\??|more|details|explain|show me|huh\??|so\??|\?\??|y|n)$/i;
|
|
10035
11060
|
const _isFollowUp = FOLLOWUP_RE.test(input.trim());
|
|
@@ -10065,11 +11090,6 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10065
11090
|
ctx.cesar.queue = null;
|
|
10066
11091
|
ctx.cesar.abortSignal = null;
|
|
10067
11092
|
} else {
|
|
10068
|
-
if (_isFollowUp) {
|
|
10069
|
-
const elapsed = Math.round((Date.now() - busySince) / 1e3);
|
|
10070
|
-
dispatch({ type: "info", message: `Cesar still working\u2026 ${elapsed}s` });
|
|
10071
|
-
return { delegated: false, responded: true };
|
|
10072
|
-
}
|
|
10073
11093
|
const existing = ctx.cesar.queue;
|
|
10074
11094
|
if (existing) {
|
|
10075
11095
|
existing.input = existing.input + "\n\n" + input;
|
|
@@ -10094,6 +11114,7 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10094
11114
|
ctx.cesar.searchNudged = false;
|
|
10095
11115
|
ctx.cesar.turnId = _turnId;
|
|
10096
11116
|
ctx.cesar.planDispatch = dispatch;
|
|
11117
|
+
markSteeringTurn(_turnId);
|
|
10097
11118
|
const _brainStartMs = Date.now();
|
|
10098
11119
|
if (ctx.eventBus) await ctx.eventBus.emit("pre:cesar-brain", { input });
|
|
10099
11120
|
try {
|
|
@@ -10106,6 +11127,33 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10106
11127
|
}
|
|
10107
11128
|
const cesarEngineId = config.cesarEngine ?? config.forgeFixedStarter ?? "claude";
|
|
10108
11129
|
_actualCesarEngineId = cesarEngineId;
|
|
11130
|
+
let _lastDrainedSteerImages = null;
|
|
11131
|
+
const drainSteeringIntoSend = (carrier, source) => {
|
|
11132
|
+
_lastDrainedSteerImages = null;
|
|
11133
|
+
const pending = drainSteering(_turnId);
|
|
11134
|
+
if (pending.length === 0) return carrier;
|
|
11135
|
+
const blocks = [];
|
|
11136
|
+
const drainedImages = [];
|
|
11137
|
+
for (const msg of pending) {
|
|
11138
|
+
const text = (msg.input ?? "").trim();
|
|
11139
|
+
for (const img of msg.images ?? []) {
|
|
11140
|
+
const p = img?.path;
|
|
11141
|
+
if (typeof p === "string" && p) drainedImages.push(p);
|
|
11142
|
+
}
|
|
11143
|
+
if (!text) continue;
|
|
11144
|
+
dispatch({ type: "user-message", content: text });
|
|
11145
|
+
appendMessage(ctx.chatSession, { role: "user", content: text, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11146
|
+
blocks.push(text);
|
|
11147
|
+
recordTimeline({ event: "steering_injected", engineId: cesarEngineId, cwd: _turnCwd, source, input: { text, images: drainedImages.length || void 0 } });
|
|
11148
|
+
}
|
|
11149
|
+
if (drainedImages.length) _lastDrainedSteerImages = drainedImages;
|
|
11150
|
+
if (blocks.length === 0) return carrier;
|
|
11151
|
+
const steer = blocks.map((b) => `[User steering \u2014 injected mid-turn]
|
|
11152
|
+
${b}`).join("\n\n");
|
|
11153
|
+
return carrier ? `${carrier}
|
|
11154
|
+
|
|
11155
|
+
${steer}` : steer;
|
|
11156
|
+
};
|
|
10109
11157
|
recordTimeline({
|
|
10110
11158
|
event: "turn_start",
|
|
10111
11159
|
engineId: cesarEngineId,
|
|
@@ -10126,9 +11174,26 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10126
11174
|
ctx.setActiveAbort(abort);
|
|
10127
11175
|
ctx.cesar.abortSignal = abort.signal;
|
|
10128
11176
|
ctx.cesar.lastDispatch = dispatch;
|
|
11177
|
+
dispatch({ type: "todos-clear", scope: "live" });
|
|
10129
11178
|
dispatch({ type: "confidence-update", value: null });
|
|
10130
11179
|
dispatch({ type: "spinner-start", message: "Cesar thinking\u2026", color });
|
|
10131
11180
|
await yieldToInk();
|
|
11181
|
+
try {
|
|
11182
|
+
const _budgetBackend = resolveCesarBackend(ctx, cesarEngineId);
|
|
11183
|
+
const _budgetGate = await enforceContextBudget(
|
|
11184
|
+
ctx,
|
|
11185
|
+
ctx.cesarSession ?? null,
|
|
11186
|
+
_budgetBackend.engine,
|
|
11187
|
+
_budgetBackend.backend,
|
|
11188
|
+
dispatch,
|
|
11189
|
+
input
|
|
11190
|
+
);
|
|
11191
|
+
if (!_budgetGate.proceed) {
|
|
11192
|
+
dispatch({ type: "spinner-stop" });
|
|
11193
|
+
return { delegated: false, responded: false };
|
|
11194
|
+
}
|
|
11195
|
+
} catch {
|
|
11196
|
+
}
|
|
10132
11197
|
let session;
|
|
10133
11198
|
try {
|
|
10134
11199
|
session = await ensureCesarSession(ctx);
|
|
@@ -10161,14 +11226,18 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10161
11226
|
if (freshResult.stdout.trim()) {
|
|
10162
11227
|
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: freshResult.stdout.trim() });
|
|
10163
11228
|
appendMessage(ctx.chatSession, { role: "engine", engineId: cesarEngineId, content: freshResult.stdout.trim(), timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10164
|
-
|
|
11229
|
+
if (freshResult.usage && freshResult.usage.totalTokens > 0) {
|
|
11230
|
+
tracker.record(cesarEngineId, { usage: freshResult.usage });
|
|
11231
|
+
} else {
|
|
11232
|
+
tracker.record(cesarEngineId, { prompt: input, response: freshResult.stdout.trim() });
|
|
11233
|
+
}
|
|
10165
11234
|
return { delegated: false, responded: true };
|
|
10166
11235
|
}
|
|
10167
11236
|
appendUserTurnIfAbsent(ctx.chatSession, input);
|
|
10168
11237
|
const brainHint = (freshResult.stderr || "").split("\n")[0].slice(0, 200).trim();
|
|
10169
11238
|
if (brainHint) dispatch({ type: "warning", message: `Cesar (${cesarEngineId}) returned no response: ${brainHint}` });
|
|
10170
11239
|
const health = engineHealth.get(cesarEngineId);
|
|
10171
|
-
if (health && (health.status === "auth-failed" || health.status === "unreachable")) {
|
|
11240
|
+
if (health && (health.status === "auth-failed" || health.status === "unreachable" || health.status === "binary-missing")) {
|
|
10172
11241
|
dispatch({ type: "warning", message: `Engine ${cesarEngineId} marked ${health.status} \u2014 run /cesar to switch to a healthy engine, or /engines to fix credentials.` });
|
|
10173
11242
|
}
|
|
10174
11243
|
return { delegated: false, responded: false };
|
|
@@ -10186,17 +11255,21 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10186
11255
|
let response = "";
|
|
10187
11256
|
let streaming = false;
|
|
10188
11257
|
let wasStreamed = false;
|
|
11258
|
+
let previewShown = false;
|
|
10189
11259
|
let parsedConfidence = null;
|
|
10190
11260
|
let confidenceParsed = false;
|
|
10191
11261
|
let insideThinkBlock = false;
|
|
10192
11262
|
let suppressXmlToolDisplay = false;
|
|
10193
11263
|
let sawStreamingXmlToolCall = false;
|
|
10194
11264
|
let xmlDisplayHold = "";
|
|
11265
|
+
const stripTodosForDisplay = createTodosDisplayStripper();
|
|
11266
|
+
const stripPreambleForDisplay = createPreambleStripper();
|
|
10195
11267
|
let hadToolActivity = false;
|
|
10196
11268
|
let _engineErrored = false;
|
|
10197
11269
|
let _engineErrorMsg = "";
|
|
10198
11270
|
let secondOpinionPromise = null;
|
|
10199
11271
|
let usedQuickNero = false;
|
|
11272
|
+
let _escalationSuggested = false;
|
|
10200
11273
|
const eagerPromises = [];
|
|
10201
11274
|
let eagerToolCtx = null;
|
|
10202
11275
|
const shouldInterruptForXmlTool = () => {
|
|
@@ -10342,12 +11415,12 @@ ${enrichedInput}`;
|
|
|
10342
11415
|
const signalDir = ctx.cesar.mcpSignalPath ? join14(ctx.cesar.mcpSignalPath, "..") : null;
|
|
10343
11416
|
const processMcpSideChannel = () => {
|
|
10344
11417
|
try {
|
|
10345
|
-
if (!signalDir || !
|
|
11418
|
+
if (!signalDir || !existsSync14(signalDir)) return;
|
|
10346
11419
|
const completions = readdirSync(signalDir).filter((f) => f.includes("-tool-") && f.endsWith(".json"));
|
|
10347
11420
|
for (const f of completions) {
|
|
10348
11421
|
const donePath = join14(signalDir, f);
|
|
10349
11422
|
try {
|
|
10350
|
-
const done = JSON.parse(
|
|
11423
|
+
const done = JSON.parse(readFileSync14(donePath, "utf-8"));
|
|
10351
11424
|
if (done.type !== "tool-completion") continue;
|
|
10352
11425
|
try {
|
|
10353
11426
|
unlinkSync(donePath);
|
|
@@ -10357,21 +11430,21 @@ ${enrichedInput}`;
|
|
|
10357
11430
|
const status = done.status === "error" ? "error" : "done";
|
|
10358
11431
|
const toolInput = typeof done.args === "string" ? done.args : JSON.stringify(done.args ?? {});
|
|
10359
11432
|
recordToolUse(String(done.tool ?? "tool"), "mcp", toolInput, status);
|
|
10360
|
-
|
|
11433
|
+
dispatchToolCall({
|
|
10361
11434
|
type: "tool-call",
|
|
10362
11435
|
engineId: cesarEngineId,
|
|
10363
11436
|
tool: String(done.tool ?? "tool"),
|
|
10364
11437
|
input: toolInput,
|
|
10365
11438
|
status,
|
|
10366
11439
|
output: typeof done.output === "string" ? done.output : void 0
|
|
10367
|
-
});
|
|
11440
|
+
}, { standalone: true });
|
|
10368
11441
|
} catch {
|
|
10369
11442
|
}
|
|
10370
11443
|
}
|
|
10371
11444
|
const files = readdirSync(signalDir).filter((f) => f.includes("-perm-") && !f.includes("-response"));
|
|
10372
11445
|
for (const f of files) {
|
|
10373
11446
|
const reqPath = join14(signalDir, f);
|
|
10374
|
-
const req = JSON.parse(
|
|
11447
|
+
const req = JSON.parse(readFileSync14(reqPath, "utf-8"));
|
|
10375
11448
|
if (req.type !== "permission-request") continue;
|
|
10376
11449
|
if (Date.now() - req.timestamp > 65e3) {
|
|
10377
11450
|
try {
|
|
@@ -10381,7 +11454,7 @@ ${enrichedInput}`;
|
|
|
10381
11454
|
continue;
|
|
10382
11455
|
}
|
|
10383
11456
|
const respPath = reqPath.replace(".json", "-response.json");
|
|
10384
|
-
if (
|
|
11457
|
+
if (existsSync14(respPath)) continue;
|
|
10385
11458
|
const cfg = loadConfig();
|
|
10386
11459
|
const allowed = cfg.allowedCommands ?? [];
|
|
10387
11460
|
const cmdBase = (req.args?.command ?? "").toString().trim().split(/\s+/)[0];
|
|
@@ -10414,12 +11487,30 @@ ${enrichedInput}`;
|
|
|
10414
11487
|
input: reqArgs
|
|
10415
11488
|
});
|
|
10416
11489
|
};
|
|
11490
|
+
if (String(cfg.permissionMode ?? "ask") === "deny-all") {
|
|
11491
|
+
logMcpApproval("denied", "settings.permissionMode", "permissionMode=deny-all");
|
|
11492
|
+
writeFileSync11(respPath, JSON.stringify({ type: "permission-response", id: req.id, approved: false, reason: "All tool execution is denied (permissionMode=deny-all)" }));
|
|
11493
|
+
continue;
|
|
11494
|
+
}
|
|
11495
|
+
const mcpRuleSet = parsePermissionRuleSet(cfg.permissions);
|
|
11496
|
+
const mcpRuleArg = reqTool === "Bash" ? String(req.args?.command ?? "") : String(req.args?.file_path ?? "");
|
|
11497
|
+
const mcpRuleDecision = evaluateToolRules(reqTool, mcpRuleArg, resolveWorkingDir(), mcpRuleSet);
|
|
11498
|
+
if (mcpRuleDecision === "deny") {
|
|
11499
|
+
logMcpApproval("denied", "settings.permissions", `${reqTool} denied by permissions rule`);
|
|
11500
|
+
writeFileSync11(respPath, JSON.stringify({ type: "permission-response", id: req.id, approved: false, reason: `${reqTool} blocked by deny rule in .agon.json permissions` }));
|
|
11501
|
+
continue;
|
|
11502
|
+
}
|
|
11503
|
+
if (mcpRuleDecision === "allow") {
|
|
11504
|
+
logMcpApproval("approved", "settings.permissions", `${reqTool} allowed by permissions rule`);
|
|
11505
|
+
writeFileSync11(respPath, JSON.stringify({ type: "permission-response", id: req.id, approved: true }));
|
|
11506
|
+
continue;
|
|
11507
|
+
}
|
|
10417
11508
|
if (cmdBase && allowed.some((a) => cmdBase.toLowerCase().startsWith(a.toLowerCase()))) {
|
|
10418
11509
|
logMcpApproval("approved", "mcp.allowedCommands", "command matched allowedCommands");
|
|
10419
11510
|
writeFileSync11(respPath, JSON.stringify({ type: "permission-response", id: req.id, approved: true }));
|
|
10420
11511
|
continue;
|
|
10421
11512
|
}
|
|
10422
|
-
if (reqTool === "Edit" || reqTool === "Write") {
|
|
11513
|
+
if (reqTool === "Edit" || reqTool === "Write" || reqTool === "MultiEdit") {
|
|
10423
11514
|
const approvalCwd = resolveWorkingDir();
|
|
10424
11515
|
const approvalCache = getProjectFileStateCache(approvalCwd);
|
|
10425
11516
|
const activePlan = ctx.activePlan;
|
|
@@ -10441,7 +11532,16 @@ ${enrichedInput}`;
|
|
|
10441
11532
|
}
|
|
10442
11533
|
}
|
|
10443
11534
|
logMcpApproval("prompted", "mcp.user-prompt", "Cesar wants to execute");
|
|
10444
|
-
|
|
11535
|
+
const askCommand = renderToolPermissionCommand(reqTool, reqArgs);
|
|
11536
|
+
const askReason = reqTool === "SaveMemory" ? "Cesar wants to save a durable project memory" : "Cesar wants to execute";
|
|
11537
|
+
let permDiffPreview = void 0;
|
|
11538
|
+
if (approvalToolIsFileMutating(req.tool)) {
|
|
11539
|
+
try {
|
|
11540
|
+
permDiffPreview = buildApprovalDiffPreview(String(req.tool), reqArgs);
|
|
11541
|
+
} catch {
|
|
11542
|
+
}
|
|
11543
|
+
}
|
|
11544
|
+
dispatch({ type: "permission-ask", tool: req.tool, command: askCommand, reason: askReason, diffPreview: permDiffPreview && Array.isArray(permDiffPreview.files) ? permDiffPreview : void 0, fallbackNote: permDiffPreview && typeof permDiffPreview.fallback === "string" ? permDiffPreview.fallback : void 0, resolve: (approved) => {
|
|
10445
11545
|
const wasApproved = typeof approved === "string" ? approved === "y" || approved === "a" : approved;
|
|
10446
11546
|
logMcpApproval(wasApproved ? "approved" : "denied", "mcp.user-prompt", wasApproved ? "user approved" : "user denied");
|
|
10447
11547
|
if (typeof approved === "string" && approved === "a" || approved === true) {
|
|
@@ -10470,6 +11570,13 @@ ${enrichedInput}`;
|
|
|
10470
11570
|
const gen = session.send(sendOptions);
|
|
10471
11571
|
for await (const chunk of gen) {
|
|
10472
11572
|
if (abort.signal.aborted) break;
|
|
11573
|
+
if (chunk.type === "preview") {
|
|
11574
|
+
if (!abort.signal.aborted && !streaming && !wasStreamed) {
|
|
11575
|
+
previewShown = true;
|
|
11576
|
+
dispatch({ type: "streaming-preview", engineId: cesarEngineId, content: String(chunk.content ?? "") });
|
|
11577
|
+
}
|
|
11578
|
+
continue;
|
|
11579
|
+
}
|
|
10473
11580
|
if (chunk.type === "status") {
|
|
10474
11581
|
const statusText = String(chunk.content ?? "");
|
|
10475
11582
|
const _ctxMeta = chunk.metadata ?? {};
|
|
@@ -10480,7 +11587,8 @@ ${enrichedInput}`;
|
|
|
10480
11587
|
used: Number(_ctxMeta.used ?? 0),
|
|
10481
11588
|
limit: Number(_ctxMeta.limit ?? 0),
|
|
10482
11589
|
compacted: Number(_ctxMeta.compacted ?? 0),
|
|
10483
|
-
cached: Number(_ctxMeta.cached ?? 0)
|
|
11590
|
+
cached: Number(_ctxMeta.cached ?? 0),
|
|
11591
|
+
source: typeof _ctxMeta.source === "string" ? _ctxMeta.source : void 0
|
|
10484
11592
|
});
|
|
10485
11593
|
continue;
|
|
10486
11594
|
}
|
|
@@ -10507,7 +11615,7 @@ ${enrichedInput}`;
|
|
|
10507
11615
|
dispatch({ type: "spinner-update", message: `Cesar: ${toolName}\u2026` });
|
|
10508
11616
|
if (meta.input && STREAM_ORCH.has(toolName)) {
|
|
10509
11617
|
if (cesarFastPath) {
|
|
10510
|
-
|
|
11618
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: `Blocked by fast-${fastPathMode}: do the direct work without orchestration.` });
|
|
10511
11619
|
continue;
|
|
10512
11620
|
}
|
|
10513
11621
|
if (!ctx.cesar.pendingDelegation) {
|
|
@@ -10515,35 +11623,35 @@ ${enrichedInput}`;
|
|
|
10515
11623
|
ctx.eventBus?.emit("cesar:delegation", { action: toolName.toLowerCase(), source: `stream-${toolStatus}` }).catch(() => {
|
|
10516
11624
|
});
|
|
10517
11625
|
}
|
|
10518
|
-
|
|
11626
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "done", output: typeof meta.output === "string" ? meta.output : void 0 });
|
|
10519
11627
|
continue;
|
|
10520
11628
|
}
|
|
10521
11629
|
if (toolStatus === "done") {
|
|
10522
|
-
|
|
11630
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "done", output: typeof meta.output === "string" ? meta.output : void 0 }, { toolCallId: meta.toolCallId });
|
|
10523
11631
|
} else if (toolStatus === "native") {
|
|
10524
|
-
|
|
11632
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "running" }, { toolCallId: meta.toolCallId });
|
|
10525
11633
|
} else if (toolStatus === "running" && meta.input && toolRegistry && !ctx.cesar.hasNativeTools) {
|
|
10526
11634
|
if (ctx.cesar.pendingDelegation) {
|
|
10527
|
-
|
|
11635
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "done" }, { toolCallId: meta.toolCallId });
|
|
10528
11636
|
continue;
|
|
10529
11637
|
}
|
|
10530
11638
|
const EAGER_ORCH = STREAM_ORCH;
|
|
10531
11639
|
if (EAGER_ORCH.has(toolName)) {
|
|
10532
11640
|
if (cesarFastPath) {
|
|
10533
|
-
|
|
11641
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: `Blocked by fast-${fastPathMode}: do the direct work without orchestration.` });
|
|
10534
11642
|
continue;
|
|
10535
11643
|
}
|
|
10536
11644
|
ctx.cesar.pendingDelegation = extractDelegation(toolName, meta.input ?? {});
|
|
10537
11645
|
ctx.eventBus?.emit("cesar:delegation", { action: toolName.toLowerCase(), source: "stream" }).catch(() => {
|
|
10538
11646
|
});
|
|
10539
|
-
|
|
11647
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "done" });
|
|
10540
11648
|
continue;
|
|
10541
11649
|
}
|
|
10542
|
-
|
|
11650
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "running" }, { toolCallId: meta.toolCallId, eager: true });
|
|
10543
11651
|
if (!eagerToolCtx) eagerToolCtx = createEagerToolContext(ctx, config, abort.signal, dispatch);
|
|
10544
11652
|
eagerPromises.push(executeEagerTool(toolName, meta, toolRegistry, eagerToolCtx, dispatch, cesarEngineId));
|
|
10545
11653
|
} else {
|
|
10546
|
-
|
|
11654
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: toolStatus, output: typeof meta.output === "string" ? meta.output : void 0 }, { toolCallId: meta.toolCallId });
|
|
10547
11655
|
}
|
|
10548
11656
|
continue;
|
|
10549
11657
|
}
|
|
@@ -10557,6 +11665,7 @@ ${enrichedInput}`;
|
|
|
10557
11665
|
const errBody = _errFull.slice(0, 200) || "unknown stream error";
|
|
10558
11666
|
const _deterministic = /\b(?:400|401|403|404)\b|not found|unauthorized|invalid api key|authentication|no such (?:route|endpoint)/i.test(_errFull);
|
|
10559
11667
|
dispatch({ type: "warning", message: `Cesar (${cesarEngineId}) stream error before any output: ${errBody}.${_deterministic ? " Looks like an engine config/auth issue \u2014 check the engine with /engines." : " Try again or switch engine with /engine."}` });
|
|
11668
|
+
if (previewShown) dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
10560
11669
|
clearInterval(heartbeat);
|
|
10561
11670
|
processMcpSideChannel();
|
|
10562
11671
|
if (mcpWatcherInterval) clearInterval(mcpWatcherInterval);
|
|
@@ -10624,19 +11733,29 @@ ${enrichedInput}`;
|
|
|
10624
11733
|
cleanFirst = response.replace(/<think>[\s\S]*/gi, "");
|
|
10625
11734
|
}
|
|
10626
11735
|
}
|
|
11736
|
+
emitPreamble(response);
|
|
11737
|
+
cleanFirst = stripPreambleForDisplay(cleanFirst);
|
|
10627
11738
|
if (!ctx.cesar.hasNativeTools) {
|
|
10628
11739
|
const split = splitBeforeToolMarkup(cleanFirst);
|
|
10629
11740
|
cleanFirst = split.visible;
|
|
10630
11741
|
if (split.hasToolMarkup) suppressXmlToolDisplay = true;
|
|
11742
|
+
} else {
|
|
11743
|
+
cleanFirst = stripTodosForDisplay(cleanFirst);
|
|
10631
11744
|
}
|
|
10632
11745
|
if (cleanFirst.trim()) dispatch({ type: "streaming-chunk", engineId: cesarEngineId, chunk: cleanFirst });
|
|
10633
11746
|
} else {
|
|
10634
11747
|
response += chunk.content;
|
|
10635
11748
|
noteXmlToolDetected(true);
|
|
10636
11749
|
let displayChunk = chunk.content;
|
|
11750
|
+
emitPreamble(response);
|
|
11751
|
+
displayChunk = stripPreambleForDisplay(displayChunk);
|
|
11752
|
+
if (!displayChunk) continue;
|
|
10637
11753
|
if (!ctx.cesar.hasNativeTools) {
|
|
10638
11754
|
displayChunk = takeXmlSafeDisplayChunk(displayChunk);
|
|
10639
11755
|
if (!displayChunk) continue;
|
|
11756
|
+
} else {
|
|
11757
|
+
displayChunk = stripTodosForDisplay(displayChunk);
|
|
11758
|
+
if (!displayChunk && !insideThinkBlock) continue;
|
|
10640
11759
|
}
|
|
10641
11760
|
if (insideThinkBlock) {
|
|
10642
11761
|
if (displayChunk.includes("</think>")) {
|
|
@@ -10670,10 +11789,21 @@ ${enrichedInput}`;
|
|
|
10670
11789
|
}
|
|
10671
11790
|
}
|
|
10672
11791
|
}
|
|
11792
|
+
const trailingPreambleSafe = stripPreambleForDisplay("", true);
|
|
11793
|
+
if (streaming && trailingPreambleSafe) {
|
|
11794
|
+
const routed = ctx.cesar.hasNativeTools ? stripTodosForDisplay(trailingPreambleSafe) : takeXmlSafeDisplayChunk(trailingPreambleSafe);
|
|
11795
|
+
if (routed.trim()) dispatch({ type: "streaming-chunk", engineId: cesarEngineId, chunk: routed });
|
|
11796
|
+
}
|
|
10673
11797
|
const trailingXmlSafe = takeXmlSafeDisplayChunk("", true);
|
|
10674
11798
|
if (streaming && trailingXmlSafe.trim()) {
|
|
10675
11799
|
dispatch({ type: "streaming-chunk", engineId: cesarEngineId, chunk: trailingXmlSafe });
|
|
10676
11800
|
}
|
|
11801
|
+
if (ctx.cesar.hasNativeTools) {
|
|
11802
|
+
const trailingTodosSafe = stripTodosForDisplay("", true);
|
|
11803
|
+
if (streaming && trailingTodosSafe.trim()) {
|
|
11804
|
+
dispatch({ type: "streaming-chunk", engineId: cesarEngineId, chunk: trailingTodosSafe });
|
|
11805
|
+
}
|
|
11806
|
+
}
|
|
10677
11807
|
} catch (err) {
|
|
10678
11808
|
clearInterval(heartbeat);
|
|
10679
11809
|
processMcpSideChannel();
|
|
@@ -10684,6 +11814,7 @@ ${enrichedInput}`;
|
|
|
10684
11814
|
dispatch({ type: "warning", message: `Cesar stream error (partial response preserved): ${(err.message ?? "").slice(0, 80)}` });
|
|
10685
11815
|
} else {
|
|
10686
11816
|
dispatch({ type: "warning", message: "Cesar session error \u2014 will restart on next message" });
|
|
11817
|
+
if (previewShown) dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
10687
11818
|
return { delegated: false, responded: false, decisionReason: "stream-error" };
|
|
10688
11819
|
}
|
|
10689
11820
|
}
|
|
@@ -10692,6 +11823,7 @@ ${enrichedInput}`;
|
|
|
10692
11823
|
if (mcpWatcherInterval) clearInterval(mcpWatcherInterval);
|
|
10693
11824
|
if (abort.signal.aborted) {
|
|
10694
11825
|
dispatch({ type: "spinner-stop" });
|
|
11826
|
+
if (previewShown) dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
10695
11827
|
const elapsed = Math.round((Date.now() - _turnStart) / 1e3);
|
|
10696
11828
|
if (elapsed >= cesarTimeout) {
|
|
10697
11829
|
dispatch({ type: "warning", message: `Cesar timed out after ${elapsed}s. Try a simpler question, or use /forge for complex tasks.` });
|
|
@@ -10704,6 +11836,8 @@ ${enrichedInput}`;
|
|
|
10704
11836
|
if (ctx.cesar.hasNativeTools) {
|
|
10705
11837
|
response = response.replace(/<tool\s+name="[^"]+">[\s\S]*?<\/tool>/g, "").trim();
|
|
10706
11838
|
}
|
|
11839
|
+
response = emitPreamble(response);
|
|
11840
|
+
response = emitLiveTodos(response);
|
|
10707
11841
|
if (eagerPromises.length > 0 && !ctx.cesar.hasNativeTools && session.alive && !abort.signal.aborted) {
|
|
10708
11842
|
dispatch({ type: "spinner-start", message: `Cesar: awaiting ${eagerPromises.length} tool result${eagerPromises.length > 1 ? "s" : ""}\u2026`, color });
|
|
10709
11843
|
const eagerResults = await Promise.all(eagerPromises);
|
|
@@ -10716,7 +11850,8 @@ ${enrichedInput}`;
|
|
|
10716
11850
|
const failedTools = eagerFailedToolNames(eagerResults);
|
|
10717
11851
|
const repairUsed = [];
|
|
10718
11852
|
const repairResults = [];
|
|
10719
|
-
const
|
|
11853
|
+
const _steerMsg = drainSteeringIntoSend(formatted, "xml");
|
|
11854
|
+
const contGen = session.send({ message: _steerMsg, signal: abort.signal, ..._lastDrainedSteerImages ? { images: _lastDrainedSteerImages } : {} });
|
|
10720
11855
|
for await (const chunk of contGen) {
|
|
10721
11856
|
if (chunk.type === "text") continuation += chunk.content;
|
|
10722
11857
|
if (chunk.type === "tool_call") {
|
|
@@ -10733,9 +11868,9 @@ ${enrichedInput}`;
|
|
|
10733
11868
|
continue;
|
|
10734
11869
|
}
|
|
10735
11870
|
if (toolStatus !== "running" && toolStatus !== "native") {
|
|
10736
|
-
|
|
11871
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: toolStatus, output: typeof meta.output === "string" ? meta.output : void 0 });
|
|
10737
11872
|
} else if (failedTools.includes(toolName)) {
|
|
10738
|
-
|
|
11873
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: "Repair retry already used for this tool in this turn." });
|
|
10739
11874
|
}
|
|
10740
11875
|
}
|
|
10741
11876
|
if (chunk.type === "done" || chunk.type === "error") break;
|
|
@@ -10754,7 +11889,7 @@ ${enrichedInput}`;
|
|
|
10754
11889
|
const meta = chunk.metadata ?? {};
|
|
10755
11890
|
const toolName = chunk.content || "tool";
|
|
10756
11891
|
const toolInput = typeof meta.input === "string" ? meta.input : meta.input ? JSON.stringify(meta.input) : "";
|
|
10757
|
-
|
|
11892
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: "Tool repair loop is one retry per failed tool; further tool calls were not executed automatically." });
|
|
10758
11893
|
}
|
|
10759
11894
|
if (chunk.type === "done" || chunk.type === "error") break;
|
|
10760
11895
|
}
|
|
@@ -10762,7 +11897,7 @@ ${enrichedInput}`;
|
|
|
10762
11897
|
}
|
|
10763
11898
|
}
|
|
10764
11899
|
dispatch({ type: "spinner-stop" });
|
|
10765
|
-
if (continuation.trim()) response = continuation.trim();
|
|
11900
|
+
if (continuation.trim()) response = emitLiveTodos(emitPreamble(continuation.trim()));
|
|
10766
11901
|
}
|
|
10767
11902
|
}
|
|
10768
11903
|
if (!confidenceParsed && response) {
|
|
@@ -10798,22 +11933,22 @@ ${enrichedInput}`;
|
|
|
10798
11933
|
if (!ctx.cesar.pendingDelegation && ctx.cesar.mcpSignalPath) {
|
|
10799
11934
|
try {
|
|
10800
11935
|
const signalPath = ctx.cesar.mcpSignalPath;
|
|
10801
|
-
if (
|
|
10802
|
-
const signals = JSON.parse(
|
|
11936
|
+
if (existsSync14(signalPath)) {
|
|
11937
|
+
const signals = JSON.parse(readFileSync14(signalPath, "utf-8"));
|
|
10803
11938
|
unlinkSync(signalPath);
|
|
10804
11939
|
for (const signal of Array.isArray(signals) ? signals : [signals]) {
|
|
10805
11940
|
if (!signal.timestamp || Date.now() - signal.timestamp >= 6e4) continue;
|
|
10806
11941
|
if (cesarFastPath && FAST_PATH_BLOCKED_TOOLS.includes(signal.tool)) {
|
|
10807
11942
|
const toolInput = JSON.stringify(signal.args ?? {});
|
|
10808
11943
|
recordToolUse(signal.tool, "mcp", toolInput, "error");
|
|
10809
|
-
|
|
11944
|
+
dispatchToolCall({
|
|
10810
11945
|
type: "tool-call",
|
|
10811
11946
|
engineId: cesarEngineId,
|
|
10812
11947
|
tool: signal.tool,
|
|
10813
11948
|
input: toolInput,
|
|
10814
11949
|
status: "error",
|
|
10815
11950
|
output: `Blocked by fast-${fastPathMode}: do the direct work without orchestration.`
|
|
10816
|
-
});
|
|
11951
|
+
}, { standalone: true });
|
|
10817
11952
|
continue;
|
|
10818
11953
|
}
|
|
10819
11954
|
if (signal.tool === "ReportConfidence") {
|
|
@@ -10843,17 +11978,17 @@ ${enrichedInput}`;
|
|
|
10843
11978
|
recordToolUse("ProposePlan", "mcp", JSON.stringify(signal.args ?? {}), "done");
|
|
10844
11979
|
const activePlan = ctx.activePlan;
|
|
10845
11980
|
if (activePlan && ["planning", "running", "paused"].includes(activePlan.state)) {
|
|
10846
|
-
|
|
11981
|
+
dispatchToolCall({
|
|
10847
11982
|
type: "tool-call",
|
|
10848
11983
|
engineId: cesarEngineId,
|
|
10849
11984
|
tool: "ProposePlan",
|
|
10850
11985
|
input: JSON.stringify(signal.args ?? {}),
|
|
10851
11986
|
status: "error",
|
|
10852
11987
|
output: "A Cesar plan is already active; nested plans are blocked. Resume or cancel the current plan before proposing another."
|
|
10853
|
-
});
|
|
11988
|
+
}, { standalone: true });
|
|
10854
11989
|
continue;
|
|
10855
11990
|
}
|
|
10856
|
-
const { handleProposePlan } = await import("./plan-mode-
|
|
11991
|
+
const { handleProposePlan } = await import("./plan-mode-I3BZOBFB.js");
|
|
10857
11992
|
const planDispatch = ctx.cesar.planDispatch ?? dispatch;
|
|
10858
11993
|
if (planDispatch) {
|
|
10859
11994
|
try {
|
|
@@ -10866,11 +12001,11 @@ ${enrichedInput}`;
|
|
|
10866
12001
|
}
|
|
10867
12002
|
} else if (signal.tool === "ExitPlanMode") {
|
|
10868
12003
|
recordToolUse("ExitPlanMode", "mcp", JSON.stringify(signal.args ?? {}), "done");
|
|
10869
|
-
const { handleExitPlanMode } = await import("./plan-mode-
|
|
12004
|
+
const { handleExitPlanMode } = await import("./plan-mode-I3BZOBFB.js");
|
|
10870
12005
|
const planDispatch = ctx.cesar.planDispatch ?? dispatch;
|
|
10871
12006
|
try {
|
|
10872
12007
|
const exitResult = handleExitPlanMode(String(signal.args?.reason ?? ""), planDispatch, ctx);
|
|
10873
|
-
|
|
12008
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: "ExitPlanMode", input: JSON.stringify(signal.args ?? {}), status: "done", output: exitResult }, { standalone: true });
|
|
10874
12009
|
} catch (err) {
|
|
10875
12010
|
console.warn(`[agon] ExitPlanMode via MCP signal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10876
12011
|
}
|
|
@@ -10923,7 +12058,14 @@ ${enrichedInput}`;
|
|
|
10923
12058
|
// Phase 1: investigate only — mutating tools blocked until after escalation check
|
|
10924
12059
|
blockedTools: cesarFastPath ? FAST_PATH_BLOCKED_TOOLS : void 0,
|
|
10925
12060
|
blockedToolMessage: cesarFastPath ? `Blocked by fast-${fastPathMode}: do the direct work without orchestration.` : void 0,
|
|
10926
|
-
source: "orchestrator"
|
|
12061
|
+
source: "orchestrator",
|
|
12062
|
+
// CC-parity allow/deny rules reach all three XML runToolLoop paths via
|
|
12063
|
+
// this shared toolCtx (runToolLoop → executeToolCalls → executeToolCall
|
|
12064
|
+
// → handler.checkPermission). deny-first, before mode-based auto-allow.
|
|
12065
|
+
permissionRules: parsePermissionRuleSet(config.permissions),
|
|
12066
|
+
// CC-parity PreToolUse/PostToolUse hooks reach all three XML
|
|
12067
|
+
// runToolLoop paths via this shared toolCtx (→ executeToolCall).
|
|
12068
|
+
toolHooks: parseToolHooks(config.hooks)
|
|
10927
12069
|
};
|
|
10928
12070
|
const _lastToolInputs = {};
|
|
10929
12071
|
const xmlToolBridge = createStreamBridge(dispatch, { initialEngineId: cesarEngineId });
|
|
@@ -10969,12 +12111,13 @@ ${enrichedInput}`;
|
|
|
10969
12111
|
async (message) => {
|
|
10970
12112
|
if (ctx.cesar.pendingDelegation) return "[Delegation pending]";
|
|
10971
12113
|
if (!session.alive || abort.signal.aborted) return "";
|
|
12114
|
+
const sendMessage = drainSteeringIntoSend(message, "xml");
|
|
10972
12115
|
dispatch({ type: "spinner-start", message: "Cesar processing results\u2026", color });
|
|
10973
12116
|
_engineErrored = false;
|
|
10974
12117
|
_engineErrorMsg = "";
|
|
10975
12118
|
let nextResponse = "";
|
|
10976
12119
|
const gen = session.send({
|
|
10977
|
-
message,
|
|
12120
|
+
message: sendMessage,
|
|
10978
12121
|
signal: abort.signal,
|
|
10979
12122
|
toolLoopBaseBudget: cesarFastPath ? fastPathBaseBudget : void 0,
|
|
10980
12123
|
toolLoopMaxBudget: cesarFastPath ? fastPathMaxBudget : void 0
|
|
@@ -11046,7 +12189,7 @@ ${enrichedInput}`;
|
|
|
11046
12189
|
const lastInput = _lastToolInputs[tool] ?? "{}";
|
|
11047
12190
|
let command = "";
|
|
11048
12191
|
try {
|
|
11049
|
-
command =
|
|
12192
|
+
command = renderToolPermissionCommand(tool, JSON.parse(lastInput));
|
|
11050
12193
|
} catch {
|
|
11051
12194
|
command = lastInput;
|
|
11052
12195
|
}
|
|
@@ -11073,7 +12216,7 @@ ${enrichedInput}`;
|
|
|
11073
12216
|
try {
|
|
11074
12217
|
const activePlan = ctx.activePlan;
|
|
11075
12218
|
if (activePlan && ["planning", "running", "paused"].includes(activePlan.state)) {
|
|
11076
|
-
|
|
12219
|
+
dispatchToolCall({
|
|
11077
12220
|
type: "tool-call",
|
|
11078
12221
|
engineId: cesarEngineId,
|
|
11079
12222
|
tool: "ProposePlan",
|
|
@@ -11082,7 +12225,7 @@ ${enrichedInput}`;
|
|
|
11082
12225
|
output: "A Cesar plan is already active; nested plans are blocked. Resume or cancel the current plan before proposing another."
|
|
11083
12226
|
});
|
|
11084
12227
|
} else {
|
|
11085
|
-
const { handleProposePlan } = await import("./plan-mode-
|
|
12228
|
+
const { handleProposePlan } = await import("./plan-mode-I3BZOBFB.js");
|
|
11086
12229
|
const planDispatch = ctx.cesar.planDispatch ?? dispatch;
|
|
11087
12230
|
const plan = await handleProposePlan(ppArgs, planDispatch, ctx);
|
|
11088
12231
|
if (ctx.setActivePlan) ctx.setActivePlan(plan);
|
|
@@ -11106,10 +12249,10 @@ ${enrichedInput}`;
|
|
|
11106
12249
|
const epArgs = ctx.cesar._exitPlanModeArgs;
|
|
11107
12250
|
delete ctx.cesar._exitPlanModeArgs;
|
|
11108
12251
|
try {
|
|
11109
|
-
const { handleExitPlanMode } = await import("./plan-mode-
|
|
12252
|
+
const { handleExitPlanMode } = await import("./plan-mode-I3BZOBFB.js");
|
|
11110
12253
|
const planDispatch = ctx.cesar.planDispatch ?? dispatch;
|
|
11111
12254
|
const exitResult = handleExitPlanMode(String(epArgs?.reason ?? ""), planDispatch, ctx);
|
|
11112
|
-
|
|
12255
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: "ExitPlanMode", input: JSON.stringify(epArgs ?? {}), status: "done", output: exitResult });
|
|
11113
12256
|
} catch (err) {
|
|
11114
12257
|
console.warn(`[agon] ExitPlanMode via tool loop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11115
12258
|
}
|
|
@@ -11135,7 +12278,7 @@ ${enrichedInput}`;
|
|
|
11135
12278
|
confidenceParsed = true;
|
|
11136
12279
|
}
|
|
11137
12280
|
}
|
|
11138
|
-
const shouldQuickNero = parsedConfidence !== null && !cesarFastPath && !secondOpinionPromise && !ctx.cesar.advisorPending && !_isFollowUp && !abort.signal.aborted && !ctx.cesar.pendingDelegation &&
|
|
12281
|
+
const shouldQuickNero = parsedConfidence !== null && !cesarFastPath && !secondOpinionPromise && !ctx.cesar.advisorPending && !_isFollowUp && !abort.signal.aborted && !ctx.cesar.pendingDelegation && ctx.cesar.quickNeroRequested === true;
|
|
11139
12282
|
if (ctx.cesar.quickNeroRequested) ctx.cesar.quickNeroRequested = false;
|
|
11140
12283
|
if (shouldQuickNero) {
|
|
11141
12284
|
const quickNeroConfidence = parsedConfidence;
|
|
@@ -11175,7 +12318,7 @@ ${qnResult.challengeText}` : ""
|
|
|
11175
12318
|
appendMessage(ctx.chatSession, { role: "engine", engineId: ch.engineId, content: ch.content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11176
12319
|
}
|
|
11177
12320
|
}
|
|
11178
|
-
|
|
12321
|
+
recordCesarTurn(ctx, cesarEngineId, input, response);
|
|
11179
12322
|
return {
|
|
11180
12323
|
mode: escalationAction,
|
|
11181
12324
|
delegated: true,
|
|
@@ -11189,12 +12332,24 @@ ${qnResult.challengeText}` : ""
|
|
|
11189
12332
|
};
|
|
11190
12333
|
}
|
|
11191
12334
|
}
|
|
12335
|
+
if (!_escalationSuggested && !usedQuickNero && !cesarFastPath && !_isFollowUp && !abort.signal.aborted && !ctx.cesar.pendingDelegation) {
|
|
12336
|
+
const _strictConf = extractStrictConfidence(response);
|
|
12337
|
+
if (_strictConf !== null && _strictConf < ESCALATION_SUGGESTION_THRESHOLD) {
|
|
12338
|
+
_escalationSuggested = true;
|
|
12339
|
+
dispatch({ type: "info", message: buildEscalationSuggestionLine(_strictConf) });
|
|
12340
|
+
}
|
|
12341
|
+
}
|
|
11192
12342
|
const investigationResponse = response;
|
|
11193
12343
|
let mutationStallForced = false;
|
|
11194
12344
|
if (!mutationDeferred && !inPlanMode && !ctx.cesar.pendingDelegation && (hadToolActivity || ranToolLoop) && session.alive && !abort.signal.aborted && detectMutationIntentStall(response)) {
|
|
11195
|
-
|
|
11196
|
-
|
|
11197
|
-
|
|
12345
|
+
const _usedMutatingTool = _toolsUsed.some((t) => isWriteToolName(t) || isBashToolName(t));
|
|
12346
|
+
if (shouldDeescalateGuard({ intakeKind: routingHints.intakeKind, recommendedFlow: routingHints.recommendedFlow, usedMutatingTool: _usedMutatingTool })) {
|
|
12347
|
+
dispatch({ type: "warning", message: "Cesar guard: write-narration matched on a conversational turn \u2014 warning only, no auto-unlock (de-escalated)." });
|
|
12348
|
+
} else {
|
|
12349
|
+
mutationDeferred = true;
|
|
12350
|
+
mutationStallForced = true;
|
|
12351
|
+
dispatch({ type: "warning", message: "Cesar described a write but called no tool \u2014 unlocking execution and pushing it to apply directly." });
|
|
12352
|
+
}
|
|
11198
12353
|
}
|
|
11199
12354
|
if (mutationDeferred && toolRegistry && session.alive && !abort.signal.aborted) {
|
|
11200
12355
|
toolCtx.readOnlyMode = false;
|
|
@@ -11214,10 +12369,11 @@ ${qnResult.challengeText}` : ""
|
|
|
11214
12369
|
async (message) => {
|
|
11215
12370
|
if (ctx.cesar.pendingDelegation) return "[Delegation pending]";
|
|
11216
12371
|
if (!session.alive || abort.signal.aborted) return "";
|
|
12372
|
+
const sendMessage = drainSteeringIntoSend(message, "exec");
|
|
11217
12373
|
dispatch({ type: "spinner-start", message: "Cesar executing\u2026", color });
|
|
11218
12374
|
let nextResponse = "";
|
|
11219
12375
|
const gen = session.send({
|
|
11220
|
-
message,
|
|
12376
|
+
message: sendMessage,
|
|
11221
12377
|
signal: abort.signal,
|
|
11222
12378
|
toolLoopBaseBudget: cesarFastPath ? fastPathBaseBudget : void 0,
|
|
11223
12379
|
toolLoopMaxBudget: cesarFastPath ? fastPathMaxBudget : void 0
|
|
@@ -11293,7 +12449,14 @@ ${qnResult.challengeText}` : ""
|
|
|
11293
12449
|
dispatch({ type: "spinner-stop" });
|
|
11294
12450
|
}
|
|
11295
12451
|
}
|
|
11296
|
-
|
|
12452
|
+
const _fabDelegationDetected = !ctx.cesar.pendingDelegation && session.alive && !abort.signal.aborted && detectFabricatedDelegation(response.trim());
|
|
12453
|
+
if (_fabDelegationDetected && shouldDeescalateGuard({
|
|
12454
|
+
intakeKind: routingHints.intakeKind,
|
|
12455
|
+
recommendedFlow: routingHints.recommendedFlow,
|
|
12456
|
+
usedMutatingTool: _toolsUsed.some((t) => isWriteToolName(t) || isBashToolName(t))
|
|
12457
|
+
})) {
|
|
12458
|
+
dispatch({ type: "warning", message: "Cesar guard: dispatch-claim matched on a conversational turn \u2014 warning only, no grounding turn (de-escalated)." });
|
|
12459
|
+
} else if (_fabDelegationDetected) {
|
|
11297
12460
|
dispatch({ type: "warning", message: "Cesar claimed a job was running but never dispatched one \u2014 grounding..." });
|
|
11298
12461
|
dispatch({ type: "spinner-start", message: "Cesar grounding\u2026", color });
|
|
11299
12462
|
try {
|
|
@@ -11405,12 +12568,11 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11405
12568
|
dispatch({ type: "spinner-stop" });
|
|
11406
12569
|
}
|
|
11407
12570
|
}
|
|
11408
|
-
const _AUTO_CONT_WRITE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
11409
12571
|
const _AUTO_CONT_LOOP_ORCH = /* @__PURE__ */ new Set(["Forge", "Brainstorm", "Tribunal", "Campfire", "Pipeline", "Review", "Agent", "Goal", "Conquer"]);
|
|
11410
12572
|
const _AUTO_CONT_CONTINUE_RE = /\b(?:now i'?ll|next i'?ll|still need|let me also|i'?ll also|then i'?ll|next step|next up)\b/i;
|
|
11411
12573
|
const _AUTO_CONT_READONLY_DONE_RE = /\b(?:tests? passed|all (?:tests|checks) pass|no matches found|no issues found|no errors|all clean|nothing to (?:do|fix|change)|already (?:correct|fixed|in place|done))\b/i;
|
|
11412
12574
|
const _detectTurnState = (resp, baselineToolCount) => {
|
|
11413
|
-
const wroteSinceBaseline = _toolsUsed.slice(baselineToolCount).some((t) =>
|
|
12575
|
+
const wroteSinceBaseline = _toolsUsed.slice(baselineToolCount).some((t) => isWriteToolName(t));
|
|
11414
12576
|
if (findTrailingUserQuestion(resp)) return "asks-user";
|
|
11415
12577
|
if (wroteSinceBaseline && !_AUTO_CONT_CONTINUE_RE.test(resp)) return "done";
|
|
11416
12578
|
const effectSummary = /(?:created|modified|deleted|updated|added|removed|fixed|implemented|renamed)\b[^.]{0,80}(?:\b(?:and|,)\b[^.]{0,80}\b(?:created|modified|deleted|updated|added|removed|fixed|implemented|renamed)\b)/i.test(resp);
|
|
@@ -11419,6 +12581,18 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11419
12581
|
if (_AUTO_CONT_READONLY_DONE_RE.test(resp)) return "done";
|
|
11420
12582
|
return "stuck";
|
|
11421
12583
|
};
|
|
12584
|
+
const _doneClaimSignature = (resp) => {
|
|
12585
|
+
return `${_toolsUsed.length}:${resp.trim().slice(-200)}`;
|
|
12586
|
+
};
|
|
12587
|
+
const _shouldGateNudge = (resp) => {
|
|
12588
|
+
const g = ctx.cesar?.discoveredGate;
|
|
12589
|
+
if (!g || !g.command || !g.matchers.length) return false;
|
|
12590
|
+
if (ctx.cesar?.gateWaived) return false;
|
|
12591
|
+
if (_ranGate) return false;
|
|
12592
|
+
if (!_toolsUsed.some((t) => isWriteToolName(t))) return false;
|
|
12593
|
+
if (ctx.cesar?.gateNudgedClaim === _doneClaimSignature(resp)) return false;
|
|
12594
|
+
return true;
|
|
12595
|
+
};
|
|
11422
12596
|
const _buildContToolLoopOpts = () => ({
|
|
11423
12597
|
onToolCall: (name, inp) => {
|
|
11424
12598
|
_lastToolInputs[name] = JSON.stringify(inp);
|
|
@@ -11453,7 +12627,7 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11453
12627
|
const lastInput = _lastToolInputs[tool] ?? "{}";
|
|
11454
12628
|
let command = "";
|
|
11455
12629
|
try {
|
|
11456
|
-
command =
|
|
12630
|
+
command = renderToolPermissionCommand(tool, JSON.parse(lastInput));
|
|
11457
12631
|
} catch {
|
|
11458
12632
|
command = lastInput;
|
|
11459
12633
|
}
|
|
@@ -11477,8 +12651,85 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11477
12651
|
let _consecutiveNoProgress = 0;
|
|
11478
12652
|
while (_continuations < MAX_CONTINUATIONS && session.alive && !abort.signal.aborted && !ctx.cesar.pendingDelegation) {
|
|
11479
12653
|
const state = _detectTurnState(response, _loopStartToolCount);
|
|
11480
|
-
if (state === "asks-user"
|
|
11481
|
-
|
|
12654
|
+
if (state === "asks-user") break;
|
|
12655
|
+
if (state === "done") {
|
|
12656
|
+
if (_shouldGateNudge(response)) {
|
|
12657
|
+
if (ctx.cesar) ctx.cesar.gateNudgedClaim = _doneClaimSignature(response);
|
|
12658
|
+
const _g = ctx.cesar.discoveredGate;
|
|
12659
|
+
const _gateNudge = `[SYSTEM] You claimed the task is done but never ran the project's verification gate (${_g.command}) this turn. Run it now to confirm the change is green, or tell me in one sentence why it should be skipped.`;
|
|
12660
|
+
_continuations++;
|
|
12661
|
+
dispatch({ type: "warning", message: `Cesar claimed done without running the gate (${_g.command}) \u2014 nudging to verify (${_continuations}/${MAX_CONTINUATIONS}).` });
|
|
12662
|
+
dispatch({ type: "spinner-start", message: `${cesarEngineId} verifying\u2026`, color });
|
|
12663
|
+
let _gateResp = "";
|
|
12664
|
+
try {
|
|
12665
|
+
const _gateGen = session.send({
|
|
12666
|
+
message: _gateNudge,
|
|
12667
|
+
signal: abort.signal,
|
|
12668
|
+
toolLoopBaseBudget: cesarFastPath ? fastPathBaseBudget : void 0,
|
|
12669
|
+
toolLoopMaxBudget: cesarFastPath ? fastPathMaxBudget : void 0
|
|
12670
|
+
});
|
|
12671
|
+
for await (const _c of _gateGen) {
|
|
12672
|
+
if (abort.signal.aborted) break;
|
|
12673
|
+
if (_c.type === "text") _gateResp += _c.content;
|
|
12674
|
+
if (_c.type === "error" && !_gateResp.trim()) {
|
|
12675
|
+
_engineErrored = true;
|
|
12676
|
+
_engineErrorMsg = String(_c.content ?? "").slice(0, 300);
|
|
12677
|
+
}
|
|
12678
|
+
if (_c.type === "done" || _c.type === "error") break;
|
|
12679
|
+
}
|
|
12680
|
+
} catch (gateErr) {
|
|
12681
|
+
dispatch({ type: "spinner-stop" });
|
|
12682
|
+
_engineErrored = true;
|
|
12683
|
+
_engineErrorMsg = gateErr instanceof Error ? gateErr.message : String(gateErr);
|
|
12684
|
+
break;
|
|
12685
|
+
}
|
|
12686
|
+
dispatch({ type: "spinner-stop" });
|
|
12687
|
+
if (_engineErrored) break;
|
|
12688
|
+
const _cleanGate = _gateResp.replace(/<think>[\s\S]*?<\/think>\s*/gi, "").trim();
|
|
12689
|
+
try {
|
|
12690
|
+
const _parsedGate = parseToolCalls(_cleanGate);
|
|
12691
|
+
if (_parsedGate.hasToolCalls && toolRegistry) {
|
|
12692
|
+
const _gateResult = await runToolLoop(
|
|
12693
|
+
async (message) => {
|
|
12694
|
+
if (!session.alive || abort.signal.aborted) return "";
|
|
12695
|
+
_engineErrored = false;
|
|
12696
|
+
_engineErrorMsg = "";
|
|
12697
|
+
let _nr = "";
|
|
12698
|
+
const _g2 = session.send({ message, signal: abort.signal, toolLoopBaseBudget: cesarFastPath ? fastPathBaseBudget : void 0, toolLoopMaxBudget: cesarFastPath ? fastPathMaxBudget : void 0 });
|
|
12699
|
+
for await (const _ch of _g2) {
|
|
12700
|
+
if (_ch.type === "text") _nr += _ch.content;
|
|
12701
|
+
if (_ch.type === "error" && !_nr.trim()) {
|
|
12702
|
+
_engineErrored = true;
|
|
12703
|
+
_engineErrorMsg = String(_ch.content ?? "").slice(0, 300);
|
|
12704
|
+
}
|
|
12705
|
+
if (_ch.type === "done" || _ch.type === "error") break;
|
|
12706
|
+
}
|
|
12707
|
+
return _nr.trim() || (_engineErrorMsg ? `[Engine error: ${_engineErrorMsg}]` : "[No response from engine]");
|
|
12708
|
+
},
|
|
12709
|
+
_cleanGate,
|
|
12710
|
+
toolCtx,
|
|
12711
|
+
toolRegistry,
|
|
12712
|
+
_buildContToolLoopOpts()
|
|
12713
|
+
);
|
|
12714
|
+
response = response + "\n\n" + (_gateResult.finalText?.trim() ?? "");
|
|
12715
|
+
_toolCallTurns += _gateResult.turns ?? 0;
|
|
12716
|
+
} else if (_cleanGate) {
|
|
12717
|
+
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: _cleanGate });
|
|
12718
|
+
response = response + "\n\n" + _cleanGate;
|
|
12719
|
+
}
|
|
12720
|
+
} catch {
|
|
12721
|
+
if (_cleanGate) {
|
|
12722
|
+
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: _cleanGate });
|
|
12723
|
+
response = response + "\n\n" + _cleanGate;
|
|
12724
|
+
}
|
|
12725
|
+
}
|
|
12726
|
+
_prevToolCount = _toolsUsed.length;
|
|
12727
|
+
if (ctx.cesar) ctx.cesar.gateNudgedClaim = _doneClaimSignature(response);
|
|
12728
|
+
continue;
|
|
12729
|
+
}
|
|
12730
|
+
break;
|
|
12731
|
+
}
|
|
12732
|
+
const wroteFiles = _toolsUsed.some((t) => isWriteToolName(t));
|
|
11482
12733
|
const filePathRe = /(?:^|\s|`)((?:\.{1,2}\/|~\/|\/|(?:packages|src|tests|scripts|kern)\/)[\w./-]+\.(?:ts|tsx|kern|js|jsx|py|md|json|yaml|yml|toml|sh|css|scss|html))\b/g;
|
|
11483
12734
|
const filesMentioned = Array.from(new Set(Array.from(response.matchAll(filePathRe), (m) => m[1]))).slice(0, 3);
|
|
11484
12735
|
let nudge;
|
|
@@ -11514,7 +12765,7 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11514
12765
|
break;
|
|
11515
12766
|
}
|
|
11516
12767
|
dispatch({ type: "spinner-stop" });
|
|
11517
|
-
const cleanCont = contResponse.replace(/<think>[\s\S]*?<\/think>\s*/gi, "").trim();
|
|
12768
|
+
const cleanCont = emitLiveTodos(emitPreamble(contResponse.replace(/<think>[\s\S]*?<\/think>\s*/gi, "").trim()));
|
|
11518
12769
|
if (_engineErrored) {
|
|
11519
12770
|
const _reason = _engineErrorMsg || "no response";
|
|
11520
12771
|
dispatch({ type: "warning", message: `Cesar (${cesarEngineId}) stopped \u2014 engine did not respond: ${_reason.slice(0, 160)}. Try again, /compact, or switch engine with /engine.` });
|
|
@@ -11535,12 +12786,13 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11535
12786
|
async (message) => {
|
|
11536
12787
|
if (ctx.cesar.pendingDelegation) return "[Delegation pending]";
|
|
11537
12788
|
if (!session.alive || abort.signal.aborted) return "";
|
|
12789
|
+
const sendMessage = drainSteeringIntoSend(message, "auto");
|
|
11538
12790
|
dispatch({ type: "spinner-start", message: "Cesar executing\u2026", color });
|
|
11539
12791
|
_engineErrored = false;
|
|
11540
12792
|
_engineErrorMsg = "";
|
|
11541
12793
|
let nextResp = "";
|
|
11542
12794
|
const gen = session.send({
|
|
11543
|
-
message,
|
|
12795
|
+
message: sendMessage,
|
|
11544
12796
|
signal: abort.signal,
|
|
11545
12797
|
toolLoopBaseBudget: cesarFastPath ? fastPathBaseBudget : void 0,
|
|
11546
12798
|
toolLoopMaxBudget: cesarFastPath ? fastPathMaxBudget : void 0
|
|
@@ -11637,6 +12889,9 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11637
12889
|
message: _readHeavy ? `Cesar made ${_turnToolEvents} tool calls this turn (${_readToolEventCount} reads) \u2014 read-heavy, may be searching in circles. Consider /compact or a more specific instruction.` : `Cesar made ${_turnToolEvents} tool calls this turn \u2014 unusually high. If it felt stuck, /compact to shrink context or re-prompt more specifically.`
|
|
11638
12890
|
});
|
|
11639
12891
|
}
|
|
12892
|
+
if (previewShown && !streaming) {
|
|
12893
|
+
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
12894
|
+
}
|
|
11640
12895
|
if (!streaming && response && !ranToolLoop && !wasStreamed) {
|
|
11641
12896
|
dispatch({ type: "spinner-stop" });
|
|
11642
12897
|
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: response });
|
|
@@ -11655,7 +12910,7 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11655
12910
|
appendMessage(ctx.chatSession, { role: "engine", engineId: ch.engineId, content: ch.content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11656
12911
|
}
|
|
11657
12912
|
}
|
|
11658
|
-
const tokenUsage =
|
|
12913
|
+
const tokenUsage = recordCesarTurn(ctx, cesarEngineId, input, response);
|
|
11659
12914
|
try {
|
|
11660
12915
|
const tracePath = join14(RUNS_DIR, "cesar-trace.jsonl");
|
|
11661
12916
|
const taskClass = classifyTask(input);
|
|
@@ -11687,6 +12942,11 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11687
12942
|
confidenceToolUsed: toolTelemetry.confidenceToolUsed,
|
|
11688
12943
|
hasNativeTools: toolTelemetry.hasNativeTools,
|
|
11689
12944
|
delegated: false,
|
|
12945
|
+
// Live-todo telemetry: emitted? + whether this was a multi-tool turn
|
|
12946
|
+
// (toolCallTurns>1) — the later-gated metric is multi-tool turns that
|
|
12947
|
+
// emitted ZERO todos, derivable from these two fields.
|
|
12948
|
+
liveTodosEmitted: toolTelemetry.liveTodosEmitted,
|
|
12949
|
+
multiToolTurn: toolTelemetry.toolCallTurns > 1,
|
|
11690
12950
|
confidence: parsedConfidence,
|
|
11691
12951
|
tokens: tokenUsage ? { prompt: tokenUsage.promptTokens, response: tokenUsage.responseTokens, cost: tokenUsage.costUsd } : void 0
|
|
11692
12952
|
}) + "\n");
|
|
@@ -11802,6 +13062,7 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11802
13062
|
ctx.cesar.abortSignal = null;
|
|
11803
13063
|
ctx.cesar.turnId = void 0;
|
|
11804
13064
|
ctx.setActiveAbort(null);
|
|
13065
|
+
releaseSteeringTurn(_turnId);
|
|
11805
13066
|
const queued = ctx.cesar.queue;
|
|
11806
13067
|
if (queued) {
|
|
11807
13068
|
ctx.cesar.queue = null;
|
|
@@ -11833,6 +13094,7 @@ export {
|
|
|
11833
13094
|
joinProblemInput,
|
|
11834
13095
|
runThinkChain,
|
|
11835
13096
|
runDelegate,
|
|
13097
|
+
runPrText,
|
|
11836
13098
|
goalDir,
|
|
11837
13099
|
loadJournal,
|
|
11838
13100
|
isTestFile,
|
|
@@ -11863,15 +13125,20 @@ export {
|
|
|
11863
13125
|
clearPermissionQueue,
|
|
11864
13126
|
clearThinkingBuffer,
|
|
11865
13127
|
handleOutputEvent,
|
|
13128
|
+
yieldToInk,
|
|
13129
|
+
replayCesarHarnessLogs,
|
|
11866
13130
|
parseConfidence,
|
|
11867
13131
|
confidenceBadge,
|
|
11868
13132
|
parseSuggestion,
|
|
11869
|
-
|
|
11870
|
-
|
|
13133
|
+
onSteeringChange,
|
|
13134
|
+
pushSteering,
|
|
13135
|
+
peekSteeringCount,
|
|
13136
|
+
drainLeftoverSteering,
|
|
13137
|
+
clearSteering,
|
|
13138
|
+
handleCesarBrain,
|
|
11871
13139
|
buildCesarSystemPrompt,
|
|
11872
13140
|
saveCesarConversationSnapshot,
|
|
11873
13141
|
resolveCesarBackend,
|
|
11874
|
-
ensureCesarSession
|
|
11875
|
-
handleCesarBrain
|
|
13142
|
+
ensureCesarSession
|
|
11876
13143
|
};
|
|
11877
|
-
//# sourceMappingURL=chunk-
|
|
13144
|
+
//# sourceMappingURL=chunk-24EWX243.js.map
|