@kernlang/agon 0.1.7 → 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-AONHRJRW.js → chunk-24EWX243.js} +1394 -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-RKXVKX25.js → chunk-6SOOHJZQ.js} +75 -13
- package/dist/chunk-6SOOHJZQ.js.map +1 -0
- package/dist/{chunk-6WWOJXG4.js → chunk-FORBHCTM.js} +41 -7
- package/dist/chunk-FORBHCTM.js.map +1 -0
- package/dist/{chunk-WDT5NJOA.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-O2SJ5JIQ.js → forge-ZFCSXC3Z.js} +6 -6
- package/dist/index.js +16659 -14480
- 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-ODAAXWOD.js → update-H3JQXPGO.js} +6 -6
- package/package.json +3 -3
- package/dist/chunk-6WWOJXG4.js.map +0 -1
- package/dist/chunk-AONHRJRW.js.map +0 -1
- package/dist/chunk-BPKY4OF2.js.map +0 -1
- package/dist/chunk-RKXVKX25.js.map +0 -1
- package/dist/chunk-WDT5NJOA.js.map +0 -1
- package/dist/plan-mode-PFLUPGSY.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-O2SJ5JIQ.js.map → forge-ZFCSXC3Z.js.map} +0 -0
- /package/dist/{plan-mode-PFLUPGSY.js.map → plan-mode-I3BZOBFB.js.map} +0 -0
- /package/dist/{src-253BUXEF.js.map → src-WMV62WO7.js.map} +0 -0
- /package/dist/{update-ODAAXWOD.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) {
|
|
@@ -8869,6 +9520,7 @@ RULE 2b \u2014 INTAKE FLOW: Every normal turn includes ROUTING CONTEXT with INTA
|
|
|
8869
9520
|
forge-slice / forge-full: define the target clearly first; forge only the hard slice or full broad task that benefits from competing implementations.
|
|
8870
9521
|
brainstorm / tribunal / campfire / review: call the matching orchestration tool directly when that is the recommended flow.
|
|
8871
9522
|
answer: answer directly.
|
|
9523
|
+
RULE 2c \u2014 MODE IS A CHOICE, NOT A REFLEX (decisions, not retrieval): Before firing ANY orchestration mode, ask what the task actually needs, then pick the mode that fits \u2014 never the one your reflex reaches for. Orchestration (Brainstorm/Tribunal/Campfire/Council) exists to resolve TRADEOFFS and DECISIONS: competing approaches, contested correctness, hard-to-reverse calls. It is NOT a way to FIND an answer you can produce yourself by reading code. If the ask is a lookup, an analysis, or already in hand \u2192 answer directly; convening a panel to discover what one investigation would reveal burns the user's tokens and time. "Find me X / are there issues in Y / how does Z work" = investigate and answer, NOT brainstorm. Escalate by stakes and reversibility: QuickNero = one adversary \xB7 Tribunal = two-side debate \xB7 Council = whole-panel + chair \xB7 Forge = competitive build. When the user explicitly names a mode ("brainstorm it", "tribunal this"), honor it \u2014 this rule governs the turns where YOU own the routing choice.
|
|
8872
9524
|
|
|
8873
9525
|
CRITICAL: You have orchestration modes as DIRECT TOOL CALLS. NEVER use Bash to run CLI commands for orchestration. Call the tools directly:
|
|
8874
9526
|
|
|
@@ -8956,8 +9608,15 @@ RULE 4b \u2014 MODE PURPOSE (don't mix them up \u2014 and don't always default t
|
|
|
8956
9608
|
Your code will be auto-reviewed after implementation \u2014 write carefully the first time.
|
|
8957
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.
|
|
8958
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.
|
|
8959
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.
|
|
8960
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.
|
|
8961
9620
|
|
|
8962
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.
|
|
8963
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.
|
|
@@ -8975,7 +9634,12 @@ RULE 10 \u2014 TURN CLOSURE: End every turn with one clear closing line so the u
|
|
|
8975
9634
|
|
|
8976
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.
|
|
8977
9636
|
|
|
8978
|
-
|
|
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).`;
|
|
8979
9643
|
function buildCesarSystemPrompt(ctx) {
|
|
8980
9644
|
const config = ctx.config;
|
|
8981
9645
|
const cesarCwd = resolveWorkingDir();
|
|
@@ -9007,6 +9671,18 @@ MODES vs ENGINES: the names above are ENGINES \u2014 runnable backends you deleg
|
|
|
9007
9671
|
systemParts.push(`## OPERATING MODE
|
|
9008
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.`);
|
|
9009
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
|
+
}
|
|
9010
9686
|
if (ctx.cesarMemory) {
|
|
9011
9687
|
const memoryCtx = ctx.cesarMemory.toPromptContext();
|
|
9012
9688
|
if (memoryCtx) systemParts.push(memoryCtx);
|
|
@@ -9033,14 +9709,14 @@ ${fragments.join("\n")}`);
|
|
|
9033
9709
|
} else {
|
|
9034
9710
|
const stats = tracker.getStats();
|
|
9035
9711
|
let budgetWarning = "";
|
|
9036
|
-
if (stats.
|
|
9712
|
+
if (stats.meteredCostUsd > 0.5) {
|
|
9037
9713
|
budgetWarning = `
|
|
9038
9714
|
|
|
9039
|
-
URGENT: Planning has spent $${stats.
|
|
9040
|
-
} 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) {
|
|
9041
9717
|
budgetWarning = `
|
|
9042
9718
|
|
|
9043
|
-
WARNING: Planning has spent $${stats.
|
|
9719
|
+
WARNING: Planning has spent $${stats.meteredCostUsd.toFixed(2)}. Wrap up and decide \u2014 call ProposePlan or ExitPlanMode.`;
|
|
9044
9720
|
}
|
|
9045
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.
|
|
9046
9722
|
|
|
@@ -9146,6 +9822,16 @@ function capSnapshotMessageContent(content) {
|
|
|
9146
9822
|
return `${content.slice(0, CESAR_SNAPSHOT_MSG_CHAR_CAP)}
|
|
9147
9823
|
\u2026 [${content.length - CESAR_SNAPSHOT_MSG_CHAR_CAP} chars truncated for Cesar context]`;
|
|
9148
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
|
+
}
|
|
9149
9835
|
function buildCesarConversationSnapshot(session, chatSession) {
|
|
9150
9836
|
const directHistory = session?.getMessageHistory?.() ?? [];
|
|
9151
9837
|
if (directHistory.length > 0) {
|
|
@@ -9196,12 +9882,20 @@ function buildOnToolCall(ctx, toolRegistry, config) {
|
|
|
9196
9882
|
allowedCommands: config.allowedCommands ?? [],
|
|
9197
9883
|
toolPermissions: config.toolPermissions ?? {},
|
|
9198
9884
|
sessionAllowList: getSessionAllowList(),
|
|
9199
|
-
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)
|
|
9200
9894
|
};
|
|
9201
9895
|
return async (name, args, callId) => {
|
|
9202
9896
|
const activePlan = ctx.activePlan;
|
|
9203
9897
|
if (activePlan && ["planning", "awaiting_approval"].includes(activePlan.state)) {
|
|
9204
|
-
const BLOCKED_IN_PLAN = ["Forge", "Pipeline", "Agent", "Goal", "Edit", "Write"];
|
|
9898
|
+
const BLOCKED_IN_PLAN = ["Forge", "Pipeline", "Agent", "Goal", "Edit", "Write", "MultiEdit"];
|
|
9205
9899
|
if (BLOCKED_IN_PLAN.includes(name)) {
|
|
9206
9900
|
return `[BLOCKED] Tool "${name}" is not available in plan mode. Use ProposePlan to propose your execution strategy.`;
|
|
9207
9901
|
}
|
|
@@ -9268,7 +9962,7 @@ ${cleaned}`;
|
|
|
9268
9962
|
}
|
|
9269
9963
|
}
|
|
9270
9964
|
if (name === "ExitPlanMode") {
|
|
9271
|
-
const { handleExitPlanMode } = await import("./plan-mode-
|
|
9965
|
+
const { handleExitPlanMode } = await import("./plan-mode-I3BZOBFB.js");
|
|
9272
9966
|
return "[DELEGATION_BREAK] " + handleExitPlanMode(String(args.reason ?? ""), ctx.cesar?.planDispatch ?? null, ctx);
|
|
9273
9967
|
}
|
|
9274
9968
|
if (name === "ProposePlan") {
|
|
@@ -9291,7 +9985,7 @@ ${cleaned}`;
|
|
|
9291
9985
|
}
|
|
9292
9986
|
}
|
|
9293
9987
|
}
|
|
9294
|
-
const { handleProposePlan } = await import("./plan-mode-
|
|
9988
|
+
const { handleProposePlan } = await import("./plan-mode-I3BZOBFB.js");
|
|
9295
9989
|
const dispatch = ctx.cesar.planDispatch;
|
|
9296
9990
|
if (!dispatch) {
|
|
9297
9991
|
return "[PLAN_ERROR] Internal plan display dispatch unavailable. Retry the plan request so Agon can render the approval panel.";
|
|
@@ -9357,8 +10051,15 @@ ${cleaned}`;
|
|
|
9357
10051
|
return new Promise((resolve4) => {
|
|
9358
10052
|
const d = ctx.cesar.lastDispatch;
|
|
9359
10053
|
if (d) {
|
|
9360
|
-
const cmd =
|
|
9361
|
-
|
|
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 });
|
|
9362
10063
|
} else {
|
|
9363
10064
|
resolve4(true);
|
|
9364
10065
|
}
|
|
@@ -9366,11 +10067,15 @@ ${cleaned}`;
|
|
|
9366
10067
|
}
|
|
9367
10068
|
);
|
|
9368
10069
|
let output = result.result.ok ? result.result.content : result.result.error ?? "Tool execution failed";
|
|
9369
|
-
|
|
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) {
|
|
9370
10072
|
const diag = buildToolErrorDiagnostic(name, args, result.result.error);
|
|
9371
10073
|
const retryKey = `${name}:${JSON.stringify(args)}`;
|
|
9372
10074
|
const used = nativeToolErrorRetries.get(retryKey) ?? 0;
|
|
9373
|
-
|
|
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) {
|
|
9374
10079
|
nativeToolErrorRetries.set(retryKey, 1);
|
|
9375
10080
|
output = `[RETRYABLE_TOOL_ERROR] ${diag}
|
|
9376
10081
|
Retry this ${name} call ONCE with corrected input that matches the tool's schema. Do not narrate before retrying.`;
|
|
@@ -9383,7 +10088,7 @@ Repair retry already used for this exact ${name} input in this turn. Stop retryi
|
|
|
9383
10088
|
}
|
|
9384
10089
|
if (CACHEABLE_TOOLS.has(name)) {
|
|
9385
10090
|
toolResultCache.set(cacheKey, output);
|
|
9386
|
-
} else if (["Edit", "Write", "Bash"].includes(name)) {
|
|
10091
|
+
} else if (["Edit", "Write", "MultiEdit", "Bash"].includes(name)) {
|
|
9387
10092
|
toolResultCache.clear();
|
|
9388
10093
|
ctx.cesar.blockedOnConfidence = null;
|
|
9389
10094
|
}
|
|
@@ -9407,9 +10112,12 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9407
10112
|
const perms = cfg.toolPermissions ?? {};
|
|
9408
10113
|
const allowed = cfg.allowedCommands ?? [];
|
|
9409
10114
|
const mode = cfg.permissionMode ?? "ask";
|
|
9410
|
-
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" };
|
|
9411
10116
|
const agonTool = toolMap[tool.toLowerCase()] ?? tool;
|
|
9412
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);
|
|
9413
10121
|
const turnId = ctx.cesar?.turnId;
|
|
9414
10122
|
const cwd = resolveWorkingDir();
|
|
9415
10123
|
const logApproval = (decision, source, reason, args) => {
|
|
@@ -9444,7 +10152,7 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9444
10152
|
}
|
|
9445
10153
|
};
|
|
9446
10154
|
if (ctx.explorationMode) {
|
|
9447
|
-
const WRITE_TOOLS = ["Edit", "Write", "Bash"];
|
|
10155
|
+
const WRITE_TOOLS = ["Edit", "Write", "MultiEdit", "Bash"];
|
|
9448
10156
|
if (WRITE_TOOLS.includes(agonTool)) {
|
|
9449
10157
|
logApproval("blocked", "policy.exploration", "exploration mode is read-only");
|
|
9450
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.";
|
|
@@ -9460,7 +10168,7 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9460
10168
|
logApproval("blocked", "policy.plan-mode", "plan mode blocks mutating Bash");
|
|
9461
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.";
|
|
9462
10170
|
}
|
|
9463
|
-
const WRITE_TOOLS = ["Edit", "Write"];
|
|
10171
|
+
const WRITE_TOOLS = ["Edit", "Write", "MultiEdit"];
|
|
9464
10172
|
if (WRITE_TOOLS.includes(agonTool)) {
|
|
9465
10173
|
logApproval("blocked", "policy.plan-mode", "plan mode blocks file writes");
|
|
9466
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.";
|
|
@@ -9486,7 +10194,7 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9486
10194
|
}
|
|
9487
10195
|
}
|
|
9488
10196
|
if (!ctx.cesar.confidenceSatisfied) {
|
|
9489
|
-
const WRITE_TOOLS = ["Edit", "Write"];
|
|
10197
|
+
const WRITE_TOOLS = ["Edit", "Write", "MultiEdit"];
|
|
9490
10198
|
if (WRITE_TOOLS.includes(agonTool)) {
|
|
9491
10199
|
const blocks = (ctx.cesar.confidenceBlockCount ?? 0) + 1;
|
|
9492
10200
|
ctx.cesar.confidenceBlockCount = blocks;
|
|
@@ -9499,11 +10207,19 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9499
10207
|
ctx.cesar.blockedOnConfidence = null;
|
|
9500
10208
|
}
|
|
9501
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
|
+
}
|
|
9502
10214
|
if (perm === "deny" || mode === "deny-all") {
|
|
9503
10215
|
logApproval("denied", perm === "deny" ? "settings.toolPermissions" : "settings.permissionMode", perm === "deny" ? `${agonTool} denied in settings` : "permissionMode=deny-all");
|
|
9504
10216
|
return false;
|
|
9505
10217
|
}
|
|
9506
|
-
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") {
|
|
9507
10223
|
const approvalCwd = resolveWorkingDir();
|
|
9508
10224
|
const approvalCache = getProjectFileStateCache(approvalCwd);
|
|
9509
10225
|
const approvalArgs = approvalArgsFromCommand(agonTool, command);
|
|
@@ -9551,7 +10267,14 @@ function buildOnApproval(ctx, engineId) {
|
|
|
9551
10267
|
const dispatch = ctx.cesar.lastDispatch;
|
|
9552
10268
|
if (dispatch) {
|
|
9553
10269
|
logApproval("prompted", "user-prompt", `Cesar (${engineId}) wants to execute`);
|
|
9554
|
-
|
|
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) => {
|
|
9555
10278
|
const wasApproved = typeof approved === "string" ? approved === "y" || approved === "a" : !!approved;
|
|
9556
10279
|
logApproval(wasApproved ? "approved" : "denied", "user-prompt", wasApproved ? "user approved" : "user denied");
|
|
9557
10280
|
resolve4(wasApproved);
|
|
@@ -9595,7 +10318,7 @@ function loadCesarMcpServers(config, cwd) {
|
|
|
9595
10318
|
const resolvedPath = isAbsolute2(rawPath) ? rawPath : resolve3(cwd, rawPath);
|
|
9596
10319
|
let parsed;
|
|
9597
10320
|
try {
|
|
9598
|
-
parsed = JSON.parse(
|
|
10321
|
+
parsed = JSON.parse(readFileSync13(resolvedPath, "utf-8"));
|
|
9599
10322
|
} catch (err) {
|
|
9600
10323
|
throw new Error(`Failed to load Cesar MCP config at ${resolvedPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9601
10324
|
}
|
|
@@ -9619,7 +10342,7 @@ function mcpConfigFingerprint(config) {
|
|
|
9619
10342
|
if (enabled && configPath) {
|
|
9620
10343
|
try {
|
|
9621
10344
|
const resolvedPath = isAbsolute2(configPath) ? configPath : resolve3(resolveWorkingDir(), configPath);
|
|
9622
|
-
mtime = String(
|
|
10345
|
+
mtime = String(statSync5(resolvedPath).mtimeMs);
|
|
9623
10346
|
} catch {
|
|
9624
10347
|
}
|
|
9625
10348
|
}
|
|
@@ -9630,17 +10353,17 @@ function resolveAgonMcpServerPath(fromUrl) {
|
|
|
9630
10353
|
const raw = fromUrl ?? import.meta.url;
|
|
9631
10354
|
const url = raw.startsWith("file:") ? raw : pathToFileURL(raw).href;
|
|
9632
10355
|
const bundledSibling = join12(dirname5(fileURLToPath(url)), "mcp", "index.js");
|
|
9633
|
-
if (
|
|
10356
|
+
if (existsSync13(bundledSibling)) return bundledSibling;
|
|
9634
10357
|
try {
|
|
9635
10358
|
const req = createRequire(url);
|
|
9636
10359
|
const resolved = req.resolve("@kernlang/agon-mcp");
|
|
9637
|
-
if (
|
|
10360
|
+
if (existsSync13(resolved)) return resolved;
|
|
9638
10361
|
} catch {
|
|
9639
10362
|
}
|
|
9640
10363
|
let dir = dirname5(fileURLToPath(url));
|
|
9641
10364
|
for (let i = 0; i < 12; i++) {
|
|
9642
10365
|
const cand = join12(dir, "packages", "mcp", "dist", "index.js");
|
|
9643
|
-
if (
|
|
10366
|
+
if (existsSync13(cand)) return cand;
|
|
9644
10367
|
const parent = dirname5(dir);
|
|
9645
10368
|
if (parent === dir) break;
|
|
9646
10369
|
dir = parent;
|
|
@@ -9759,7 +10482,7 @@ async function ensureCesarSession(ctx) {
|
|
|
9759
10482
|
mkdirSync11(signalDir, { recursive: true });
|
|
9760
10483
|
const sessionSignalId = `cesar-${Date.now()}`;
|
|
9761
10484
|
const mcpServerPath = resolveAgonMcpServerPath();
|
|
9762
|
-
if (!
|
|
10485
|
+
if (!existsSync13(mcpServerPath)) {
|
|
9763
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.`);
|
|
9764
10487
|
}
|
|
9765
10488
|
const mcpEnv = { AGON_SIGNAL_DIR: signalDir, AGON_SESSION_ID: sessionSignalId };
|
|
@@ -9810,9 +10533,177 @@ async function ensureCesarSession(ctx) {
|
|
|
9810
10533
|
await session.start();
|
|
9811
10534
|
ctx.setCesarSession(session);
|
|
9812
10535
|
ctx.cesar.mcpFingerprint = currentMcpFp;
|
|
10536
|
+
ctx.cesar.budgetWarned = false;
|
|
9813
10537
|
return session;
|
|
9814
10538
|
}
|
|
9815
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
|
+
|
|
9816
10707
|
// src/generated/cesar/escalation.ts
|
|
9817
10708
|
import { join as join13 } from "path";
|
|
9818
10709
|
import { mkdirSync as mkdirSync12 } from "fs";
|
|
@@ -9882,18 +10773,94 @@ async function promptDelegation(action, dispatch, hardened, tribunalMode, team)
|
|
|
9882
10773
|
return { approved: answer === "y" };
|
|
9883
10774
|
}
|
|
9884
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
|
+
|
|
9885
10851
|
// src/generated/cesar/brain.ts
|
|
9886
10852
|
async function commitTurnAndDelegate(pendingDel, input, response, cesarEngineId, streaming, dispatch, ctx, telemetry) {
|
|
9887
10853
|
if (streaming) {
|
|
9888
10854
|
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
9889
10855
|
}
|
|
9890
10856
|
if (!streaming) {
|
|
10857
|
+
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
9891
10858
|
dispatch({ type: "spinner-stop" });
|
|
9892
10859
|
}
|
|
9893
10860
|
await yieldToInk();
|
|
9894
10861
|
appendMessage(ctx.chatSession, { role: "user", content: input, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9895
10862
|
appendMessage(ctx.chatSession, { role: "engine", engineId: cesarEngineId, content: response, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9896
|
-
|
|
10863
|
+
recordCesarTurn(ctx, cesarEngineId, input, response);
|
|
9897
10864
|
const delResult = await promptDelegation(pendingDel.action, dispatch, pendingDel.hardened, pendingDel.tribunalMode, pendingDel.team);
|
|
9898
10865
|
const happened = buildWhatHappenedSummary(telemetry ?? {});
|
|
9899
10866
|
if (happened) {
|
|
@@ -9915,12 +10882,13 @@ async function commitTurnAndSuggest(suggestion, input, response, cesarEngineId,
|
|
|
9915
10882
|
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
9916
10883
|
}
|
|
9917
10884
|
if (!streaming) {
|
|
10885
|
+
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
9918
10886
|
dispatch({ type: "spinner-stop" });
|
|
9919
10887
|
}
|
|
9920
10888
|
await yieldToInk();
|
|
9921
10889
|
appendMessage(ctx.chatSession, { role: "user", content: input, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9922
10890
|
appendMessage(ctx.chatSession, { role: "engine", engineId: cesarEngineId, content: response, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9923
|
-
|
|
10891
|
+
recordCesarTurn(ctx, cesarEngineId, input, response);
|
|
9924
10892
|
if (suggestion.rest) {
|
|
9925
10893
|
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: suggestion.rest });
|
|
9926
10894
|
}
|
|
@@ -9957,6 +10925,22 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
9957
10925
|
}
|
|
9958
10926
|
const _toolsUsed = [];
|
|
9959
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
|
+
};
|
|
9960
10944
|
let _toolEventCount = 0;
|
|
9961
10945
|
let _readToolEventCount = 0;
|
|
9962
10946
|
let _toolCallTurns = 0;
|
|
@@ -9966,6 +10950,7 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
9966
10950
|
let _narratedToolStalls = 0;
|
|
9967
10951
|
let _autoToolExecutions = 0;
|
|
9968
10952
|
let _confidenceToolUsed = false;
|
|
10953
|
+
let _liveTodosEmitted = false;
|
|
9969
10954
|
let _actualCesarEngineId = "";
|
|
9970
10955
|
let _actualCesarBackend = "unknown";
|
|
9971
10956
|
let _actualHasNativeTools = false;
|
|
@@ -9978,6 +10963,7 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
9978
10963
|
};
|
|
9979
10964
|
const recordToolUse = (name, source, input2, status) => {
|
|
9980
10965
|
const toolName = String(name || "tool");
|
|
10966
|
+
_noteBashForGate(toolName, input2);
|
|
9981
10967
|
const normalizedSource = source === "eager" ? "xml" : source === "auto" ? "native" : source;
|
|
9982
10968
|
const key = `${normalizedSource}:${toolName}:${String(input2 ?? "").slice(0, 500)}`;
|
|
9983
10969
|
_toolEventCount++;
|
|
@@ -10002,6 +10988,45 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10002
10988
|
input: input2
|
|
10003
10989
|
});
|
|
10004
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
|
+
};
|
|
10005
11030
|
const normalizeConfidenceReasoning = (value) => {
|
|
10006
11031
|
return String(value ?? "").replace(/\s+/g, " ").trim();
|
|
10007
11032
|
};
|
|
@@ -10028,7 +11053,8 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10028
11053
|
xmlToolCalls: _xmlToolCalls,
|
|
10029
11054
|
narratedToolStalls: _narratedToolStalls,
|
|
10030
11055
|
autoToolExecutions: _autoToolExecutions,
|
|
10031
|
-
confidenceToolUsed: _confidenceToolUsed
|
|
11056
|
+
confidenceToolUsed: _confidenceToolUsed,
|
|
11057
|
+
liveTodosEmitted: _liveTodosEmitted
|
|
10032
11058
|
});
|
|
10033
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;
|
|
10034
11060
|
const _isFollowUp = FOLLOWUP_RE.test(input.trim());
|
|
@@ -10064,11 +11090,6 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10064
11090
|
ctx.cesar.queue = null;
|
|
10065
11091
|
ctx.cesar.abortSignal = null;
|
|
10066
11092
|
} else {
|
|
10067
|
-
if (_isFollowUp) {
|
|
10068
|
-
const elapsed = Math.round((Date.now() - busySince) / 1e3);
|
|
10069
|
-
dispatch({ type: "info", message: `Cesar still working\u2026 ${elapsed}s` });
|
|
10070
|
-
return { delegated: false, responded: true };
|
|
10071
|
-
}
|
|
10072
11093
|
const existing = ctx.cesar.queue;
|
|
10073
11094
|
if (existing) {
|
|
10074
11095
|
existing.input = existing.input + "\n\n" + input;
|
|
@@ -10093,6 +11114,7 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10093
11114
|
ctx.cesar.searchNudged = false;
|
|
10094
11115
|
ctx.cesar.turnId = _turnId;
|
|
10095
11116
|
ctx.cesar.planDispatch = dispatch;
|
|
11117
|
+
markSteeringTurn(_turnId);
|
|
10096
11118
|
const _brainStartMs = Date.now();
|
|
10097
11119
|
if (ctx.eventBus) await ctx.eventBus.emit("pre:cesar-brain", { input });
|
|
10098
11120
|
try {
|
|
@@ -10105,6 +11127,33 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10105
11127
|
}
|
|
10106
11128
|
const cesarEngineId = config.cesarEngine ?? config.forgeFixedStarter ?? "claude";
|
|
10107
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
|
+
};
|
|
10108
11157
|
recordTimeline({
|
|
10109
11158
|
event: "turn_start",
|
|
10110
11159
|
engineId: cesarEngineId,
|
|
@@ -10125,9 +11174,26 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10125
11174
|
ctx.setActiveAbort(abort);
|
|
10126
11175
|
ctx.cesar.abortSignal = abort.signal;
|
|
10127
11176
|
ctx.cesar.lastDispatch = dispatch;
|
|
11177
|
+
dispatch({ type: "todos-clear", scope: "live" });
|
|
10128
11178
|
dispatch({ type: "confidence-update", value: null });
|
|
10129
11179
|
dispatch({ type: "spinner-start", message: "Cesar thinking\u2026", color });
|
|
10130
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
|
+
}
|
|
10131
11197
|
let session;
|
|
10132
11198
|
try {
|
|
10133
11199
|
session = await ensureCesarSession(ctx);
|
|
@@ -10160,14 +11226,18 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10160
11226
|
if (freshResult.stdout.trim()) {
|
|
10161
11227
|
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: freshResult.stdout.trim() });
|
|
10162
11228
|
appendMessage(ctx.chatSession, { role: "engine", engineId: cesarEngineId, content: freshResult.stdout.trim(), timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10163
|
-
|
|
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
|
+
}
|
|
10164
11234
|
return { delegated: false, responded: true };
|
|
10165
11235
|
}
|
|
10166
11236
|
appendUserTurnIfAbsent(ctx.chatSession, input);
|
|
10167
11237
|
const brainHint = (freshResult.stderr || "").split("\n")[0].slice(0, 200).trim();
|
|
10168
11238
|
if (brainHint) dispatch({ type: "warning", message: `Cesar (${cesarEngineId}) returned no response: ${brainHint}` });
|
|
10169
11239
|
const health = engineHealth.get(cesarEngineId);
|
|
10170
|
-
if (health && (health.status === "auth-failed" || health.status === "unreachable")) {
|
|
11240
|
+
if (health && (health.status === "auth-failed" || health.status === "unreachable" || health.status === "binary-missing")) {
|
|
10171
11241
|
dispatch({ type: "warning", message: `Engine ${cesarEngineId} marked ${health.status} \u2014 run /cesar to switch to a healthy engine, or /engines to fix credentials.` });
|
|
10172
11242
|
}
|
|
10173
11243
|
return { delegated: false, responded: false };
|
|
@@ -10185,17 +11255,21 @@ async function handleCesarBrain(input, dispatch, ctx, images) {
|
|
|
10185
11255
|
let response = "";
|
|
10186
11256
|
let streaming = false;
|
|
10187
11257
|
let wasStreamed = false;
|
|
11258
|
+
let previewShown = false;
|
|
10188
11259
|
let parsedConfidence = null;
|
|
10189
11260
|
let confidenceParsed = false;
|
|
10190
11261
|
let insideThinkBlock = false;
|
|
10191
11262
|
let suppressXmlToolDisplay = false;
|
|
10192
11263
|
let sawStreamingXmlToolCall = false;
|
|
10193
11264
|
let xmlDisplayHold = "";
|
|
11265
|
+
const stripTodosForDisplay = createTodosDisplayStripper();
|
|
11266
|
+
const stripPreambleForDisplay = createPreambleStripper();
|
|
10194
11267
|
let hadToolActivity = false;
|
|
10195
11268
|
let _engineErrored = false;
|
|
10196
11269
|
let _engineErrorMsg = "";
|
|
10197
11270
|
let secondOpinionPromise = null;
|
|
10198
11271
|
let usedQuickNero = false;
|
|
11272
|
+
let _escalationSuggested = false;
|
|
10199
11273
|
const eagerPromises = [];
|
|
10200
11274
|
let eagerToolCtx = null;
|
|
10201
11275
|
const shouldInterruptForXmlTool = () => {
|
|
@@ -10341,12 +11415,12 @@ ${enrichedInput}`;
|
|
|
10341
11415
|
const signalDir = ctx.cesar.mcpSignalPath ? join14(ctx.cesar.mcpSignalPath, "..") : null;
|
|
10342
11416
|
const processMcpSideChannel = () => {
|
|
10343
11417
|
try {
|
|
10344
|
-
if (!signalDir || !
|
|
11418
|
+
if (!signalDir || !existsSync14(signalDir)) return;
|
|
10345
11419
|
const completions = readdirSync(signalDir).filter((f) => f.includes("-tool-") && f.endsWith(".json"));
|
|
10346
11420
|
for (const f of completions) {
|
|
10347
11421
|
const donePath = join14(signalDir, f);
|
|
10348
11422
|
try {
|
|
10349
|
-
const done = JSON.parse(
|
|
11423
|
+
const done = JSON.parse(readFileSync14(donePath, "utf-8"));
|
|
10350
11424
|
if (done.type !== "tool-completion") continue;
|
|
10351
11425
|
try {
|
|
10352
11426
|
unlinkSync(donePath);
|
|
@@ -10356,21 +11430,21 @@ ${enrichedInput}`;
|
|
|
10356
11430
|
const status = done.status === "error" ? "error" : "done";
|
|
10357
11431
|
const toolInput = typeof done.args === "string" ? done.args : JSON.stringify(done.args ?? {});
|
|
10358
11432
|
recordToolUse(String(done.tool ?? "tool"), "mcp", toolInput, status);
|
|
10359
|
-
|
|
11433
|
+
dispatchToolCall({
|
|
10360
11434
|
type: "tool-call",
|
|
10361
11435
|
engineId: cesarEngineId,
|
|
10362
11436
|
tool: String(done.tool ?? "tool"),
|
|
10363
11437
|
input: toolInput,
|
|
10364
11438
|
status,
|
|
10365
11439
|
output: typeof done.output === "string" ? done.output : void 0
|
|
10366
|
-
});
|
|
11440
|
+
}, { standalone: true });
|
|
10367
11441
|
} catch {
|
|
10368
11442
|
}
|
|
10369
11443
|
}
|
|
10370
11444
|
const files = readdirSync(signalDir).filter((f) => f.includes("-perm-") && !f.includes("-response"));
|
|
10371
11445
|
for (const f of files) {
|
|
10372
11446
|
const reqPath = join14(signalDir, f);
|
|
10373
|
-
const req = JSON.parse(
|
|
11447
|
+
const req = JSON.parse(readFileSync14(reqPath, "utf-8"));
|
|
10374
11448
|
if (req.type !== "permission-request") continue;
|
|
10375
11449
|
if (Date.now() - req.timestamp > 65e3) {
|
|
10376
11450
|
try {
|
|
@@ -10380,7 +11454,7 @@ ${enrichedInput}`;
|
|
|
10380
11454
|
continue;
|
|
10381
11455
|
}
|
|
10382
11456
|
const respPath = reqPath.replace(".json", "-response.json");
|
|
10383
|
-
if (
|
|
11457
|
+
if (existsSync14(respPath)) continue;
|
|
10384
11458
|
const cfg = loadConfig();
|
|
10385
11459
|
const allowed = cfg.allowedCommands ?? [];
|
|
10386
11460
|
const cmdBase = (req.args?.command ?? "").toString().trim().split(/\s+/)[0];
|
|
@@ -10413,12 +11487,30 @@ ${enrichedInput}`;
|
|
|
10413
11487
|
input: reqArgs
|
|
10414
11488
|
});
|
|
10415
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
|
+
}
|
|
10416
11508
|
if (cmdBase && allowed.some((a) => cmdBase.toLowerCase().startsWith(a.toLowerCase()))) {
|
|
10417
11509
|
logMcpApproval("approved", "mcp.allowedCommands", "command matched allowedCommands");
|
|
10418
11510
|
writeFileSync11(respPath, JSON.stringify({ type: "permission-response", id: req.id, approved: true }));
|
|
10419
11511
|
continue;
|
|
10420
11512
|
}
|
|
10421
|
-
if (reqTool === "Edit" || reqTool === "Write") {
|
|
11513
|
+
if (reqTool === "Edit" || reqTool === "Write" || reqTool === "MultiEdit") {
|
|
10422
11514
|
const approvalCwd = resolveWorkingDir();
|
|
10423
11515
|
const approvalCache = getProjectFileStateCache(approvalCwd);
|
|
10424
11516
|
const activePlan = ctx.activePlan;
|
|
@@ -10440,7 +11532,16 @@ ${enrichedInput}`;
|
|
|
10440
11532
|
}
|
|
10441
11533
|
}
|
|
10442
11534
|
logMcpApproval("prompted", "mcp.user-prompt", "Cesar wants to execute");
|
|
10443
|
-
|
|
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) => {
|
|
10444
11545
|
const wasApproved = typeof approved === "string" ? approved === "y" || approved === "a" : approved;
|
|
10445
11546
|
logMcpApproval(wasApproved ? "approved" : "denied", "mcp.user-prompt", wasApproved ? "user approved" : "user denied");
|
|
10446
11547
|
if (typeof approved === "string" && approved === "a" || approved === true) {
|
|
@@ -10469,6 +11570,13 @@ ${enrichedInput}`;
|
|
|
10469
11570
|
const gen = session.send(sendOptions);
|
|
10470
11571
|
for await (const chunk of gen) {
|
|
10471
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
|
+
}
|
|
10472
11580
|
if (chunk.type === "status") {
|
|
10473
11581
|
const statusText = String(chunk.content ?? "");
|
|
10474
11582
|
const _ctxMeta = chunk.metadata ?? {};
|
|
@@ -10479,7 +11587,8 @@ ${enrichedInput}`;
|
|
|
10479
11587
|
used: Number(_ctxMeta.used ?? 0),
|
|
10480
11588
|
limit: Number(_ctxMeta.limit ?? 0),
|
|
10481
11589
|
compacted: Number(_ctxMeta.compacted ?? 0),
|
|
10482
|
-
cached: Number(_ctxMeta.cached ?? 0)
|
|
11590
|
+
cached: Number(_ctxMeta.cached ?? 0),
|
|
11591
|
+
source: typeof _ctxMeta.source === "string" ? _ctxMeta.source : void 0
|
|
10483
11592
|
});
|
|
10484
11593
|
continue;
|
|
10485
11594
|
}
|
|
@@ -10506,7 +11615,7 @@ ${enrichedInput}`;
|
|
|
10506
11615
|
dispatch({ type: "spinner-update", message: `Cesar: ${toolName}\u2026` });
|
|
10507
11616
|
if (meta.input && STREAM_ORCH.has(toolName)) {
|
|
10508
11617
|
if (cesarFastPath) {
|
|
10509
|
-
|
|
11618
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: `Blocked by fast-${fastPathMode}: do the direct work without orchestration.` });
|
|
10510
11619
|
continue;
|
|
10511
11620
|
}
|
|
10512
11621
|
if (!ctx.cesar.pendingDelegation) {
|
|
@@ -10514,35 +11623,35 @@ ${enrichedInput}`;
|
|
|
10514
11623
|
ctx.eventBus?.emit("cesar:delegation", { action: toolName.toLowerCase(), source: `stream-${toolStatus}` }).catch(() => {
|
|
10515
11624
|
});
|
|
10516
11625
|
}
|
|
10517
|
-
|
|
11626
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "done", output: typeof meta.output === "string" ? meta.output : void 0 });
|
|
10518
11627
|
continue;
|
|
10519
11628
|
}
|
|
10520
11629
|
if (toolStatus === "done") {
|
|
10521
|
-
|
|
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 });
|
|
10522
11631
|
} else if (toolStatus === "native") {
|
|
10523
|
-
|
|
11632
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "running" }, { toolCallId: meta.toolCallId });
|
|
10524
11633
|
} else if (toolStatus === "running" && meta.input && toolRegistry && !ctx.cesar.hasNativeTools) {
|
|
10525
11634
|
if (ctx.cesar.pendingDelegation) {
|
|
10526
|
-
|
|
11635
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "done" }, { toolCallId: meta.toolCallId });
|
|
10527
11636
|
continue;
|
|
10528
11637
|
}
|
|
10529
11638
|
const EAGER_ORCH = STREAM_ORCH;
|
|
10530
11639
|
if (EAGER_ORCH.has(toolName)) {
|
|
10531
11640
|
if (cesarFastPath) {
|
|
10532
|
-
|
|
11641
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: `Blocked by fast-${fastPathMode}: do the direct work without orchestration.` });
|
|
10533
11642
|
continue;
|
|
10534
11643
|
}
|
|
10535
11644
|
ctx.cesar.pendingDelegation = extractDelegation(toolName, meta.input ?? {});
|
|
10536
11645
|
ctx.eventBus?.emit("cesar:delegation", { action: toolName.toLowerCase(), source: "stream" }).catch(() => {
|
|
10537
11646
|
});
|
|
10538
|
-
|
|
11647
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "done" });
|
|
10539
11648
|
continue;
|
|
10540
11649
|
}
|
|
10541
|
-
|
|
11650
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "running" }, { toolCallId: meta.toolCallId, eager: true });
|
|
10542
11651
|
if (!eagerToolCtx) eagerToolCtx = createEagerToolContext(ctx, config, abort.signal, dispatch);
|
|
10543
11652
|
eagerPromises.push(executeEagerTool(toolName, meta, toolRegistry, eagerToolCtx, dispatch, cesarEngineId));
|
|
10544
11653
|
} else {
|
|
10545
|
-
|
|
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 });
|
|
10546
11655
|
}
|
|
10547
11656
|
continue;
|
|
10548
11657
|
}
|
|
@@ -10556,6 +11665,7 @@ ${enrichedInput}`;
|
|
|
10556
11665
|
const errBody = _errFull.slice(0, 200) || "unknown stream error";
|
|
10557
11666
|
const _deterministic = /\b(?:400|401|403|404)\b|not found|unauthorized|invalid api key|authentication|no such (?:route|endpoint)/i.test(_errFull);
|
|
10558
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 });
|
|
10559
11669
|
clearInterval(heartbeat);
|
|
10560
11670
|
processMcpSideChannel();
|
|
10561
11671
|
if (mcpWatcherInterval) clearInterval(mcpWatcherInterval);
|
|
@@ -10623,19 +11733,29 @@ ${enrichedInput}`;
|
|
|
10623
11733
|
cleanFirst = response.replace(/<think>[\s\S]*/gi, "");
|
|
10624
11734
|
}
|
|
10625
11735
|
}
|
|
11736
|
+
emitPreamble(response);
|
|
11737
|
+
cleanFirst = stripPreambleForDisplay(cleanFirst);
|
|
10626
11738
|
if (!ctx.cesar.hasNativeTools) {
|
|
10627
11739
|
const split = splitBeforeToolMarkup(cleanFirst);
|
|
10628
11740
|
cleanFirst = split.visible;
|
|
10629
11741
|
if (split.hasToolMarkup) suppressXmlToolDisplay = true;
|
|
11742
|
+
} else {
|
|
11743
|
+
cleanFirst = stripTodosForDisplay(cleanFirst);
|
|
10630
11744
|
}
|
|
10631
11745
|
if (cleanFirst.trim()) dispatch({ type: "streaming-chunk", engineId: cesarEngineId, chunk: cleanFirst });
|
|
10632
11746
|
} else {
|
|
10633
11747
|
response += chunk.content;
|
|
10634
11748
|
noteXmlToolDetected(true);
|
|
10635
11749
|
let displayChunk = chunk.content;
|
|
11750
|
+
emitPreamble(response);
|
|
11751
|
+
displayChunk = stripPreambleForDisplay(displayChunk);
|
|
11752
|
+
if (!displayChunk) continue;
|
|
10636
11753
|
if (!ctx.cesar.hasNativeTools) {
|
|
10637
11754
|
displayChunk = takeXmlSafeDisplayChunk(displayChunk);
|
|
10638
11755
|
if (!displayChunk) continue;
|
|
11756
|
+
} else {
|
|
11757
|
+
displayChunk = stripTodosForDisplay(displayChunk);
|
|
11758
|
+
if (!displayChunk && !insideThinkBlock) continue;
|
|
10639
11759
|
}
|
|
10640
11760
|
if (insideThinkBlock) {
|
|
10641
11761
|
if (displayChunk.includes("</think>")) {
|
|
@@ -10669,10 +11789,21 @@ ${enrichedInput}`;
|
|
|
10669
11789
|
}
|
|
10670
11790
|
}
|
|
10671
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
|
+
}
|
|
10672
11797
|
const trailingXmlSafe = takeXmlSafeDisplayChunk("", true);
|
|
10673
11798
|
if (streaming && trailingXmlSafe.trim()) {
|
|
10674
11799
|
dispatch({ type: "streaming-chunk", engineId: cesarEngineId, chunk: trailingXmlSafe });
|
|
10675
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
|
+
}
|
|
10676
11807
|
} catch (err) {
|
|
10677
11808
|
clearInterval(heartbeat);
|
|
10678
11809
|
processMcpSideChannel();
|
|
@@ -10683,6 +11814,7 @@ ${enrichedInput}`;
|
|
|
10683
11814
|
dispatch({ type: "warning", message: `Cesar stream error (partial response preserved): ${(err.message ?? "").slice(0, 80)}` });
|
|
10684
11815
|
} else {
|
|
10685
11816
|
dispatch({ type: "warning", message: "Cesar session error \u2014 will restart on next message" });
|
|
11817
|
+
if (previewShown) dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
10686
11818
|
return { delegated: false, responded: false, decisionReason: "stream-error" };
|
|
10687
11819
|
}
|
|
10688
11820
|
}
|
|
@@ -10691,6 +11823,7 @@ ${enrichedInput}`;
|
|
|
10691
11823
|
if (mcpWatcherInterval) clearInterval(mcpWatcherInterval);
|
|
10692
11824
|
if (abort.signal.aborted) {
|
|
10693
11825
|
dispatch({ type: "spinner-stop" });
|
|
11826
|
+
if (previewShown) dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
10694
11827
|
const elapsed = Math.round((Date.now() - _turnStart) / 1e3);
|
|
10695
11828
|
if (elapsed >= cesarTimeout) {
|
|
10696
11829
|
dispatch({ type: "warning", message: `Cesar timed out after ${elapsed}s. Try a simpler question, or use /forge for complex tasks.` });
|
|
@@ -10703,6 +11836,8 @@ ${enrichedInput}`;
|
|
|
10703
11836
|
if (ctx.cesar.hasNativeTools) {
|
|
10704
11837
|
response = response.replace(/<tool\s+name="[^"]+">[\s\S]*?<\/tool>/g, "").trim();
|
|
10705
11838
|
}
|
|
11839
|
+
response = emitPreamble(response);
|
|
11840
|
+
response = emitLiveTodos(response);
|
|
10706
11841
|
if (eagerPromises.length > 0 && !ctx.cesar.hasNativeTools && session.alive && !abort.signal.aborted) {
|
|
10707
11842
|
dispatch({ type: "spinner-start", message: `Cesar: awaiting ${eagerPromises.length} tool result${eagerPromises.length > 1 ? "s" : ""}\u2026`, color });
|
|
10708
11843
|
const eagerResults = await Promise.all(eagerPromises);
|
|
@@ -10715,7 +11850,8 @@ ${enrichedInput}`;
|
|
|
10715
11850
|
const failedTools = eagerFailedToolNames(eagerResults);
|
|
10716
11851
|
const repairUsed = [];
|
|
10717
11852
|
const repairResults = [];
|
|
10718
|
-
const
|
|
11853
|
+
const _steerMsg = drainSteeringIntoSend(formatted, "xml");
|
|
11854
|
+
const contGen = session.send({ message: _steerMsg, signal: abort.signal, ..._lastDrainedSteerImages ? { images: _lastDrainedSteerImages } : {} });
|
|
10719
11855
|
for await (const chunk of contGen) {
|
|
10720
11856
|
if (chunk.type === "text") continuation += chunk.content;
|
|
10721
11857
|
if (chunk.type === "tool_call") {
|
|
@@ -10732,9 +11868,9 @@ ${enrichedInput}`;
|
|
|
10732
11868
|
continue;
|
|
10733
11869
|
}
|
|
10734
11870
|
if (toolStatus !== "running" && toolStatus !== "native") {
|
|
10735
|
-
|
|
11871
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: toolStatus, output: typeof meta.output === "string" ? meta.output : void 0 });
|
|
10736
11872
|
} else if (failedTools.includes(toolName)) {
|
|
10737
|
-
|
|
11873
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: toolName, input: toolInput, status: "error", output: "Repair retry already used for this tool in this turn." });
|
|
10738
11874
|
}
|
|
10739
11875
|
}
|
|
10740
11876
|
if (chunk.type === "done" || chunk.type === "error") break;
|
|
@@ -10753,7 +11889,7 @@ ${enrichedInput}`;
|
|
|
10753
11889
|
const meta = chunk.metadata ?? {};
|
|
10754
11890
|
const toolName = chunk.content || "tool";
|
|
10755
11891
|
const toolInput = typeof meta.input === "string" ? meta.input : meta.input ? JSON.stringify(meta.input) : "";
|
|
10756
|
-
|
|
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." });
|
|
10757
11893
|
}
|
|
10758
11894
|
if (chunk.type === "done" || chunk.type === "error") break;
|
|
10759
11895
|
}
|
|
@@ -10761,7 +11897,7 @@ ${enrichedInput}`;
|
|
|
10761
11897
|
}
|
|
10762
11898
|
}
|
|
10763
11899
|
dispatch({ type: "spinner-stop" });
|
|
10764
|
-
if (continuation.trim()) response = continuation.trim();
|
|
11900
|
+
if (continuation.trim()) response = emitLiveTodos(emitPreamble(continuation.trim()));
|
|
10765
11901
|
}
|
|
10766
11902
|
}
|
|
10767
11903
|
if (!confidenceParsed && response) {
|
|
@@ -10797,22 +11933,22 @@ ${enrichedInput}`;
|
|
|
10797
11933
|
if (!ctx.cesar.pendingDelegation && ctx.cesar.mcpSignalPath) {
|
|
10798
11934
|
try {
|
|
10799
11935
|
const signalPath = ctx.cesar.mcpSignalPath;
|
|
10800
|
-
if (
|
|
10801
|
-
const signals = JSON.parse(
|
|
11936
|
+
if (existsSync14(signalPath)) {
|
|
11937
|
+
const signals = JSON.parse(readFileSync14(signalPath, "utf-8"));
|
|
10802
11938
|
unlinkSync(signalPath);
|
|
10803
11939
|
for (const signal of Array.isArray(signals) ? signals : [signals]) {
|
|
10804
11940
|
if (!signal.timestamp || Date.now() - signal.timestamp >= 6e4) continue;
|
|
10805
11941
|
if (cesarFastPath && FAST_PATH_BLOCKED_TOOLS.includes(signal.tool)) {
|
|
10806
11942
|
const toolInput = JSON.stringify(signal.args ?? {});
|
|
10807
11943
|
recordToolUse(signal.tool, "mcp", toolInput, "error");
|
|
10808
|
-
|
|
11944
|
+
dispatchToolCall({
|
|
10809
11945
|
type: "tool-call",
|
|
10810
11946
|
engineId: cesarEngineId,
|
|
10811
11947
|
tool: signal.tool,
|
|
10812
11948
|
input: toolInput,
|
|
10813
11949
|
status: "error",
|
|
10814
11950
|
output: `Blocked by fast-${fastPathMode}: do the direct work without orchestration.`
|
|
10815
|
-
});
|
|
11951
|
+
}, { standalone: true });
|
|
10816
11952
|
continue;
|
|
10817
11953
|
}
|
|
10818
11954
|
if (signal.tool === "ReportConfidence") {
|
|
@@ -10842,17 +11978,17 @@ ${enrichedInput}`;
|
|
|
10842
11978
|
recordToolUse("ProposePlan", "mcp", JSON.stringify(signal.args ?? {}), "done");
|
|
10843
11979
|
const activePlan = ctx.activePlan;
|
|
10844
11980
|
if (activePlan && ["planning", "running", "paused"].includes(activePlan.state)) {
|
|
10845
|
-
|
|
11981
|
+
dispatchToolCall({
|
|
10846
11982
|
type: "tool-call",
|
|
10847
11983
|
engineId: cesarEngineId,
|
|
10848
11984
|
tool: "ProposePlan",
|
|
10849
11985
|
input: JSON.stringify(signal.args ?? {}),
|
|
10850
11986
|
status: "error",
|
|
10851
11987
|
output: "A Cesar plan is already active; nested plans are blocked. Resume or cancel the current plan before proposing another."
|
|
10852
|
-
});
|
|
11988
|
+
}, { standalone: true });
|
|
10853
11989
|
continue;
|
|
10854
11990
|
}
|
|
10855
|
-
const { handleProposePlan } = await import("./plan-mode-
|
|
11991
|
+
const { handleProposePlan } = await import("./plan-mode-I3BZOBFB.js");
|
|
10856
11992
|
const planDispatch = ctx.cesar.planDispatch ?? dispatch;
|
|
10857
11993
|
if (planDispatch) {
|
|
10858
11994
|
try {
|
|
@@ -10865,11 +12001,11 @@ ${enrichedInput}`;
|
|
|
10865
12001
|
}
|
|
10866
12002
|
} else if (signal.tool === "ExitPlanMode") {
|
|
10867
12003
|
recordToolUse("ExitPlanMode", "mcp", JSON.stringify(signal.args ?? {}), "done");
|
|
10868
|
-
const { handleExitPlanMode } = await import("./plan-mode-
|
|
12004
|
+
const { handleExitPlanMode } = await import("./plan-mode-I3BZOBFB.js");
|
|
10869
12005
|
const planDispatch = ctx.cesar.planDispatch ?? dispatch;
|
|
10870
12006
|
try {
|
|
10871
12007
|
const exitResult = handleExitPlanMode(String(signal.args?.reason ?? ""), planDispatch, ctx);
|
|
10872
|
-
|
|
12008
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: "ExitPlanMode", input: JSON.stringify(signal.args ?? {}), status: "done", output: exitResult }, { standalone: true });
|
|
10873
12009
|
} catch (err) {
|
|
10874
12010
|
console.warn(`[agon] ExitPlanMode via MCP signal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10875
12011
|
}
|
|
@@ -10922,7 +12058,14 @@ ${enrichedInput}`;
|
|
|
10922
12058
|
// Phase 1: investigate only — mutating tools blocked until after escalation check
|
|
10923
12059
|
blockedTools: cesarFastPath ? FAST_PATH_BLOCKED_TOOLS : void 0,
|
|
10924
12060
|
blockedToolMessage: cesarFastPath ? `Blocked by fast-${fastPathMode}: do the direct work without orchestration.` : void 0,
|
|
10925
|
-
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)
|
|
10926
12069
|
};
|
|
10927
12070
|
const _lastToolInputs = {};
|
|
10928
12071
|
const xmlToolBridge = createStreamBridge(dispatch, { initialEngineId: cesarEngineId });
|
|
@@ -10968,12 +12111,13 @@ ${enrichedInput}`;
|
|
|
10968
12111
|
async (message) => {
|
|
10969
12112
|
if (ctx.cesar.pendingDelegation) return "[Delegation pending]";
|
|
10970
12113
|
if (!session.alive || abort.signal.aborted) return "";
|
|
12114
|
+
const sendMessage = drainSteeringIntoSend(message, "xml");
|
|
10971
12115
|
dispatch({ type: "spinner-start", message: "Cesar processing results\u2026", color });
|
|
10972
12116
|
_engineErrored = false;
|
|
10973
12117
|
_engineErrorMsg = "";
|
|
10974
12118
|
let nextResponse = "";
|
|
10975
12119
|
const gen = session.send({
|
|
10976
|
-
message,
|
|
12120
|
+
message: sendMessage,
|
|
10977
12121
|
signal: abort.signal,
|
|
10978
12122
|
toolLoopBaseBudget: cesarFastPath ? fastPathBaseBudget : void 0,
|
|
10979
12123
|
toolLoopMaxBudget: cesarFastPath ? fastPathMaxBudget : void 0
|
|
@@ -11045,7 +12189,7 @@ ${enrichedInput}`;
|
|
|
11045
12189
|
const lastInput = _lastToolInputs[tool] ?? "{}";
|
|
11046
12190
|
let command = "";
|
|
11047
12191
|
try {
|
|
11048
|
-
command =
|
|
12192
|
+
command = renderToolPermissionCommand(tool, JSON.parse(lastInput));
|
|
11049
12193
|
} catch {
|
|
11050
12194
|
command = lastInput;
|
|
11051
12195
|
}
|
|
@@ -11072,7 +12216,7 @@ ${enrichedInput}`;
|
|
|
11072
12216
|
try {
|
|
11073
12217
|
const activePlan = ctx.activePlan;
|
|
11074
12218
|
if (activePlan && ["planning", "running", "paused"].includes(activePlan.state)) {
|
|
11075
|
-
|
|
12219
|
+
dispatchToolCall({
|
|
11076
12220
|
type: "tool-call",
|
|
11077
12221
|
engineId: cesarEngineId,
|
|
11078
12222
|
tool: "ProposePlan",
|
|
@@ -11081,7 +12225,7 @@ ${enrichedInput}`;
|
|
|
11081
12225
|
output: "A Cesar plan is already active; nested plans are blocked. Resume or cancel the current plan before proposing another."
|
|
11082
12226
|
});
|
|
11083
12227
|
} else {
|
|
11084
|
-
const { handleProposePlan } = await import("./plan-mode-
|
|
12228
|
+
const { handleProposePlan } = await import("./plan-mode-I3BZOBFB.js");
|
|
11085
12229
|
const planDispatch = ctx.cesar.planDispatch ?? dispatch;
|
|
11086
12230
|
const plan = await handleProposePlan(ppArgs, planDispatch, ctx);
|
|
11087
12231
|
if (ctx.setActivePlan) ctx.setActivePlan(plan);
|
|
@@ -11105,10 +12249,10 @@ ${enrichedInput}`;
|
|
|
11105
12249
|
const epArgs = ctx.cesar._exitPlanModeArgs;
|
|
11106
12250
|
delete ctx.cesar._exitPlanModeArgs;
|
|
11107
12251
|
try {
|
|
11108
|
-
const { handleExitPlanMode } = await import("./plan-mode-
|
|
12252
|
+
const { handleExitPlanMode } = await import("./plan-mode-I3BZOBFB.js");
|
|
11109
12253
|
const planDispatch = ctx.cesar.planDispatch ?? dispatch;
|
|
11110
12254
|
const exitResult = handleExitPlanMode(String(epArgs?.reason ?? ""), planDispatch, ctx);
|
|
11111
|
-
|
|
12255
|
+
dispatchToolCall({ type: "tool-call", engineId: cesarEngineId, tool: "ExitPlanMode", input: JSON.stringify(epArgs ?? {}), status: "done", output: exitResult });
|
|
11112
12256
|
} catch (err) {
|
|
11113
12257
|
console.warn(`[agon] ExitPlanMode via tool loop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11114
12258
|
}
|
|
@@ -11134,7 +12278,7 @@ ${enrichedInput}`;
|
|
|
11134
12278
|
confidenceParsed = true;
|
|
11135
12279
|
}
|
|
11136
12280
|
}
|
|
11137
|
-
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;
|
|
11138
12282
|
if (ctx.cesar.quickNeroRequested) ctx.cesar.quickNeroRequested = false;
|
|
11139
12283
|
if (shouldQuickNero) {
|
|
11140
12284
|
const quickNeroConfidence = parsedConfidence;
|
|
@@ -11174,7 +12318,7 @@ ${qnResult.challengeText}` : ""
|
|
|
11174
12318
|
appendMessage(ctx.chatSession, { role: "engine", engineId: ch.engineId, content: ch.content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11175
12319
|
}
|
|
11176
12320
|
}
|
|
11177
|
-
|
|
12321
|
+
recordCesarTurn(ctx, cesarEngineId, input, response);
|
|
11178
12322
|
return {
|
|
11179
12323
|
mode: escalationAction,
|
|
11180
12324
|
delegated: true,
|
|
@@ -11188,12 +12332,24 @@ ${qnResult.challengeText}` : ""
|
|
|
11188
12332
|
};
|
|
11189
12333
|
}
|
|
11190
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
|
+
}
|
|
11191
12342
|
const investigationResponse = response;
|
|
11192
12343
|
let mutationStallForced = false;
|
|
11193
12344
|
if (!mutationDeferred && !inPlanMode && !ctx.cesar.pendingDelegation && (hadToolActivity || ranToolLoop) && session.alive && !abort.signal.aborted && detectMutationIntentStall(response)) {
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
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
|
+
}
|
|
11197
12353
|
}
|
|
11198
12354
|
if (mutationDeferred && toolRegistry && session.alive && !abort.signal.aborted) {
|
|
11199
12355
|
toolCtx.readOnlyMode = false;
|
|
@@ -11213,10 +12369,11 @@ ${qnResult.challengeText}` : ""
|
|
|
11213
12369
|
async (message) => {
|
|
11214
12370
|
if (ctx.cesar.pendingDelegation) return "[Delegation pending]";
|
|
11215
12371
|
if (!session.alive || abort.signal.aborted) return "";
|
|
12372
|
+
const sendMessage = drainSteeringIntoSend(message, "exec");
|
|
11216
12373
|
dispatch({ type: "spinner-start", message: "Cesar executing\u2026", color });
|
|
11217
12374
|
let nextResponse = "";
|
|
11218
12375
|
const gen = session.send({
|
|
11219
|
-
message,
|
|
12376
|
+
message: sendMessage,
|
|
11220
12377
|
signal: abort.signal,
|
|
11221
12378
|
toolLoopBaseBudget: cesarFastPath ? fastPathBaseBudget : void 0,
|
|
11222
12379
|
toolLoopMaxBudget: cesarFastPath ? fastPathMaxBudget : void 0
|
|
@@ -11292,7 +12449,14 @@ ${qnResult.challengeText}` : ""
|
|
|
11292
12449
|
dispatch({ type: "spinner-stop" });
|
|
11293
12450
|
}
|
|
11294
12451
|
}
|
|
11295
|
-
|
|
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) {
|
|
11296
12460
|
dispatch({ type: "warning", message: "Cesar claimed a job was running but never dispatched one \u2014 grounding..." });
|
|
11297
12461
|
dispatch({ type: "spinner-start", message: "Cesar grounding\u2026", color });
|
|
11298
12462
|
try {
|
|
@@ -11404,12 +12568,11 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11404
12568
|
dispatch({ type: "spinner-stop" });
|
|
11405
12569
|
}
|
|
11406
12570
|
}
|
|
11407
|
-
const _AUTO_CONT_WRITE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
11408
12571
|
const _AUTO_CONT_LOOP_ORCH = /* @__PURE__ */ new Set(["Forge", "Brainstorm", "Tribunal", "Campfire", "Pipeline", "Review", "Agent", "Goal", "Conquer"]);
|
|
11409
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;
|
|
11410
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;
|
|
11411
12574
|
const _detectTurnState = (resp, baselineToolCount) => {
|
|
11412
|
-
const wroteSinceBaseline = _toolsUsed.slice(baselineToolCount).some((t) =>
|
|
12575
|
+
const wroteSinceBaseline = _toolsUsed.slice(baselineToolCount).some((t) => isWriteToolName(t));
|
|
11413
12576
|
if (findTrailingUserQuestion(resp)) return "asks-user";
|
|
11414
12577
|
if (wroteSinceBaseline && !_AUTO_CONT_CONTINUE_RE.test(resp)) return "done";
|
|
11415
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);
|
|
@@ -11418,6 +12581,18 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11418
12581
|
if (_AUTO_CONT_READONLY_DONE_RE.test(resp)) return "done";
|
|
11419
12582
|
return "stuck";
|
|
11420
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
|
+
};
|
|
11421
12596
|
const _buildContToolLoopOpts = () => ({
|
|
11422
12597
|
onToolCall: (name, inp) => {
|
|
11423
12598
|
_lastToolInputs[name] = JSON.stringify(inp);
|
|
@@ -11452,7 +12627,7 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11452
12627
|
const lastInput = _lastToolInputs[tool] ?? "{}";
|
|
11453
12628
|
let command = "";
|
|
11454
12629
|
try {
|
|
11455
|
-
command =
|
|
12630
|
+
command = renderToolPermissionCommand(tool, JSON.parse(lastInput));
|
|
11456
12631
|
} catch {
|
|
11457
12632
|
command = lastInput;
|
|
11458
12633
|
}
|
|
@@ -11476,8 +12651,85 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11476
12651
|
let _consecutiveNoProgress = 0;
|
|
11477
12652
|
while (_continuations < MAX_CONTINUATIONS && session.alive && !abort.signal.aborted && !ctx.cesar.pendingDelegation) {
|
|
11478
12653
|
const state = _detectTurnState(response, _loopStartToolCount);
|
|
11479
|
-
if (state === "asks-user"
|
|
11480
|
-
|
|
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));
|
|
11481
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;
|
|
11482
12734
|
const filesMentioned = Array.from(new Set(Array.from(response.matchAll(filePathRe), (m) => m[1]))).slice(0, 3);
|
|
11483
12735
|
let nudge;
|
|
@@ -11513,7 +12765,7 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11513
12765
|
break;
|
|
11514
12766
|
}
|
|
11515
12767
|
dispatch({ type: "spinner-stop" });
|
|
11516
|
-
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()));
|
|
11517
12769
|
if (_engineErrored) {
|
|
11518
12770
|
const _reason = _engineErrorMsg || "no response";
|
|
11519
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.` });
|
|
@@ -11534,12 +12786,13 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11534
12786
|
async (message) => {
|
|
11535
12787
|
if (ctx.cesar.pendingDelegation) return "[Delegation pending]";
|
|
11536
12788
|
if (!session.alive || abort.signal.aborted) return "";
|
|
12789
|
+
const sendMessage = drainSteeringIntoSend(message, "auto");
|
|
11537
12790
|
dispatch({ type: "spinner-start", message: "Cesar executing\u2026", color });
|
|
11538
12791
|
_engineErrored = false;
|
|
11539
12792
|
_engineErrorMsg = "";
|
|
11540
12793
|
let nextResp = "";
|
|
11541
12794
|
const gen = session.send({
|
|
11542
|
-
message,
|
|
12795
|
+
message: sendMessage,
|
|
11543
12796
|
signal: abort.signal,
|
|
11544
12797
|
toolLoopBaseBudget: cesarFastPath ? fastPathBaseBudget : void 0,
|
|
11545
12798
|
toolLoopMaxBudget: cesarFastPath ? fastPathMaxBudget : void 0
|
|
@@ -11636,6 +12889,9 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11636
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.`
|
|
11637
12890
|
});
|
|
11638
12891
|
}
|
|
12892
|
+
if (previewShown && !streaming) {
|
|
12893
|
+
dispatch({ type: "streaming-end", engineId: cesarEngineId });
|
|
12894
|
+
}
|
|
11639
12895
|
if (!streaming && response && !ranToolLoop && !wasStreamed) {
|
|
11640
12896
|
dispatch({ type: "spinner-stop" });
|
|
11641
12897
|
dispatch({ type: "engine-block", engineId: cesarEngineId, color, content: response });
|
|
@@ -11654,7 +12910,7 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11654
12910
|
appendMessage(ctx.chatSession, { role: "engine", engineId: ch.engineId, content: ch.content, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11655
12911
|
}
|
|
11656
12912
|
}
|
|
11657
|
-
const tokenUsage =
|
|
12913
|
+
const tokenUsage = recordCesarTurn(ctx, cesarEngineId, input, response);
|
|
11658
12914
|
try {
|
|
11659
12915
|
const tracePath = join14(RUNS_DIR, "cesar-trace.jsonl");
|
|
11660
12916
|
const taskClass = classifyTask(input);
|
|
@@ -11686,6 +12942,11 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11686
12942
|
confidenceToolUsed: toolTelemetry.confidenceToolUsed,
|
|
11687
12943
|
hasNativeTools: toolTelemetry.hasNativeTools,
|
|
11688
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,
|
|
11689
12950
|
confidence: parsedConfidence,
|
|
11690
12951
|
tokens: tokenUsage ? { prompt: tokenUsage.promptTokens, response: tokenUsage.responseTokens, cost: tokenUsage.costUsd } : void 0
|
|
11691
12952
|
}) + "\n");
|
|
@@ -11801,6 +13062,7 @@ ${cleanFinalAnswer}` : cleanFinalAnswer;
|
|
|
11801
13062
|
ctx.cesar.abortSignal = null;
|
|
11802
13063
|
ctx.cesar.turnId = void 0;
|
|
11803
13064
|
ctx.setActiveAbort(null);
|
|
13065
|
+
releaseSteeringTurn(_turnId);
|
|
11804
13066
|
const queued = ctx.cesar.queue;
|
|
11805
13067
|
if (queued) {
|
|
11806
13068
|
ctx.cesar.queue = null;
|
|
@@ -11832,6 +13094,7 @@ export {
|
|
|
11832
13094
|
joinProblemInput,
|
|
11833
13095
|
runThinkChain,
|
|
11834
13096
|
runDelegate,
|
|
13097
|
+
runPrText,
|
|
11835
13098
|
goalDir,
|
|
11836
13099
|
loadJournal,
|
|
11837
13100
|
isTestFile,
|
|
@@ -11862,15 +13125,20 @@ export {
|
|
|
11862
13125
|
clearPermissionQueue,
|
|
11863
13126
|
clearThinkingBuffer,
|
|
11864
13127
|
handleOutputEvent,
|
|
13128
|
+
yieldToInk,
|
|
13129
|
+
replayCesarHarnessLogs,
|
|
11865
13130
|
parseConfidence,
|
|
11866
13131
|
confidenceBadge,
|
|
11867
13132
|
parseSuggestion,
|
|
11868
|
-
|
|
11869
|
-
|
|
13133
|
+
onSteeringChange,
|
|
13134
|
+
pushSteering,
|
|
13135
|
+
peekSteeringCount,
|
|
13136
|
+
drainLeftoverSteering,
|
|
13137
|
+
clearSteering,
|
|
13138
|
+
handleCesarBrain,
|
|
11870
13139
|
buildCesarSystemPrompt,
|
|
11871
13140
|
saveCesarConversationSnapshot,
|
|
11872
13141
|
resolveCesarBackend,
|
|
11873
|
-
ensureCesarSession
|
|
11874
|
-
handleCesarBrain
|
|
13142
|
+
ensureCesarSession
|
|
11875
13143
|
};
|
|
11876
|
-
//# sourceMappingURL=chunk-
|
|
13144
|
+
//# sourceMappingURL=chunk-24EWX243.js.map
|