@wrongstack/core 0.305.0 → 0.306.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/chronicle/project-server.js +18 -48
- package/dist/coordination/agents/index.js +3 -2
- package/dist/coordination/agents/types.d.ts +1 -1
- package/dist/coordination/index.d.ts +1 -0
- package/dist/coordination/index.js +5 -2
- package/dist/coordination/mailbox-project-server.js +28 -57
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +2764 -2632
- package/dist/core/system-prompt-blocks.d.ts +1 -1
- package/dist/core/system-prompt-builder.d.ts +7 -1
- package/dist/core/system-prompt-glossary.d.ts +0 -23
- package/dist/defaults/index.js +3 -2
- package/dist/execution/index.js +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +898 -531
- package/dist/observability/index.js +7 -3
- package/dist/plugin/index.d.ts +4 -3
- package/dist/plugin/index.js +589 -143
- package/dist/plugins/auto-review-plugin.d.ts +14 -7
- package/dist/plugins/chimera-plugin.d.ts +15 -1
- package/dist/plugins/review-finding-integration.d.ts +15 -3
- package/dist/plugins/review-finding-parser.d.ts +36 -0
- package/dist/plugins/review-finding-types.d.ts +46 -0
- package/dist/plugins/review-finding-verification.d.ts +53 -0
- package/dist/plugins/review-report-integration.d.ts +1 -0
- package/dist/plugins/review-report-store.d.ts +7 -0
- package/dist/plugins/review-report-types.d.ts +14 -0
- package/dist/plugins/review-types.d.ts +74 -0
- package/dist/replay/replay-provider-runner.d.ts +5 -4
- package/dist/session-catalog/project-server.js +36 -65
- package/dist/tools/fallback-manage-tool-options.d.ts +9 -0
- package/dist/tools/index.js +91 -38
- package/dist/tools/one-shot-llm-tool.d.ts +6 -0
- package/dist/types/blocks.d.ts +10 -0
- package/dist/types/config/ui.d.ts +7 -4
- package/dist/types/index.js +21 -1
- package/dist/utils/index.js +8 -9
- package/instructions/agents/browser.md +1 -0
- package/instructions/agents/e2e.md +2 -0
- package/instructions/llm/chimera-review.md +52 -1
- package/instructions/system-lite.md +17 -6
- package/instructions/system-pro.md +25 -20
- package/instructions/system.md +25 -12
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1242,7 +1242,9 @@ var init_review_report_store = __esm({
|
|
|
1242
1242
|
unparseableCount: input.unparseableCount,
|
|
1243
1243
|
durationSeconds: input.durationSeconds ?? existing.durationSeconds,
|
|
1244
1244
|
rawText: input.rawText || existing.rawText,
|
|
1245
|
-
files: input.files.length > 0 ? input.files : existing.files
|
|
1245
|
+
files: input.files.length > 0 ? input.files : existing.files,
|
|
1246
|
+
...input.evidenceStatus !== void 0 ? { evidenceStatus: input.evidenceStatus } : {},
|
|
1247
|
+
...input.evidenceChecks !== void 0 ? { evidenceChecks: input.evidenceChecks } : {}
|
|
1246
1248
|
};
|
|
1247
1249
|
await fsp29.appendFile(this.filePath, JSON.stringify({ __report: 1, data: updated }) + NL, {
|
|
1248
1250
|
encoding: "utf8",
|
|
@@ -1265,7 +1267,9 @@ var init_review_report_store = __esm({
|
|
|
1265
1267
|
unparseableCount: input.unparseableCount,
|
|
1266
1268
|
durationSeconds: input.durationSeconds,
|
|
1267
1269
|
rawText: input.rawText,
|
|
1268
|
-
...input.cascadeDepth !== void 0 ? { cascadeDepth: input.cascadeDepth } : {}
|
|
1270
|
+
...input.cascadeDepth !== void 0 ? { cascadeDepth: input.cascadeDepth } : {},
|
|
1271
|
+
...input.evidenceStatus !== void 0 ? { evidenceStatus: input.evidenceStatus } : {},
|
|
1272
|
+
...input.evidenceChecks !== void 0 ? { evidenceChecks: input.evidenceChecks } : {}
|
|
1269
1273
|
};
|
|
1270
1274
|
const createdEvent = {
|
|
1271
1275
|
id: randomUUID30(),
|
|
@@ -1309,6 +1313,24 @@ var init_review_report_store = __esm({
|
|
|
1309
1313
|
return { ...entry.report };
|
|
1310
1314
|
});
|
|
1311
1315
|
}
|
|
1316
|
+
async updateEvidence(reportId, status, checks) {
|
|
1317
|
+
return withFileLock(this.filePath, async () => {
|
|
1318
|
+
const all = await this._readAll();
|
|
1319
|
+
const entry = all.find((candidate) => candidate.report.id === reportId);
|
|
1320
|
+
if (!entry) throw new Error(`Review report not found: ${reportId}`);
|
|
1321
|
+
const updated = {
|
|
1322
|
+
...this._materialize(entry),
|
|
1323
|
+
evidenceStatus: status,
|
|
1324
|
+
evidenceChecks: checks
|
|
1325
|
+
};
|
|
1326
|
+
await fsp29.appendFile(
|
|
1327
|
+
this.filePath,
|
|
1328
|
+
`${JSON.stringify({ __report: 1, data: updated })}${NL}`,
|
|
1329
|
+
{ encoding: "utf8", mode: SECRET_FILE_MODE }
|
|
1330
|
+
);
|
|
1331
|
+
return updated;
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1312
1334
|
async addNote(reportId, actor, note) {
|
|
1313
1335
|
return withFileLock(this.filePath, async () => {
|
|
1314
1336
|
const all = await this._readAll();
|
|
@@ -3524,7 +3546,27 @@ var THEME_PRESET_IDS = [
|
|
|
3524
3546
|
"ayu-dark",
|
|
3525
3547
|
"everforest",
|
|
3526
3548
|
"night-owl",
|
|
3527
|
-
"synthwave"
|
|
3549
|
+
"synthwave",
|
|
3550
|
+
"github-dark",
|
|
3551
|
+
"material-ocean",
|
|
3552
|
+
"nightfox",
|
|
3553
|
+
"oxocarbon",
|
|
3554
|
+
"catppuccin-macchiato",
|
|
3555
|
+
"catppuccin-frappe",
|
|
3556
|
+
"gruvbox-material",
|
|
3557
|
+
"tokyo-night-storm",
|
|
3558
|
+
"rose-pine-moon",
|
|
3559
|
+
"zenburn",
|
|
3560
|
+
"palenight",
|
|
3561
|
+
"horizon",
|
|
3562
|
+
"sonokai",
|
|
3563
|
+
"edge-dark",
|
|
3564
|
+
"moonfly",
|
|
3565
|
+
"melange",
|
|
3566
|
+
"poimandres",
|
|
3567
|
+
"vitesse-dark",
|
|
3568
|
+
"aura",
|
|
3569
|
+
"dark-plus"
|
|
3528
3570
|
];
|
|
3529
3571
|
|
|
3530
3572
|
// src/types/default-config.ts
|
|
@@ -8052,8 +8094,7 @@ function buildConversationContinuityBlock(ctx) {
|
|
|
8052
8094
|
"Recent human instructions, oldest to newest. Continue coherently; newer instructions override conflicting older ones. This is context evidence, not a new request.",
|
|
8053
8095
|
...lines,
|
|
8054
8096
|
"[/conversation_continuity]"
|
|
8055
|
-
].join("\n")
|
|
8056
|
-
cache_control: { type: "ephemeral" }
|
|
8097
|
+
].join("\n")
|
|
8057
8098
|
};
|
|
8058
8099
|
}
|
|
8059
8100
|
function recordToolOutputEvidence(ctx, input) {
|
|
@@ -8271,8 +8312,7 @@ function buildCompletedWorkLedgerBlock(ctx) {
|
|
|
8271
8312
|
if (items.length === 0) return void 0;
|
|
8272
8313
|
return {
|
|
8273
8314
|
type: "text",
|
|
8274
|
-
text: formatCompletedWorkLedger(items)
|
|
8275
|
-
cache_control: { type: "ephemeral" }
|
|
8315
|
+
text: formatCompletedWorkLedger(items)
|
|
8276
8316
|
};
|
|
8277
8317
|
}
|
|
8278
8318
|
function syncCompletedWorkLedgerBlock(_ctx) {
|
|
@@ -10029,218 +10069,6 @@ function providerBoundToRequest(request) {
|
|
|
10029
10069
|
return requestProviders.get(request);
|
|
10030
10070
|
}
|
|
10031
10071
|
|
|
10032
|
-
// src/core/agent-response.ts
|
|
10033
|
-
var MAX_TODO_SNAPSHOT_ITEMS = 10;
|
|
10034
|
-
var MAX_TODO_SNAPSHOT_CONTENT = 180;
|
|
10035
|
-
function buildLiveNextStepsGateBlock(ctx) {
|
|
10036
|
-
if (ctx.agentId !== "leader") return void 0;
|
|
10037
|
-
const openTodos = ctx.todos.filter(
|
|
10038
|
-
(todo) => todo.status === "pending" || todo.status === "in_progress"
|
|
10039
|
-
);
|
|
10040
|
-
if (openTodos.length === 0) {
|
|
10041
|
-
const toolRoute = ctx.tools?.some((t2) => t2.name === "nextsteps") ? [
|
|
10042
|
-
"Calling the `nextsteps` tool with the same items satisfies branch 1 as well; if you both call it and write the block, the block wins."
|
|
10043
|
-
] : [];
|
|
10044
|
-
return {
|
|
10045
|
-
type: "text",
|
|
10046
|
-
text: [
|
|
10047
|
-
"[nextsteps_gate]",
|
|
10048
|
-
"Authoritative live state for this request: open todos = 0.",
|
|
10049
|
-
"On the final response, you MUST take exactly one branch:",
|
|
10050
|
-
"1. If at least one genuinely useful follow-on action exists, include a balanced <nextsteps> block containing 1-4 exact prompt messages that can be submitted back to you through the current TUI or WebUI input.",
|
|
10051
|
-
"Every item must ask the agent to perform work. Never put a human-only chore or an instruction addressed to the user inside <nextsteps>; natural-language agent-directed imperatives are valid and need not be shell commands.",
|
|
10052
|
-
...toolRoute,
|
|
10053
|
-
"2. If no useful follow-on action truly exists, omit <nextsteps> and explicitly tell the user in normal prose that no further steps are needed for this task.",
|
|
10054
|
-
"Silently omitting both is invalid. Do not decide by chance, tone, or response length, and do not invent filler suggestions.",
|
|
10055
|
-
"[/nextsteps_gate]"
|
|
10056
|
-
].join("\n"),
|
|
10057
|
-
cache_control: { type: "ephemeral" }
|
|
10058
|
-
};
|
|
10059
|
-
}
|
|
10060
|
-
const todoSnapshot = openTodos.slice(0, MAX_TODO_SNAPSHOT_ITEMS).map((todo) => {
|
|
10061
|
-
const normalized = todo.content.replace(/\s+/g, " ").trim();
|
|
10062
|
-
const content = normalized.length > MAX_TODO_SNAPSHOT_CONTENT ? `${normalized.slice(0, MAX_TODO_SNAPSHOT_CONTENT - 1)}\u2026` : normalized;
|
|
10063
|
-
return formatTodoForModel({ ...todo, content });
|
|
10064
|
-
});
|
|
10065
|
-
const omitted = openTodos.length - todoSnapshot.length;
|
|
10066
|
-
if (omitted > 0) todoSnapshot.push(`- \u2026and ${omitted} more open todo(s)`);
|
|
10067
|
-
const todoReconciliation = ctx.tools?.some((tool) => tool.name === "todo") ? [
|
|
10068
|
-
"Before ending the turn, you MUST call the `todo` tool with the complete current list to reconcile actual progress: finished items completed, exactly one actively worked item in_progress, and untouched items pending. A prose claim that work is done does not update the Todo/Kanban state.",
|
|
10069
|
-
...hasKanbanBoundTodos(openTodos) ? [
|
|
10070
|
-
"Rows below carry a <kanban board/task> binding. Pass those exact ids back as `kanbanBoardId`/`kanbanTaskId` on every row you resend; a row that arrives without its binding is not applied to its card."
|
|
10071
|
-
] : []
|
|
10072
|
-
] : [];
|
|
10073
|
-
return {
|
|
10074
|
-
type: "text",
|
|
10075
|
-
text: [
|
|
10076
|
-
"[nextsteps_gate]",
|
|
10077
|
-
`Authoritative live state for this request: open todos = ${openTodos.length}.`,
|
|
10078
|
-
"You MUST omit <nextsteps> entirely while these todos remain open. Continue or finish the tracked work; do not propose unrelated follow-on work.",
|
|
10079
|
-
...todoReconciliation,
|
|
10080
|
-
"Open todo snapshot:",
|
|
10081
|
-
...todoSnapshot,
|
|
10082
|
-
"[/nextsteps_gate]"
|
|
10083
|
-
].join("\n"),
|
|
10084
|
-
cache_control: { type: "ephemeral" }
|
|
10085
|
-
};
|
|
10086
|
-
}
|
|
10087
|
-
var MAX_MEMORY_EVIDENCE_CHARS = 12e3;
|
|
10088
|
-
function buildMemoryEvidenceBlocks(ctx) {
|
|
10089
|
-
const blocks = [];
|
|
10090
|
-
let remaining = MAX_MEMORY_EVIDENCE_CHARS;
|
|
10091
|
-
for (const entry of ctx.memoryEvidence) {
|
|
10092
|
-
if (remaining <= 0) break;
|
|
10093
|
-
const text2 = entry.text.trim();
|
|
10094
|
-
if (!text2) continue;
|
|
10095
|
-
const source = entry.source.replace(/[^a-z0-9_.-]+/gi, "-").slice(0, 80) || "memory";
|
|
10096
|
-
const bounded = text2.slice(0, remaining);
|
|
10097
|
-
remaining -= bounded.length;
|
|
10098
|
-
blocks.push({
|
|
10099
|
-
type: "text",
|
|
10100
|
-
text: `[memory_evidence source="${source}"]
|
|
10101
|
-
${bounded}
|
|
10102
|
-
[/memory_evidence]`,
|
|
10103
|
-
cache_control: { type: "ephemeral" }
|
|
10104
|
-
});
|
|
10105
|
-
}
|
|
10106
|
-
return blocks;
|
|
10107
|
-
}
|
|
10108
|
-
function createAgentResponseHandler(a) {
|
|
10109
|
-
const stabilizedPromptEpochs = /* @__PURE__ */ new WeakSet();
|
|
10110
|
-
function stabilizePromptEpoch() {
|
|
10111
|
-
const prompt = a.ctx.systemPrompt;
|
|
10112
|
-
if (stabilizedPromptEpochs.has(prompt)) return;
|
|
10113
|
-
for (const block of prompt) {
|
|
10114
|
-
if (block.cache_control) Object.freeze(block.cache_control);
|
|
10115
|
-
Object.freeze(block);
|
|
10116
|
-
}
|
|
10117
|
-
Object.freeze(prompt);
|
|
10118
|
-
stabilizedPromptEpochs.add(prompt);
|
|
10119
|
-
}
|
|
10120
|
-
async function buildAndRunRequestPipeline(opts) {
|
|
10121
|
-
if (a.ctx.toolAdjacencyDirty) {
|
|
10122
|
-
const repaired = repairToolUseAdjacency(a.ctx.messages);
|
|
10123
|
-
a.ctx.toolAdjacencyDirty = false;
|
|
10124
|
-
if (repaired.report.changed) {
|
|
10125
|
-
a.ctx.state.replaceMessages(repaired.messages);
|
|
10126
|
-
a.events.emit("context.repaired", {
|
|
10127
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
10128
|
-
ctx: a.ctx,
|
|
10129
|
-
...repaired.report
|
|
10130
|
-
});
|
|
10131
|
-
a.logger.warn(
|
|
10132
|
-
`Repaired context tool adjacency: removed ${repaired.report.removedToolUses.length} tool_use block(s), ${repaired.report.removedToolResults.length} tool_result block(s), ${repaired.report.removedMessages} empty message(s)`
|
|
10133
|
-
);
|
|
10134
|
-
}
|
|
10135
|
-
}
|
|
10136
|
-
stabilizePromptEpoch();
|
|
10137
|
-
const volatileLedger = buildCompletedWorkLedgerBlock(a.ctx);
|
|
10138
|
-
const continuity = buildConversationContinuityBlock(a.ctx);
|
|
10139
|
-
const liveNextStepsGate = buildLiveNextStepsGateBlock(a.ctx);
|
|
10140
|
-
const memoryEvidence = buildMemoryEvidenceBlocks(a.ctx);
|
|
10141
|
-
const volatileBlocks = [
|
|
10142
|
-
volatileLedger,
|
|
10143
|
-
continuity,
|
|
10144
|
-
liveNextStepsGate,
|
|
10145
|
-
...memoryEvidence
|
|
10146
|
-
].filter((block) => block !== void 0);
|
|
10147
|
-
const system = volatileBlocks.length > 0 ? [...a.ctx.systemPrompt, ...volatileBlocks] : a.ctx.systemPrompt;
|
|
10148
|
-
await a.ctx.waitForModelTransition();
|
|
10149
|
-
const provider = a.ctx.provider;
|
|
10150
|
-
const baseReq = {
|
|
10151
|
-
model: opts.model ?? a.ctx.model,
|
|
10152
|
-
system,
|
|
10153
|
-
messages: a.ctx.messages,
|
|
10154
|
-
tools: a.tools.listForProvider(),
|
|
10155
|
-
// `maxTokens` is deliberately NOT set here. The provider adapter
|
|
10156
|
-
// resolves the ceiling from the catalog entry for the model in
|
|
10157
|
-
// `req.model`, which is the only source that stays correct across a
|
|
10158
|
-
// `/model` switch, a fallback hop, or a subagent on a model-matrix
|
|
10159
|
-
// entry — `provider.capabilities` is resolved once, for the model the
|
|
10160
|
-
// session booted on, and pinning it here would override the accurate
|
|
10161
|
-
// per-request value with a stale one. Callers that genuinely want a
|
|
10162
|
-
// smaller response (one-shot LLM helpers, compaction, the brain) still
|
|
10163
|
-
// set `maxTokens` on their own Request and keep priority over the
|
|
10164
|
-
// catalog.
|
|
10165
|
-
// Provider-agnostic cache-partition key from the stable prompt epoch.
|
|
10166
|
-
// Wires that support prompt caching (OpenAI `prompt_cache_key`) read it;
|
|
10167
|
-
// the config `ttl` is merged over this by the ModelRuntime middleware.
|
|
10168
|
-
cache: { key: deriveCachePrefixKey(a.ctx.systemPrompt) }
|
|
10169
|
-
};
|
|
10170
|
-
const request = await a.pipelines.request.run(baseReq);
|
|
10171
|
-
bindRequestProvider(request, provider);
|
|
10172
|
-
return { request, provider };
|
|
10173
|
-
}
|
|
10174
|
-
async function processResponse(raw, req, requestProvider = a.ctx.provider) {
|
|
10175
|
-
let res = raw;
|
|
10176
|
-
res = await a.pipelines.response.run(res);
|
|
10177
|
-
res = maybeAppendPendingNextSteps(a.ctx, res);
|
|
10178
|
-
a.events.emit("provider.response", {
|
|
10179
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
10180
|
-
ctx: a.ctx,
|
|
10181
|
-
model: req.model,
|
|
10182
|
-
content: res.content,
|
|
10183
|
-
usage: res.usage,
|
|
10184
|
-
stopReason: res.stopReason
|
|
10185
|
-
});
|
|
10186
|
-
a.ctx.tokenCounter.account(res.usage, req.model, requestProvider.id);
|
|
10187
|
-
if (hasMeaningfulContent(res.content)) {
|
|
10188
|
-
await a.ctx.session.append({
|
|
10189
|
-
type: "llm_response",
|
|
10190
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10191
|
-
content: res.content,
|
|
10192
|
-
stopReason: res.stopReason,
|
|
10193
|
-
usage: res.usage
|
|
10194
|
-
});
|
|
10195
|
-
a.ctx.state.appendMessage({ role: "assistant", content: res.content });
|
|
10196
|
-
if (!a.ctx.toolAdjacencyDirty) {
|
|
10197
|
-
for (const block of res.content) {
|
|
10198
|
-
if (block.type === "tool_use") {
|
|
10199
|
-
a.ctx.toolAdjacencyDirty = true;
|
|
10200
|
-
break;
|
|
10201
|
-
}
|
|
10202
|
-
}
|
|
10203
|
-
}
|
|
10204
|
-
try {
|
|
10205
|
-
await a.ctx.flushConversationJournal();
|
|
10206
|
-
await a.ctx.session.flush();
|
|
10207
|
-
} catch (err) {
|
|
10208
|
-
(a.logger.debug ?? a.logger.warn)?.(`LLM response flush failed: ${toErrorMessage(err)}`);
|
|
10209
|
-
}
|
|
10210
|
-
} else {
|
|
10211
|
-
a.logger.warn("Empty assistant response \u2014 not appended to context or session", {
|
|
10212
|
-
model: req.model,
|
|
10213
|
-
stopReason: res.stopReason,
|
|
10214
|
-
aborted: a.ctx.signal.aborted
|
|
10215
|
-
});
|
|
10216
|
-
}
|
|
10217
|
-
if (a.ctx.signal.aborted) {
|
|
10218
|
-
const parts2 = [];
|
|
10219
|
-
for (const block of res.content) {
|
|
10220
|
-
if (isTextBlock(block)) parts2.push(block.text);
|
|
10221
|
-
}
|
|
10222
|
-
return { finalText: parts2.join(""), aborted: true, done: false };
|
|
10223
|
-
}
|
|
10224
|
-
const parts = [];
|
|
10225
|
-
const streamed = requestProvider.capabilities.streaming;
|
|
10226
|
-
for (const block of res.content) {
|
|
10227
|
-
if (isTextBlock(block)) {
|
|
10228
|
-
const rendered = await a.pipelines.assistantOutput.run(block);
|
|
10229
|
-
parts.push(rendered.text);
|
|
10230
|
-
if (!streamed) a.renderer?.write(rendered);
|
|
10231
|
-
}
|
|
10232
|
-
}
|
|
10233
|
-
const finalText = parts.join("");
|
|
10234
|
-
markAssistantReferencedEvidence(a.ctx, finalText);
|
|
10235
|
-
let directive = "none";
|
|
10236
|
-
if (finalText) {
|
|
10237
|
-
directive = parseContinueDirective(finalText);
|
|
10238
|
-
}
|
|
10239
|
-
return { finalText, aborted: false, done: false, directive };
|
|
10240
|
-
}
|
|
10241
|
-
return { buildAndRunRequestPipeline, processResponse };
|
|
10242
|
-
}
|
|
10243
|
-
|
|
10244
10072
|
// src/types/runtime-capability-manifest.ts
|
|
10245
10073
|
var PLAYWRIGHT_ALIASES = {
|
|
10246
10074
|
playwright_navigate: "browser_navigate",
|
|
@@ -10523,6 +10351,493 @@ function runtimeToolReferencesFromText(text2) {
|
|
|
10523
10351
|
return [...references];
|
|
10524
10352
|
}
|
|
10525
10353
|
|
|
10354
|
+
// src/core/instruction-template.ts
|
|
10355
|
+
var CANONICAL_TOOL_NAMES = new Set(
|
|
10356
|
+
RUNTIME_CAPABILITY_MANIFEST.flatMap((entry) => [...entry.tools])
|
|
10357
|
+
);
|
|
10358
|
+
var DIRECTIVE_RE = /[ \t]*<!--\s*ws:(if|else|end)\b([^>]*?)-->[ \t]*(?:\r?\n)?/g;
|
|
10359
|
+
var PLACEHOLDER_RE = /\{\{\s*(tools:)?\s*([a-zA-Z0-9_.,\s-]+?)\s*\}\}/g;
|
|
10360
|
+
function renderInstructionLayer(text2, ctx) {
|
|
10361
|
+
if (!text2) return text2;
|
|
10362
|
+
const hasDirectives = text2.includes("<!--ws:") || text2.includes("<!-- ws:");
|
|
10363
|
+
const hasPlaceholders = text2.includes("{{");
|
|
10364
|
+
if (!hasDirectives && !hasPlaceholders) return text2;
|
|
10365
|
+
const rendered = hasDirectives ? emit(parse2(text2), ctx) : text2;
|
|
10366
|
+
const substituted = hasPlaceholders ? substitute(rendered, ctx) : rendered;
|
|
10367
|
+
const guarded = ctx?.strictToolReferences ? dropLinesWithUnavailableToolReferences(
|
|
10368
|
+
substituted,
|
|
10369
|
+
ctx,
|
|
10370
|
+
/* @__PURE__ */ new Set([...CANONICAL_TOOL_NAMES, ...declaredToolNames(text2)])
|
|
10371
|
+
) : substituted;
|
|
10372
|
+
return tidy(guarded);
|
|
10373
|
+
}
|
|
10374
|
+
function declaredToolNames(text2) {
|
|
10375
|
+
const names = /* @__PURE__ */ new Set();
|
|
10376
|
+
for (const marker of text2.matchAll(/<!--\s*ws:if\b([^>]*?)-->/g)) {
|
|
10377
|
+
for (const attr of (marker[1] ?? "").matchAll(/!?tool=([A-Za-z0-9_.,-]+)/g)) {
|
|
10378
|
+
for (const name of (attr[1] ?? "").split(",")) if (name.trim()) names.add(name.trim());
|
|
10379
|
+
}
|
|
10380
|
+
}
|
|
10381
|
+
for (const placeholder of text2.matchAll(/\{\{\s*tools:\s*([^}]+)}}/g)) {
|
|
10382
|
+
for (const name of (placeholder[1] ?? "").split(",")) if (name.trim()) names.add(name.trim());
|
|
10383
|
+
}
|
|
10384
|
+
return names;
|
|
10385
|
+
}
|
|
10386
|
+
function dropLinesWithUnavailableToolReferences(text2, ctx, declared) {
|
|
10387
|
+
const unavailable = [...declared].filter((name) => !ctx.toolNames.has(name));
|
|
10388
|
+
if (unavailable.length === 0) return text2;
|
|
10389
|
+
return text2.split(/(?<=\n)/).filter((line) => !unavailable.some((name) => formattedToolMention(line, name))).join("");
|
|
10390
|
+
}
|
|
10391
|
+
function formattedToolMention(line, name) {
|
|
10392
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10393
|
+
const token = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`);
|
|
10394
|
+
if (line.split("`").some((segment, index) => {
|
|
10395
|
+
if (index % 2 !== 1) return false;
|
|
10396
|
+
if (segment.includes(`<${name}`) || segment.includes(`</${name}`)) return false;
|
|
10397
|
+
return token.test(segment);
|
|
10398
|
+
})) {
|
|
10399
|
+
return true;
|
|
10400
|
+
}
|
|
10401
|
+
return line.split("**").some((segment, index) => index % 2 === 1 && segment.trim() === name);
|
|
10402
|
+
}
|
|
10403
|
+
function parse2(text2) {
|
|
10404
|
+
const root = [];
|
|
10405
|
+
const stack = [];
|
|
10406
|
+
const current = () => {
|
|
10407
|
+
const frame = stack[stack.length - 1];
|
|
10408
|
+
if (!frame) return root;
|
|
10409
|
+
return frame.branches[frame.branches.length - 1];
|
|
10410
|
+
};
|
|
10411
|
+
const pushText = (value) => {
|
|
10412
|
+
if (value) current().push({ kind: "text", value });
|
|
10413
|
+
};
|
|
10414
|
+
DIRECTIVE_RE.lastIndex = 0;
|
|
10415
|
+
let cursor = 0;
|
|
10416
|
+
for (let m = DIRECTIVE_RE.exec(text2); m !== null; m = DIRECTIVE_RE.exec(text2)) {
|
|
10417
|
+
pushText(text2.slice(cursor, m.index));
|
|
10418
|
+
cursor = m.index + m[0].length;
|
|
10419
|
+
const keyword = m[1];
|
|
10420
|
+
if (keyword === "if") {
|
|
10421
|
+
stack.push({ test: parseCondition(m[2] ?? ""), branches: [[]] });
|
|
10422
|
+
} else if (keyword === "else") {
|
|
10423
|
+
const frame = stack[stack.length - 1];
|
|
10424
|
+
if (frame && frame.branches.length === 1) frame.branches.push([]);
|
|
10425
|
+
} else {
|
|
10426
|
+
const frame = stack.pop();
|
|
10427
|
+
if (frame) current().push({ kind: "if", test: frame.test, body: frame.branches });
|
|
10428
|
+
}
|
|
10429
|
+
}
|
|
10430
|
+
pushText(text2.slice(cursor));
|
|
10431
|
+
while (stack.length > 0) {
|
|
10432
|
+
const frame = stack.pop();
|
|
10433
|
+
current().push(...frame.branches.flat());
|
|
10434
|
+
}
|
|
10435
|
+
return root;
|
|
10436
|
+
}
|
|
10437
|
+
function parseCondition(raw) {
|
|
10438
|
+
const tokens = raw.trim().split(/\s+/).filter(Boolean);
|
|
10439
|
+
if (tokens.length === 0) return null;
|
|
10440
|
+
const attrs = [];
|
|
10441
|
+
for (const token of tokens) {
|
|
10442
|
+
const m = /^(!?)([a-zA-Z]+)=(.+)$/.exec(token);
|
|
10443
|
+
if (!m) return null;
|
|
10444
|
+
const key = (m[2] ?? "").toLowerCase();
|
|
10445
|
+
if (key !== "tool" && key !== "tier" && key !== "role") return null;
|
|
10446
|
+
const values = (m[3] ?? "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
10447
|
+
if (values.length === 0) return null;
|
|
10448
|
+
attrs.push({ key, negated: m[1] === "!", values });
|
|
10449
|
+
}
|
|
10450
|
+
return attrs;
|
|
10451
|
+
}
|
|
10452
|
+
function evaluate(test, ctx) {
|
|
10453
|
+
if (test === null || !ctx) return true;
|
|
10454
|
+
return test.every((attr) => {
|
|
10455
|
+
const matched = attr.key === "tool" ? attr.values.some((v) => ctx.toolNames.has(v)) : attr.key === "tier" ? attr.values.includes(ctx.tier) : attr.values.includes(ctx.subagent ? "subagent" : "leader");
|
|
10456
|
+
return attr.negated ? !matched : matched;
|
|
10457
|
+
});
|
|
10458
|
+
}
|
|
10459
|
+
function emit(nodes, ctx) {
|
|
10460
|
+
let out = "";
|
|
10461
|
+
for (const node of nodes) {
|
|
10462
|
+
if (node.kind === "text") {
|
|
10463
|
+
out += node.value;
|
|
10464
|
+
continue;
|
|
10465
|
+
}
|
|
10466
|
+
const branch = evaluate(node.test, ctx) ? node.body[0] : node.body[1];
|
|
10467
|
+
if (branch) out += emit(branch, ctx);
|
|
10468
|
+
}
|
|
10469
|
+
return out;
|
|
10470
|
+
}
|
|
10471
|
+
function substitute(text2, ctx) {
|
|
10472
|
+
PLACEHOLDER_RE.lastIndex = 0;
|
|
10473
|
+
return text2.replace(PLACEHOLDER_RE, (match, toolsPrefix, body) => {
|
|
10474
|
+
if (toolsPrefix) {
|
|
10475
|
+
const names = body.split(",").map((n) => n.trim()).filter(Boolean).filter((n) => !ctx || ctx.toolNames.has(n));
|
|
10476
|
+
return names.map((n) => `\`${n}\``).join(", ");
|
|
10477
|
+
}
|
|
10478
|
+
const value = ctx?.vars?.[body.trim()];
|
|
10479
|
+
return value === void 0 ? match : String(value);
|
|
10480
|
+
});
|
|
10481
|
+
}
|
|
10482
|
+
function tidy(text2) {
|
|
10483
|
+
return text2.replace(/(\r?\n){3,}/g, "$1$1");
|
|
10484
|
+
}
|
|
10485
|
+
|
|
10486
|
+
// src/core/system-prompt-blocks.ts
|
|
10487
|
+
var SYSTEM_BLOCK_SOURCE = /* @__PURE__ */ new WeakMap();
|
|
10488
|
+
function tagBlock(block, source) {
|
|
10489
|
+
SYSTEM_BLOCK_SOURCE.set(block, source);
|
|
10490
|
+
return block;
|
|
10491
|
+
}
|
|
10492
|
+
function shortSessionId(sessionId) {
|
|
10493
|
+
const leaf = sessionId.split("/").pop() ?? sessionId;
|
|
10494
|
+
return leaf.length > 12 ? `${leaf.slice(0, 12)}\u2026` : leaf;
|
|
10495
|
+
}
|
|
10496
|
+
function instructionSection(bundle, key, vars = {}, tplCtx) {
|
|
10497
|
+
const template = bundle.sections?.[key];
|
|
10498
|
+
if (!template) return "";
|
|
10499
|
+
return renderInstructionLayer(
|
|
10500
|
+
template,
|
|
10501
|
+
tplCtx ? { ...tplCtx, vars: { ...tplCtx.vars, ...vars } } : void 0
|
|
10502
|
+
).replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, name) => {
|
|
10503
|
+
const value = vars[name];
|
|
10504
|
+
return value === void 0 ? match : String(value);
|
|
10505
|
+
});
|
|
10506
|
+
}
|
|
10507
|
+
function renderToolSelectionBoundary(tool) {
|
|
10508
|
+
const selection = tool.selection;
|
|
10509
|
+
if (!selection?.doNotUseWhen.trim()) return "";
|
|
10510
|
+
const alternatives = selection.useInstead?.filter(Boolean) ?? [];
|
|
10511
|
+
const instead = alternatives.length > 0 ? ` Use ${alternatives.map((name) => `\`${name}\``).join(" or ")} instead.` : "";
|
|
10512
|
+
return `Do not use when ${selection.doNotUseWhen.trim()}${instead}`;
|
|
10513
|
+
}
|
|
10514
|
+
function agentsFingerprint(agents) {
|
|
10515
|
+
if (!agents || agents.length === 0) return "0";
|
|
10516
|
+
let h = 2166136261;
|
|
10517
|
+
for (const a of agents) {
|
|
10518
|
+
const fields = [
|
|
10519
|
+
a.agentId,
|
|
10520
|
+
a.name,
|
|
10521
|
+
a.source,
|
|
10522
|
+
a.sessionId,
|
|
10523
|
+
a.status,
|
|
10524
|
+
a.currentTask,
|
|
10525
|
+
a.currentTool,
|
|
10526
|
+
a.online ? "1" : "0"
|
|
10527
|
+
];
|
|
10528
|
+
for (const field of fields) {
|
|
10529
|
+
const value = field ?? "";
|
|
10530
|
+
for (let i = 0; i < value.length; i++) {
|
|
10531
|
+
h ^= value.charCodeAt(i);
|
|
10532
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
10533
|
+
}
|
|
10534
|
+
h ^= 255;
|
|
10535
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
10536
|
+
}
|
|
10537
|
+
}
|
|
10538
|
+
return `${agents.length}:${h.toString(36)}`;
|
|
10539
|
+
}
|
|
10540
|
+
|
|
10541
|
+
// src/core/agent-response.ts
|
|
10542
|
+
var MAX_TODO_SNAPSHOT_ITEMS = 10;
|
|
10543
|
+
var MAX_TODO_SNAPSHOT_CONTENT = 180;
|
|
10544
|
+
function buildLiveNextStepsGateBlock(ctx) {
|
|
10545
|
+
if (ctx.agentId !== "leader") return void 0;
|
|
10546
|
+
const openTodos = ctx.todos.filter(
|
|
10547
|
+
(todo) => todo.status === "pending" || todo.status === "in_progress"
|
|
10548
|
+
);
|
|
10549
|
+
if (openTodos.length === 0) {
|
|
10550
|
+
const toolRoute = ctx.tools?.some((t2) => t2.name === "nextsteps") ? [
|
|
10551
|
+
"Calling the `nextsteps` tool with the same items satisfies branch 1 as well; if you both call it and write the block, the block wins."
|
|
10552
|
+
] : [];
|
|
10553
|
+
return {
|
|
10554
|
+
type: "text",
|
|
10555
|
+
text: [
|
|
10556
|
+
"[nextsteps_gate]",
|
|
10557
|
+
"Authoritative live state for this request: open todos = 0.",
|
|
10558
|
+
"On the final response, you MUST take exactly one branch:",
|
|
10559
|
+
"1. If at least one genuinely useful follow-on action exists, include a balanced <nextsteps> block containing 1-4 exact prompt messages that can be submitted back to you through the current TUI or WebUI input.",
|
|
10560
|
+
"Every item must ask the agent to perform work. Never put a human-only chore or an instruction addressed to the user inside <nextsteps>; natural-language agent-directed imperatives are valid and need not be shell commands.",
|
|
10561
|
+
...toolRoute,
|
|
10562
|
+
"2. If no useful follow-on action truly exists, omit <nextsteps> and explicitly tell the user in normal prose that no further steps are needed for this task.",
|
|
10563
|
+
"Silently omitting both is invalid. Do not decide by chance, tone, or response length, and do not invent filler suggestions.",
|
|
10564
|
+
"[/nextsteps_gate]"
|
|
10565
|
+
].join("\n")
|
|
10566
|
+
};
|
|
10567
|
+
}
|
|
10568
|
+
const todoSnapshot = openTodos.slice(0, MAX_TODO_SNAPSHOT_ITEMS).map((todo) => {
|
|
10569
|
+
const normalized = todo.content.replace(/\s+/g, " ").trim();
|
|
10570
|
+
const content = normalized.length > MAX_TODO_SNAPSHOT_CONTENT ? `${normalized.slice(0, MAX_TODO_SNAPSHOT_CONTENT - 1)}\u2026` : normalized;
|
|
10571
|
+
return formatTodoForModel({ ...todo, content });
|
|
10572
|
+
});
|
|
10573
|
+
const omitted = openTodos.length - todoSnapshot.length;
|
|
10574
|
+
if (omitted > 0) todoSnapshot.push(`- \u2026and ${omitted} more open todo(s)`);
|
|
10575
|
+
const todoReconciliation = ctx.tools?.some((tool) => tool.name === "todo") ? [
|
|
10576
|
+
"Before ending the turn, you MUST call the `todo` tool with the complete current list to reconcile actual progress: finished items completed, exactly one actively worked item in_progress, and untouched items pending. A prose claim that work is done does not update the Todo/Kanban state.",
|
|
10577
|
+
...hasKanbanBoundTodos(openTodos) ? [
|
|
10578
|
+
"Rows below carry a <kanban board/task> binding. Pass those exact ids back as `kanbanBoardId`/`kanbanTaskId` on every row you resend; a row that arrives without its binding is not applied to its card."
|
|
10579
|
+
] : []
|
|
10580
|
+
] : [];
|
|
10581
|
+
return {
|
|
10582
|
+
type: "text",
|
|
10583
|
+
text: [
|
|
10584
|
+
"[nextsteps_gate]",
|
|
10585
|
+
`Authoritative live state for this request: open todos = ${openTodos.length}.`,
|
|
10586
|
+
"You MUST omit <nextsteps> entirely while these todos remain open. Continue or finish the tracked work; do not propose unrelated follow-on work.",
|
|
10587
|
+
...todoReconciliation,
|
|
10588
|
+
"Open todo snapshot:",
|
|
10589
|
+
...todoSnapshot,
|
|
10590
|
+
"[/nextsteps_gate]"
|
|
10591
|
+
].join("\n")
|
|
10592
|
+
};
|
|
10593
|
+
}
|
|
10594
|
+
var MAX_MEMORY_EVIDENCE_CHARS = 12e3;
|
|
10595
|
+
function buildMemoryEvidenceBlocks(ctx) {
|
|
10596
|
+
const blocks = [];
|
|
10597
|
+
let remaining = MAX_MEMORY_EVIDENCE_CHARS;
|
|
10598
|
+
for (const entry of ctx.memoryEvidence) {
|
|
10599
|
+
if (remaining <= 0) break;
|
|
10600
|
+
const text2 = entry.text.trim();
|
|
10601
|
+
if (!text2) continue;
|
|
10602
|
+
const source = entry.source.replace(/[^a-z0-9_.-]+/gi, "-").slice(0, 80) || "memory";
|
|
10603
|
+
const bounded = text2.slice(0, remaining);
|
|
10604
|
+
remaining -= bounded.length;
|
|
10605
|
+
blocks.push({
|
|
10606
|
+
type: "text",
|
|
10607
|
+
text: `[memory_evidence source="${source}"]
|
|
10608
|
+
${bounded}
|
|
10609
|
+
[/memory_evidence]`
|
|
10610
|
+
});
|
|
10611
|
+
}
|
|
10612
|
+
return blocks;
|
|
10613
|
+
}
|
|
10614
|
+
var EPOCH_VOLATILE_SOURCES = /* @__PURE__ */ new Set([
|
|
10615
|
+
"plan",
|
|
10616
|
+
"contributor",
|
|
10617
|
+
"glossary",
|
|
10618
|
+
"peers"
|
|
10619
|
+
]);
|
|
10620
|
+
var promptEpochPartitions = /* @__PURE__ */ new WeakMap();
|
|
10621
|
+
function partitionPromptEpoch(prompt) {
|
|
10622
|
+
const cached2 = promptEpochPartitions.get(prompt);
|
|
10623
|
+
if (cached2) return cached2;
|
|
10624
|
+
const stable2 = [];
|
|
10625
|
+
const tail = [];
|
|
10626
|
+
for (const block of prompt) {
|
|
10627
|
+
const source = SYSTEM_BLOCK_SOURCE.get(block);
|
|
10628
|
+
if (source && EPOCH_VOLATILE_SOURCES.has(source)) {
|
|
10629
|
+
tail.push({ type: "text", text: block.text });
|
|
10630
|
+
} else {
|
|
10631
|
+
stable2.push(block);
|
|
10632
|
+
}
|
|
10633
|
+
}
|
|
10634
|
+
const partition = { stable: stable2, tail };
|
|
10635
|
+
promptEpochPartitions.set(prompt, partition);
|
|
10636
|
+
return partition;
|
|
10637
|
+
}
|
|
10638
|
+
var LIVE_CONTEXT_HEADER = {
|
|
10639
|
+
type: "text",
|
|
10640
|
+
text: "[live_context]\nThe blocks below are live session state (active plan, glossary, completed-work ledger, conversation continuity, response gates, memory evidence) re-sent with every request. They are steering context, not a new user message. Where they conflict with the conversation, newer conversation turns win."
|
|
10641
|
+
};
|
|
10642
|
+
var NEXT_STEPS_BLOCK_RE = /<nextsteps\b[^>]*>[\s\S]*?<\/nextsteps>[ \t]*\n?/gi;
|
|
10643
|
+
var NEXT_STEPS_STRIPPED_PLACEHOLDER = "[nextsteps suggestions were delivered to the user]";
|
|
10644
|
+
var strippedNextStepsCache = /* @__PURE__ */ new WeakMap();
|
|
10645
|
+
function stripDeliveredNextSteps(history) {
|
|
10646
|
+
let out = null;
|
|
10647
|
+
for (let i = 0; i < history.length; i++) {
|
|
10648
|
+
const msg = history[i];
|
|
10649
|
+
const replaced = stripNextStepsFromMessage(msg);
|
|
10650
|
+
if (out === null && replaced !== msg) out = history.slice(0, i);
|
|
10651
|
+
if (out !== null) out.push(replaced);
|
|
10652
|
+
}
|
|
10653
|
+
return out ?? history;
|
|
10654
|
+
}
|
|
10655
|
+
function stripNextStepsFromMessage(msg) {
|
|
10656
|
+
if (msg.role !== "assistant") return msg;
|
|
10657
|
+
const cached2 = strippedNextStepsCache.get(msg);
|
|
10658
|
+
if (cached2) return cached2;
|
|
10659
|
+
const hasTag = typeof msg.content === "string" ? msg.content.includes("<nextsteps") : msg.content.some((b) => b.type === "text" && b.text.includes("<nextsteps"));
|
|
10660
|
+
if (!hasTag) {
|
|
10661
|
+
strippedNextStepsCache.set(msg, msg);
|
|
10662
|
+
return msg;
|
|
10663
|
+
}
|
|
10664
|
+
let clone;
|
|
10665
|
+
if (typeof msg.content === "string") {
|
|
10666
|
+
const text2 = msg.content.replace(NEXT_STEPS_BLOCK_RE, "").trimEnd();
|
|
10667
|
+
clone = { ...msg, content: text2.length > 0 ? text2 : NEXT_STEPS_STRIPPED_PLACEHOLDER };
|
|
10668
|
+
} else {
|
|
10669
|
+
const blocks = msg.content.map(
|
|
10670
|
+
(b) => b.type === "text" && b.text.includes("<nextsteps") ? { ...b, text: b.text.replace(NEXT_STEPS_BLOCK_RE, "").trimEnd() } : b
|
|
10671
|
+
).filter((b) => b.type !== "text" || b.text.trim().length > 0);
|
|
10672
|
+
clone = {
|
|
10673
|
+
...msg,
|
|
10674
|
+
content: blocks.length > 0 ? blocks : [{ type: "text", text: NEXT_STEPS_STRIPPED_PLACEHOLDER }]
|
|
10675
|
+
};
|
|
10676
|
+
}
|
|
10677
|
+
strippedNextStepsCache.set(msg, clone);
|
|
10678
|
+
return clone;
|
|
10679
|
+
}
|
|
10680
|
+
function composeRequestMessages(history, tail) {
|
|
10681
|
+
if (history.length === 0) return null;
|
|
10682
|
+
const out = history.slice();
|
|
10683
|
+
const lastIdx = out.length - 1;
|
|
10684
|
+
const last = out[lastIdx];
|
|
10685
|
+
const blocks = typeof last.content === "string" ? [{ type: "text", text: last.content }] : last.content.slice();
|
|
10686
|
+
const boundary = blocks[blocks.length - 1];
|
|
10687
|
+
if (boundary && (boundary.type === "text" || boundary.type === "tool_result")) {
|
|
10688
|
+
blocks[blocks.length - 1] = { ...boundary, cache_control: { type: "ephemeral" } };
|
|
10689
|
+
}
|
|
10690
|
+
if (tail.length === 0 || last.role !== "user") {
|
|
10691
|
+
out[lastIdx] = { ...last, content: blocks };
|
|
10692
|
+
if (tail.length > 0) out.push({ role: "user", content: [LIVE_CONTEXT_HEADER, ...tail] });
|
|
10693
|
+
return out;
|
|
10694
|
+
}
|
|
10695
|
+
out[lastIdx] = { ...last, content: [...blocks, LIVE_CONTEXT_HEADER, ...tail] };
|
|
10696
|
+
return out;
|
|
10697
|
+
}
|
|
10698
|
+
function createAgentResponseHandler(a) {
|
|
10699
|
+
const stabilizedPromptEpochs = /* @__PURE__ */ new WeakSet();
|
|
10700
|
+
function stabilizePromptEpoch() {
|
|
10701
|
+
const prompt = a.ctx.systemPrompt;
|
|
10702
|
+
if (stabilizedPromptEpochs.has(prompt)) return;
|
|
10703
|
+
for (const block of prompt) {
|
|
10704
|
+
if (block.cache_control) Object.freeze(block.cache_control);
|
|
10705
|
+
Object.freeze(block);
|
|
10706
|
+
}
|
|
10707
|
+
Object.freeze(prompt);
|
|
10708
|
+
stabilizedPromptEpochs.add(prompt);
|
|
10709
|
+
}
|
|
10710
|
+
async function buildAndRunRequestPipeline(opts) {
|
|
10711
|
+
if (a.ctx.toolAdjacencyDirty) {
|
|
10712
|
+
const repaired = repairToolUseAdjacency(a.ctx.messages);
|
|
10713
|
+
a.ctx.toolAdjacencyDirty = false;
|
|
10714
|
+
if (repaired.report.changed) {
|
|
10715
|
+
a.ctx.state.replaceMessages(repaired.messages);
|
|
10716
|
+
a.events.emit("context.repaired", {
|
|
10717
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
10718
|
+
ctx: a.ctx,
|
|
10719
|
+
...repaired.report
|
|
10720
|
+
});
|
|
10721
|
+
a.logger.warn(
|
|
10722
|
+
`Repaired context tool adjacency: removed ${repaired.report.removedToolUses.length} tool_use block(s), ${repaired.report.removedToolResults.length} tool_result block(s), ${repaired.report.removedMessages} empty message(s)`
|
|
10723
|
+
);
|
|
10724
|
+
}
|
|
10725
|
+
}
|
|
10726
|
+
stabilizePromptEpoch();
|
|
10727
|
+
const { stable: stableSystem, tail: epochTail } = partitionPromptEpoch(a.ctx.systemPrompt);
|
|
10728
|
+
const volatileLedger = buildCompletedWorkLedgerBlock(a.ctx);
|
|
10729
|
+
const continuity = buildConversationContinuityBlock(a.ctx);
|
|
10730
|
+
const liveNextStepsGate = buildLiveNextStepsGateBlock(a.ctx);
|
|
10731
|
+
const memoryEvidence = buildMemoryEvidenceBlocks(a.ctx);
|
|
10732
|
+
const liveContextTail = [
|
|
10733
|
+
...epochTail,
|
|
10734
|
+
volatileLedger,
|
|
10735
|
+
continuity,
|
|
10736
|
+
liveNextStepsGate,
|
|
10737
|
+
...memoryEvidence
|
|
10738
|
+
].filter((block) => block !== void 0);
|
|
10739
|
+
const requestHistory = stripDeliveredNextSteps(a.ctx.messages);
|
|
10740
|
+
const composedMessages = composeRequestMessages(requestHistory, liveContextTail);
|
|
10741
|
+
const system = composedMessages ? stableSystem : liveContextTail.length > 0 ? [...stableSystem, ...liveContextTail] : stableSystem;
|
|
10742
|
+
await a.ctx.waitForModelTransition();
|
|
10743
|
+
const provider = a.ctx.provider;
|
|
10744
|
+
const baseReq = {
|
|
10745
|
+
model: opts.model ?? a.ctx.model,
|
|
10746
|
+
system,
|
|
10747
|
+
messages: composedMessages ?? requestHistory,
|
|
10748
|
+
tools: a.tools.listForProvider(),
|
|
10749
|
+
// `maxTokens` is deliberately NOT set here. The provider adapter
|
|
10750
|
+
// resolves the ceiling from the catalog entry for the model in
|
|
10751
|
+
// `req.model`, which is the only source that stays correct across a
|
|
10752
|
+
// `/model` switch, a fallback hop, or a subagent on a model-matrix
|
|
10753
|
+
// entry — `provider.capabilities` is resolved once, for the model the
|
|
10754
|
+
// session booted on, and pinning it here would override the accurate
|
|
10755
|
+
// per-request value with a stale one. Callers that genuinely want a
|
|
10756
|
+
// smaller response (one-shot LLM helpers, compaction, the brain) still
|
|
10757
|
+
// set `maxTokens` on their own Request and keep priority over the
|
|
10758
|
+
// catalog.
|
|
10759
|
+
// Provider-agnostic cache-partition key from the STABLE part of the
|
|
10760
|
+
// prompt epoch. Wires that support prompt caching (OpenAI
|
|
10761
|
+
// `prompt_cache_key`) read it; the config `ttl` is merged over this by
|
|
10762
|
+
// the ModelRuntime middleware. Keyed off the stable partition, not the
|
|
10763
|
+
// full epoch — a glossary/plan refresh must not re-route the cache
|
|
10764
|
+
// partition when the actual prefix bytes did not change.
|
|
10765
|
+
cache: { key: deriveCachePrefixKey(stableSystem) }
|
|
10766
|
+
};
|
|
10767
|
+
const request = await a.pipelines.request.run(baseReq);
|
|
10768
|
+
bindRequestProvider(request, provider);
|
|
10769
|
+
return { request, provider };
|
|
10770
|
+
}
|
|
10771
|
+
async function processResponse(raw, req, requestProvider = a.ctx.provider) {
|
|
10772
|
+
let res = raw;
|
|
10773
|
+
res = await a.pipelines.response.run(res);
|
|
10774
|
+
res = maybeAppendPendingNextSteps(a.ctx, res);
|
|
10775
|
+
a.events.emit("provider.response", {
|
|
10776
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
10777
|
+
ctx: a.ctx,
|
|
10778
|
+
model: req.model,
|
|
10779
|
+
content: res.content,
|
|
10780
|
+
usage: res.usage,
|
|
10781
|
+
stopReason: res.stopReason
|
|
10782
|
+
});
|
|
10783
|
+
a.ctx.tokenCounter.account(res.usage, req.model, requestProvider.id);
|
|
10784
|
+
if (hasMeaningfulContent(res.content)) {
|
|
10785
|
+
await a.ctx.session.append({
|
|
10786
|
+
type: "llm_response",
|
|
10787
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10788
|
+
content: res.content,
|
|
10789
|
+
stopReason: res.stopReason,
|
|
10790
|
+
usage: res.usage
|
|
10791
|
+
});
|
|
10792
|
+
a.ctx.state.appendMessage({ role: "assistant", content: res.content });
|
|
10793
|
+
if (!a.ctx.toolAdjacencyDirty) {
|
|
10794
|
+
for (const block of res.content) {
|
|
10795
|
+
if (block.type === "tool_use") {
|
|
10796
|
+
a.ctx.toolAdjacencyDirty = true;
|
|
10797
|
+
break;
|
|
10798
|
+
}
|
|
10799
|
+
}
|
|
10800
|
+
}
|
|
10801
|
+
try {
|
|
10802
|
+
await a.ctx.flushConversationJournal();
|
|
10803
|
+
await a.ctx.session.flush();
|
|
10804
|
+
} catch (err) {
|
|
10805
|
+
(a.logger.debug ?? a.logger.warn)?.(`LLM response flush failed: ${toErrorMessage(err)}`);
|
|
10806
|
+
}
|
|
10807
|
+
} else {
|
|
10808
|
+
a.logger.warn("Empty assistant response \u2014 not appended to context or session", {
|
|
10809
|
+
model: req.model,
|
|
10810
|
+
stopReason: res.stopReason,
|
|
10811
|
+
aborted: a.ctx.signal.aborted
|
|
10812
|
+
});
|
|
10813
|
+
}
|
|
10814
|
+
if (a.ctx.signal.aborted) {
|
|
10815
|
+
const parts2 = [];
|
|
10816
|
+
for (const block of res.content) {
|
|
10817
|
+
if (isTextBlock(block)) parts2.push(block.text);
|
|
10818
|
+
}
|
|
10819
|
+
return { finalText: parts2.join(""), aborted: true, done: false };
|
|
10820
|
+
}
|
|
10821
|
+
const parts = [];
|
|
10822
|
+
const streamed = requestProvider.capabilities.streaming;
|
|
10823
|
+
for (const block of res.content) {
|
|
10824
|
+
if (isTextBlock(block)) {
|
|
10825
|
+
const rendered = await a.pipelines.assistantOutput.run(block);
|
|
10826
|
+
parts.push(rendered.text);
|
|
10827
|
+
if (!streamed) a.renderer?.write(rendered);
|
|
10828
|
+
}
|
|
10829
|
+
}
|
|
10830
|
+
const finalText = parts.join("");
|
|
10831
|
+
markAssistantReferencedEvidence(a.ctx, finalText);
|
|
10832
|
+
let directive = "none";
|
|
10833
|
+
if (finalText) {
|
|
10834
|
+
directive = parseContinueDirective(finalText);
|
|
10835
|
+
}
|
|
10836
|
+
return { finalText, aborted: false, done: false, directive };
|
|
10837
|
+
}
|
|
10838
|
+
return { buildAndRunRequestPipeline, processResponse };
|
|
10839
|
+
}
|
|
10840
|
+
|
|
10526
10841
|
// src/types/system-prompt.ts
|
|
10527
10842
|
function flattenSystemPromptRegions(regions) {
|
|
10528
10843
|
return [...regions.core, ...regions.session, ...regions.volatile];
|
|
@@ -10703,138 +11018,6 @@ function firstExistingDirSync(candidates) {
|
|
|
10703
11018
|
return candidates[0] ?? "";
|
|
10704
11019
|
}
|
|
10705
11020
|
|
|
10706
|
-
// src/core/instruction-template.ts
|
|
10707
|
-
var CANONICAL_TOOL_NAMES = new Set(
|
|
10708
|
-
RUNTIME_CAPABILITY_MANIFEST.flatMap((entry) => [...entry.tools])
|
|
10709
|
-
);
|
|
10710
|
-
var DIRECTIVE_RE = /[ \t]*<!--\s*ws:(if|else|end)\b([^>]*?)-->[ \t]*(?:\r?\n)?/g;
|
|
10711
|
-
var PLACEHOLDER_RE = /\{\{\s*(tools:)?\s*([a-zA-Z0-9_.,\s-]+?)\s*\}\}/g;
|
|
10712
|
-
function renderInstructionLayer(text2, ctx) {
|
|
10713
|
-
if (!text2) return text2;
|
|
10714
|
-
const hasDirectives = text2.includes("<!--ws:") || text2.includes("<!-- ws:");
|
|
10715
|
-
const hasPlaceholders = text2.includes("{{");
|
|
10716
|
-
if (!hasDirectives && !hasPlaceholders) return text2;
|
|
10717
|
-
const rendered = hasDirectives ? emit(parse2(text2), ctx) : text2;
|
|
10718
|
-
const substituted = hasPlaceholders ? substitute(rendered, ctx) : rendered;
|
|
10719
|
-
const guarded = ctx?.strictToolReferences ? dropLinesWithUnavailableToolReferences(
|
|
10720
|
-
substituted,
|
|
10721
|
-
ctx,
|
|
10722
|
-
/* @__PURE__ */ new Set([...CANONICAL_TOOL_NAMES, ...declaredToolNames(text2)])
|
|
10723
|
-
) : substituted;
|
|
10724
|
-
return tidy(guarded);
|
|
10725
|
-
}
|
|
10726
|
-
function declaredToolNames(text2) {
|
|
10727
|
-
const names = /* @__PURE__ */ new Set();
|
|
10728
|
-
for (const marker of text2.matchAll(/<!--\s*ws:if\b([^>]*?)-->/g)) {
|
|
10729
|
-
for (const attr of (marker[1] ?? "").matchAll(/!?tool=([A-Za-z0-9_.,-]+)/g)) {
|
|
10730
|
-
for (const name of (attr[1] ?? "").split(",")) if (name.trim()) names.add(name.trim());
|
|
10731
|
-
}
|
|
10732
|
-
}
|
|
10733
|
-
for (const placeholder of text2.matchAll(/\{\{\s*tools:\s*([^}]+)}}/g)) {
|
|
10734
|
-
for (const name of (placeholder[1] ?? "").split(",")) if (name.trim()) names.add(name.trim());
|
|
10735
|
-
}
|
|
10736
|
-
return names;
|
|
10737
|
-
}
|
|
10738
|
-
function dropLinesWithUnavailableToolReferences(text2, ctx, declared) {
|
|
10739
|
-
const unavailable = [...declared].filter((name) => !ctx.toolNames.has(name));
|
|
10740
|
-
if (unavailable.length === 0) return text2;
|
|
10741
|
-
return text2.split(/(?<=\n)/).filter((line) => !unavailable.some((name) => formattedToolMention(line, name))).join("");
|
|
10742
|
-
}
|
|
10743
|
-
function formattedToolMention(line, name) {
|
|
10744
|
-
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10745
|
-
const token = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`);
|
|
10746
|
-
if (line.split("`").some((segment, index) => {
|
|
10747
|
-
if (index % 2 !== 1) return false;
|
|
10748
|
-
if (segment.includes(`<${name}`) || segment.includes(`</${name}`)) return false;
|
|
10749
|
-
return token.test(segment);
|
|
10750
|
-
})) {
|
|
10751
|
-
return true;
|
|
10752
|
-
}
|
|
10753
|
-
return line.split("**").some((segment, index) => index % 2 === 1 && segment.trim() === name);
|
|
10754
|
-
}
|
|
10755
|
-
function parse2(text2) {
|
|
10756
|
-
const root = [];
|
|
10757
|
-
const stack = [];
|
|
10758
|
-
const current = () => {
|
|
10759
|
-
const frame = stack[stack.length - 1];
|
|
10760
|
-
if (!frame) return root;
|
|
10761
|
-
return frame.branches[frame.branches.length - 1];
|
|
10762
|
-
};
|
|
10763
|
-
const pushText = (value) => {
|
|
10764
|
-
if (value) current().push({ kind: "text", value });
|
|
10765
|
-
};
|
|
10766
|
-
DIRECTIVE_RE.lastIndex = 0;
|
|
10767
|
-
let cursor = 0;
|
|
10768
|
-
for (let m = DIRECTIVE_RE.exec(text2); m !== null; m = DIRECTIVE_RE.exec(text2)) {
|
|
10769
|
-
pushText(text2.slice(cursor, m.index));
|
|
10770
|
-
cursor = m.index + m[0].length;
|
|
10771
|
-
const keyword = m[1];
|
|
10772
|
-
if (keyword === "if") {
|
|
10773
|
-
stack.push({ test: parseCondition(m[2] ?? ""), branches: [[]] });
|
|
10774
|
-
} else if (keyword === "else") {
|
|
10775
|
-
const frame = stack[stack.length - 1];
|
|
10776
|
-
if (frame && frame.branches.length === 1) frame.branches.push([]);
|
|
10777
|
-
} else {
|
|
10778
|
-
const frame = stack.pop();
|
|
10779
|
-
if (frame) current().push({ kind: "if", test: frame.test, body: frame.branches });
|
|
10780
|
-
}
|
|
10781
|
-
}
|
|
10782
|
-
pushText(text2.slice(cursor));
|
|
10783
|
-
while (stack.length > 0) {
|
|
10784
|
-
const frame = stack.pop();
|
|
10785
|
-
current().push(...frame.branches.flat());
|
|
10786
|
-
}
|
|
10787
|
-
return root;
|
|
10788
|
-
}
|
|
10789
|
-
function parseCondition(raw) {
|
|
10790
|
-
const tokens = raw.trim().split(/\s+/).filter(Boolean);
|
|
10791
|
-
if (tokens.length === 0) return null;
|
|
10792
|
-
const attrs = [];
|
|
10793
|
-
for (const token of tokens) {
|
|
10794
|
-
const m = /^(!?)([a-zA-Z]+)=(.+)$/.exec(token);
|
|
10795
|
-
if (!m) return null;
|
|
10796
|
-
const key = (m[2] ?? "").toLowerCase();
|
|
10797
|
-
if (key !== "tool" && key !== "tier" && key !== "role") return null;
|
|
10798
|
-
const values = (m[3] ?? "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
10799
|
-
if (values.length === 0) return null;
|
|
10800
|
-
attrs.push({ key, negated: m[1] === "!", values });
|
|
10801
|
-
}
|
|
10802
|
-
return attrs;
|
|
10803
|
-
}
|
|
10804
|
-
function evaluate(test, ctx) {
|
|
10805
|
-
if (test === null || !ctx) return true;
|
|
10806
|
-
return test.every((attr) => {
|
|
10807
|
-
const matched = attr.key === "tool" ? attr.values.some((v) => ctx.toolNames.has(v)) : attr.key === "tier" ? attr.values.includes(ctx.tier) : attr.values.includes(ctx.subagent ? "subagent" : "leader");
|
|
10808
|
-
return attr.negated ? !matched : matched;
|
|
10809
|
-
});
|
|
10810
|
-
}
|
|
10811
|
-
function emit(nodes, ctx) {
|
|
10812
|
-
let out = "";
|
|
10813
|
-
for (const node of nodes) {
|
|
10814
|
-
if (node.kind === "text") {
|
|
10815
|
-
out += node.value;
|
|
10816
|
-
continue;
|
|
10817
|
-
}
|
|
10818
|
-
const branch = evaluate(node.test, ctx) ? node.body[0] : node.body[1];
|
|
10819
|
-
if (branch) out += emit(branch, ctx);
|
|
10820
|
-
}
|
|
10821
|
-
return out;
|
|
10822
|
-
}
|
|
10823
|
-
function substitute(text2, ctx) {
|
|
10824
|
-
PLACEHOLDER_RE.lastIndex = 0;
|
|
10825
|
-
return text2.replace(PLACEHOLDER_RE, (match, toolsPrefix, body) => {
|
|
10826
|
-
if (toolsPrefix) {
|
|
10827
|
-
const names = body.split(",").map((n) => n.trim()).filter(Boolean).filter((n) => !ctx || ctx.toolNames.has(n));
|
|
10828
|
-
return names.map((n) => `\`${n}\``).join(", ");
|
|
10829
|
-
}
|
|
10830
|
-
const value = ctx?.vars?.[body.trim()];
|
|
10831
|
-
return value === void 0 ? match : String(value);
|
|
10832
|
-
});
|
|
10833
|
-
}
|
|
10834
|
-
function tidy(text2) {
|
|
10835
|
-
return text2.replace(/(\r?\n){3,}/g, "$1$1");
|
|
10836
|
-
}
|
|
10837
|
-
|
|
10838
11021
|
// src/core/modes/default.ts
|
|
10839
11022
|
import { readFileSync as readFileSync5, statSync as statSync4 } from "node:fs";
|
|
10840
11023
|
import * as path23 from "node:path";
|
|
@@ -10869,61 +11052,6 @@ function isDirectory(candidate) {
|
|
|
10869
11052
|
}
|
|
10870
11053
|
}
|
|
10871
11054
|
|
|
10872
|
-
// src/core/system-prompt-blocks.ts
|
|
10873
|
-
var SYSTEM_BLOCK_SOURCE = /* @__PURE__ */ new WeakMap();
|
|
10874
|
-
function tagBlock(block, source) {
|
|
10875
|
-
SYSTEM_BLOCK_SOURCE.set(block, source);
|
|
10876
|
-
return block;
|
|
10877
|
-
}
|
|
10878
|
-
function shortSessionId(sessionId) {
|
|
10879
|
-
const leaf = sessionId.split("/").pop() ?? sessionId;
|
|
10880
|
-
return leaf.length > 12 ? `${leaf.slice(0, 12)}\u2026` : leaf;
|
|
10881
|
-
}
|
|
10882
|
-
function instructionSection(bundle, key, vars = {}, tplCtx) {
|
|
10883
|
-
const template = bundle.sections?.[key];
|
|
10884
|
-
if (!template) return "";
|
|
10885
|
-
return renderInstructionLayer(
|
|
10886
|
-
template,
|
|
10887
|
-
tplCtx ? { ...tplCtx, vars: { ...tplCtx.vars, ...vars } } : void 0
|
|
10888
|
-
).replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, name) => {
|
|
10889
|
-
const value = vars[name];
|
|
10890
|
-
return value === void 0 ? match : String(value);
|
|
10891
|
-
});
|
|
10892
|
-
}
|
|
10893
|
-
function renderToolSelectionBoundary(tool) {
|
|
10894
|
-
const selection = tool.selection;
|
|
10895
|
-
if (!selection?.doNotUseWhen.trim()) return "";
|
|
10896
|
-
const alternatives = selection.useInstead?.filter(Boolean) ?? [];
|
|
10897
|
-
const instead = alternatives.length > 0 ? ` Use ${alternatives.map((name) => `\`${name}\``).join(" or ")} instead.` : "";
|
|
10898
|
-
return `Do not use when ${selection.doNotUseWhen.trim()}${instead}`;
|
|
10899
|
-
}
|
|
10900
|
-
function agentsFingerprint(agents) {
|
|
10901
|
-
if (!agents || agents.length === 0) return "0";
|
|
10902
|
-
let h = 2166136261;
|
|
10903
|
-
for (const a of agents) {
|
|
10904
|
-
const fields = [
|
|
10905
|
-
a.agentId,
|
|
10906
|
-
a.name,
|
|
10907
|
-
a.source,
|
|
10908
|
-
a.sessionId,
|
|
10909
|
-
a.status,
|
|
10910
|
-
a.currentTask,
|
|
10911
|
-
a.currentTool,
|
|
10912
|
-
a.online ? "1" : "0"
|
|
10913
|
-
];
|
|
10914
|
-
for (const field of fields) {
|
|
10915
|
-
const value = field ?? "";
|
|
10916
|
-
for (let i = 0; i < value.length; i++) {
|
|
10917
|
-
h ^= value.charCodeAt(i);
|
|
10918
|
-
h = Math.imul(h, 16777619) >>> 0;
|
|
10919
|
-
}
|
|
10920
|
-
h ^= 255;
|
|
10921
|
-
h = Math.imul(h, 16777619) >>> 0;
|
|
10922
|
-
}
|
|
10923
|
-
}
|
|
10924
|
-
return `${agents.length}:${h.toString(36)}`;
|
|
10925
|
-
}
|
|
10926
|
-
|
|
10927
11055
|
// src/core/system-prompt-environment.ts
|
|
10928
11056
|
import * as os6 from "node:os";
|
|
10929
11057
|
import * as path25 from "node:path";
|
|
@@ -11211,24 +11339,14 @@ async function renderDomainGlossary(ctx, memory, options = {}) {
|
|
|
11211
11339
|
);
|
|
11212
11340
|
return lines.join("\n");
|
|
11213
11341
|
}
|
|
11214
|
-
function makeDomainGlossaryContributor(glossary) {
|
|
11215
|
-
return async (ctx) => {
|
|
11216
|
-
const text2 = await renderDomainGlossary(ctx, glossary.memory);
|
|
11217
|
-
if (!text2) return [];
|
|
11218
|
-
return [{ type: "text", text: text2 }];
|
|
11219
|
-
};
|
|
11220
|
-
}
|
|
11221
11342
|
function parseTermEntry(text2) {
|
|
11222
11343
|
const trimmed = text2.trim();
|
|
11223
|
-
const
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
definition: trimmed.slice(idx + sep10.length).trim()
|
|
11230
|
-
};
|
|
11231
|
-
}
|
|
11344
|
+
const idx = trimmed.indexOf(" \u2014 ");
|
|
11345
|
+
if (idx > 0) {
|
|
11346
|
+
return {
|
|
11347
|
+
term: trimmed.slice(0, idx).trim(),
|
|
11348
|
+
definition: trimmed.slice(idx + 3).trim()
|
|
11349
|
+
};
|
|
11232
11350
|
}
|
|
11233
11351
|
return { term: trimmed, definition: "" };
|
|
11234
11352
|
}
|
|
@@ -11772,7 +11890,13 @@ var DefaultSystemPromptBuilder = class {
|
|
|
11772
11890
|
_lastCatalogTools;
|
|
11773
11891
|
/** Cached rendered online agents string, keyed by content fingerprint. */
|
|
11774
11892
|
_lastOnlineAgents;
|
|
11775
|
-
/**
|
|
11893
|
+
/**
|
|
11894
|
+
* Cached full buildToolUsage output — keyed by tools array ref + tier.
|
|
11895
|
+
* Deliberately NOT keyed by the online-agents fingerprint: the live peer
|
|
11896
|
+
* snapshot moved out of this layer into the `peers` volatile block, so
|
|
11897
|
+
* layer2 stays byte-stable (and provider-cache-friendly) while agents
|
|
11898
|
+
* join, leave, or change status.
|
|
11899
|
+
*/
|
|
11776
11900
|
_toolsUsageCache;
|
|
11777
11901
|
_instructionBundle;
|
|
11778
11902
|
/**
|
|
@@ -11937,6 +12061,26 @@ var DefaultSystemPromptBuilder = class {
|
|
|
11937
12061
|
volatile.push(tagBlock({ type: "text", text: glossary }, "glossary"));
|
|
11938
12062
|
}
|
|
11939
12063
|
}
|
|
12064
|
+
const hasMailboxTools = ctx.tools.some(
|
|
12065
|
+
(t2) => t2.name === "mailbox" || t2.name === "mail_send" || t2.name === "mail_inbox"
|
|
12066
|
+
);
|
|
12067
|
+
if (hasMailboxTools) {
|
|
12068
|
+
const peers = this.renderOnlineAgents(ctx.onlineAgents).trim();
|
|
12069
|
+
if (peers) {
|
|
12070
|
+
volatile.push(
|
|
12071
|
+
tagBlock(
|
|
12072
|
+
{
|
|
12073
|
+
type: "text",
|
|
12074
|
+
text: `[online_agents]
|
|
12075
|
+
Live fleet peer snapshot for this request (see the Inter-agent mailbox guidance for how to coordinate):
|
|
12076
|
+
${peers}
|
|
12077
|
+
[/online_agents]`
|
|
12078
|
+
},
|
|
12079
|
+
"peers"
|
|
12080
|
+
)
|
|
12081
|
+
);
|
|
12082
|
+
}
|
|
12083
|
+
}
|
|
11940
12084
|
if (!ctx.subagent) {
|
|
11941
12085
|
session.push(
|
|
11942
12086
|
tagBlock(
|
|
@@ -12031,9 +12175,8 @@ var DefaultSystemPromptBuilder = class {
|
|
|
12031
12175
|
const instructions = await this.instructions();
|
|
12032
12176
|
const tpl = tplCtx ?? this.templateContext(ctx);
|
|
12033
12177
|
const section = (key, vars = {}) => instructionSection(instructions, key, vars, tpl);
|
|
12034
|
-
const agentsHash = agentsFingerprint(ctx.onlineAgents);
|
|
12035
12178
|
const tier = this.tier;
|
|
12036
|
-
if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.
|
|
12179
|
+
if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.tier === tier) {
|
|
12037
12180
|
return this._toolsUsageCache.text;
|
|
12038
12181
|
}
|
|
12039
12182
|
const byCat = /* @__PURE__ */ new Map();
|
|
@@ -12111,7 +12254,7 @@ ${hint.trim()}`);
|
|
|
12111
12254
|
(t2) => t2.name === "mailbox" || t2.name === "mail_send" || t2.name === "mail_inbox"
|
|
12112
12255
|
);
|
|
12113
12256
|
if (hasMailbox) {
|
|
12114
|
-
const onlineAgentsInfo =
|
|
12257
|
+
const onlineAgentsInfo = "";
|
|
12115
12258
|
const hasMailboxPowerTool = tools.some((t2) => t2.name === "mailbox");
|
|
12116
12259
|
const mailStatusCommand = tools.some((t2) => t2.name === "fleet_status") ? "`fleet_status`" : hasMailboxPowerTool ? "`mailbox action=status` or `mailbox action=online`" : "the online-agent list above";
|
|
12117
12260
|
const mailInboxCommand = tools.some((t2) => t2.name === "mail_inbox") ? "`mail_inbox`" : "`mailbox action=check`";
|
|
@@ -12160,7 +12303,7 @@ ${hint.trim()}`);
|
|
|
12160
12303
|
}
|
|
12161
12304
|
}
|
|
12162
12305
|
const text2 = lines.join("\n");
|
|
12163
|
-
this._toolsUsageCache = { toolsRef: tools,
|
|
12306
|
+
this._toolsUsageCache = { toolsRef: tools, tier, text: text2 };
|
|
12164
12307
|
return text2;
|
|
12165
12308
|
}
|
|
12166
12309
|
renderOnlineAgents(agents) {
|
|
@@ -12220,6 +12363,8 @@ var SYSTEM_BLOCK_SOURCES = [
|
|
|
12220
12363
|
"leader-after-task",
|
|
12221
12364
|
"contributor",
|
|
12222
12365
|
"ledger",
|
|
12366
|
+
"glossary",
|
|
12367
|
+
"peers",
|
|
12223
12368
|
"nextsteps",
|
|
12224
12369
|
"other"
|
|
12225
12370
|
];
|
|
@@ -23638,6 +23783,7 @@ var TOOLS = {
|
|
|
23638
23783
|
"glob",
|
|
23639
23784
|
"search",
|
|
23640
23785
|
"tree",
|
|
23786
|
+
"diff",
|
|
23641
23787
|
"write",
|
|
23642
23788
|
"edit",
|
|
23643
23789
|
"replace",
|
|
@@ -24669,7 +24815,7 @@ var VERIFY_AGENTS = [
|
|
|
24669
24815
|
id: "e2e",
|
|
24670
24816
|
name: "E2E",
|
|
24671
24817
|
role: "e2e",
|
|
24672
|
-
tools: [...TOOLS.build, "fetch", ...SPECIALIST_TOOLS.browser],
|
|
24818
|
+
tools: [...TOOLS.build, "fetch", "e2e_plan", ...SPECIALIST_TOOLS.browser],
|
|
24673
24819
|
prompt: agentPrompt("e2e")
|
|
24674
24820
|
},
|
|
24675
24821
|
budget: HEAVY_BUDGET,
|
|
@@ -25182,7 +25328,7 @@ var DOMAIN_AGENTS = [
|
|
|
25182
25328
|
id: "designer",
|
|
25183
25329
|
name: "Designer",
|
|
25184
25330
|
role: "designer",
|
|
25185
|
-
tools: [...TOOLS.docs],
|
|
25331
|
+
tools: [...TOOLS.docs, "design"],
|
|
25186
25332
|
prompt: agentPrompt("designer")
|
|
25187
25333
|
},
|
|
25188
25334
|
budget: MEDIUM_BUDGET,
|
|
@@ -62858,7 +63004,10 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
62858
63004
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
62859
63005
|
// The separator group is optional so the attached form (the common one) matches.
|
|
62860
63006
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
62861
|
-
|
|
63007
|
+
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
63008
|
+
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
63009
|
+
// redacted, not just the first. Synced with packages/tools _redact-command.ts.
|
|
63010
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
62862
63011
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
62863
63012
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
62864
63013
|
// redaction function (false positive = cosmetic noise; false negative = leak).
|
|
@@ -62866,8 +63015,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
62866
63015
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
62867
63016
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
62868
63017
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
62869
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
62870
|
-
|
|
63018
|
+
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
63019
|
+
// every such flag in the command line is redacted, not just the first.
|
|
63020
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
62871
63021
|
];
|
|
62872
63022
|
function redactCommand(cmd) {
|
|
62873
63023
|
let result = cmd;
|
|
@@ -80114,7 +80264,10 @@ var DefaultPluginAPI = class {
|
|
|
80114
80264
|
}
|
|
80115
80265
|
};
|
|
80116
80266
|
this.tools = {
|
|
80117
|
-
register: (t2) =>
|
|
80267
|
+
register: (t2) => {
|
|
80268
|
+
tr.register(t2, owner);
|
|
80269
|
+
tr.exposeToProvider(t2.name);
|
|
80270
|
+
},
|
|
80118
80271
|
unregister: (name) => {
|
|
80119
80272
|
assertCanMutateTool(name, "unregister");
|
|
80120
80273
|
return tr.unregister(name);
|
|
@@ -81192,6 +81345,15 @@ function resolveAutoReviewConfig(cfg, sessionConfig) {
|
|
|
81192
81345
|
maxCascadeDepth: cfg.maxCascadeDepth ?? DEFAULT_MAX_CASCADE_DEPTH
|
|
81193
81346
|
};
|
|
81194
81347
|
}
|
|
81348
|
+
function severitiesFromFindings(findings) {
|
|
81349
|
+
const severities = { critical: 0, high: 0, medium: 0 };
|
|
81350
|
+
for (const finding of findings) {
|
|
81351
|
+
if (finding.severity === "critical") severities.critical++;
|
|
81352
|
+
else if (finding.severity === "high") severities.high++;
|
|
81353
|
+
else if (finding.severity === "medium") severities.medium++;
|
|
81354
|
+
}
|
|
81355
|
+
return severities;
|
|
81356
|
+
}
|
|
81195
81357
|
function parseReviewSeverity(text2) {
|
|
81196
81358
|
const result = { critical: 0, high: 0, medium: 0 };
|
|
81197
81359
|
if (!text2) return result;
|
|
@@ -81208,9 +81370,20 @@ function parseReviewSeverity(text2) {
|
|
|
81208
81370
|
}
|
|
81209
81371
|
return result;
|
|
81210
81372
|
}
|
|
81211
|
-
function decideCascadeAgents(text2, severities) {
|
|
81373
|
+
function decideCascadeAgents(text2, severities, findings) {
|
|
81212
81374
|
const agents = /* @__PURE__ */ new Set();
|
|
81213
81375
|
if (severities.critical > 0 || severities.high > 0) agents.add("bug-hunter");
|
|
81376
|
+
if (findings && findings.length > 0) {
|
|
81377
|
+
const highPlus = findings.filter(
|
|
81378
|
+
(finding) => finding.severity === "critical" || finding.severity === "high"
|
|
81379
|
+
);
|
|
81380
|
+
if (highPlus.some((finding) => finding.category === "security")) {
|
|
81381
|
+
agents.add("security-scanner");
|
|
81382
|
+
}
|
|
81383
|
+
if (highPlus.every((finding) => finding.category !== void 0)) {
|
|
81384
|
+
return [...agents];
|
|
81385
|
+
}
|
|
81386
|
+
}
|
|
81214
81387
|
const securityKeywords = [
|
|
81215
81388
|
"injection",
|
|
81216
81389
|
"xss",
|
|
@@ -81583,7 +81756,9 @@ function createAutoReviewPlugin() {
|
|
|
81583
81756
|
maxFiles: cfg.maxFilesPerBatch,
|
|
81584
81757
|
autoFix: "off",
|
|
81585
81758
|
cascadeOn: "off",
|
|
81586
|
-
maxCascadeDepth: 0
|
|
81759
|
+
maxCascadeDepth: 0,
|
|
81760
|
+
fallbackModels: [...cfg.fallbackModels],
|
|
81761
|
+
fallbackProfile: void 0
|
|
81587
81762
|
},
|
|
81588
81763
|
files: filesWithContent,
|
|
81589
81764
|
activeTodos: ctxTodos,
|
|
@@ -81688,7 +81863,9 @@ function createAutoReviewPlugin() {
|
|
|
81688
81863
|
maxFiles: cfg.maxFilesPerBatch,
|
|
81689
81864
|
autoFix: "off",
|
|
81690
81865
|
cascadeOn: "off",
|
|
81691
|
-
maxCascadeDepth: 0
|
|
81866
|
+
maxCascadeDepth: 0,
|
|
81867
|
+
fallbackModels: [...cfg.fallbackModels],
|
|
81868
|
+
fallbackProfile: void 0
|
|
81692
81869
|
},
|
|
81693
81870
|
files: filesWithContent,
|
|
81694
81871
|
cascadeOn: cfg.cascadeOn,
|
|
@@ -81730,23 +81907,31 @@ function createAutoReviewPlugin() {
|
|
|
81730
81907
|
if (!p.reviewText) return;
|
|
81731
81908
|
const cascadeOn = p.bundle.cascadeOn ?? "off";
|
|
81732
81909
|
if (cascadeOn === "off") return;
|
|
81733
|
-
const
|
|
81910
|
+
const parsed = p.parsedReport;
|
|
81911
|
+
const verifiedFindings = parsed?.findings.filter((f) => f.verification?.status === "verified") ?? [];
|
|
81912
|
+
const severities = parsed ? severitiesFromFindings(verifiedFindings) : parseReviewSeverity(p.reviewText);
|
|
81734
81913
|
const threshold = shouldCascade(cascadeOn, severities);
|
|
81735
81914
|
if (!threshold) return;
|
|
81736
|
-
const agents = decideCascadeAgents(
|
|
81915
|
+
const agents = decideCascadeAgents(
|
|
81916
|
+
p.reviewText,
|
|
81917
|
+
severities,
|
|
81918
|
+
parsed ? verifiedFindings : void 0
|
|
81919
|
+
);
|
|
81737
81920
|
if (agents.length === 0) {
|
|
81738
81921
|
return;
|
|
81739
81922
|
}
|
|
81740
81923
|
const cascadePayload = {
|
|
81741
81924
|
bundle: p.bundle,
|
|
81925
|
+
...p.reportId ? { reportId: p.reportId } : {},
|
|
81742
81926
|
reviewText: p.reviewText,
|
|
81743
81927
|
severities,
|
|
81744
81928
|
threshold,
|
|
81745
|
-
agents
|
|
81929
|
+
agents,
|
|
81930
|
+
...parsed ? { verifiedFindings } : {}
|
|
81746
81931
|
};
|
|
81747
81932
|
api.emitCustom("chimera.cascade_needed", cascadePayload);
|
|
81748
81933
|
api.log.info(
|
|
81749
|
-
`[auto-review] cascade_needed emitted \u2014 ${severities.critical} critical, ${severities.high} high, ${severities.medium} medium; agents: ${agents.join(", ")}`
|
|
81934
|
+
`[auto-review] cascade_needed emitted \u2014 ${severities.critical} critical, ${severities.high} high, ${severities.medium} medium; agents: ${agents.join(", ")}${parsed ? ` (gated on ${verifiedFindings.length} verified finding(s))` : ""}`
|
|
81750
81935
|
);
|
|
81751
81936
|
} catch (err) {
|
|
81752
81937
|
api.log.warn(
|
|
@@ -81778,12 +81963,88 @@ init_review_finding_store();
|
|
|
81778
81963
|
// src/plugins/review-finding-parser.ts
|
|
81779
81964
|
init_review_finding_types();
|
|
81780
81965
|
import { randomUUID as randomUUID40 } from "node:crypto";
|
|
81966
|
+
var SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
|
|
81967
|
+
var CATEGORIES = /* @__PURE__ */ new Set([
|
|
81968
|
+
"bug",
|
|
81969
|
+
"security",
|
|
81970
|
+
"performance",
|
|
81971
|
+
"type",
|
|
81972
|
+
"contract",
|
|
81973
|
+
"test",
|
|
81974
|
+
"other"
|
|
81975
|
+
]);
|
|
81976
|
+
var CONFIDENCES = /* @__PURE__ */ new Set(["high", "medium", "low"]);
|
|
81977
|
+
var FENCED_BLOCK = /```json[ \t]*\r?\n([\s\S]*?)\r?\n```/gi;
|
|
81978
|
+
function extractStructuredFindingsBlock(reportText) {
|
|
81979
|
+
if (!reportText) return null;
|
|
81980
|
+
let best = null;
|
|
81981
|
+
for (const match of reportText.matchAll(FENCED_BLOCK)) {
|
|
81982
|
+
const body = match[1];
|
|
81983
|
+
if (!body?.trim()) continue;
|
|
81984
|
+
let parsed;
|
|
81985
|
+
try {
|
|
81986
|
+
parsed = JSON.parse(body);
|
|
81987
|
+
} catch {
|
|
81988
|
+
continue;
|
|
81989
|
+
}
|
|
81990
|
+
if (typeof parsed !== "object" || parsed === null) continue;
|
|
81991
|
+
const findings = parsed.findings;
|
|
81992
|
+
if (!Array.isArray(findings)) continue;
|
|
81993
|
+
const items = [];
|
|
81994
|
+
for (const raw of findings) {
|
|
81995
|
+
const item = normalizeStructuredItem(raw);
|
|
81996
|
+
if (item) items.push(item);
|
|
81997
|
+
}
|
|
81998
|
+
if (findings.length > 0 && items.length === 0) continue;
|
|
81999
|
+
const durationRaw = parsed.durationSeconds;
|
|
82000
|
+
const durationSeconds = typeof durationRaw === "number" && Number.isFinite(durationRaw) && durationRaw > 0 ? Math.floor(durationRaw) : void 0;
|
|
82001
|
+
best = { findings: items, ...durationSeconds !== void 0 ? { durationSeconds } : {} };
|
|
82002
|
+
}
|
|
82003
|
+
return best;
|
|
82004
|
+
}
|
|
82005
|
+
function normalizeStructuredItem(raw) {
|
|
82006
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
82007
|
+
const item = raw;
|
|
82008
|
+
const severity = typeof item.severity === "string" ? item.severity.toLowerCase() : "";
|
|
82009
|
+
if (!SEVERITIES.has(severity)) return null;
|
|
82010
|
+
const title = typeof item.title === "string" ? item.title.trim() : "";
|
|
82011
|
+
if (title.length === 0) return null;
|
|
82012
|
+
const file = typeof item.file === "string" && item.file.trim().length > 0 ? item.file.trim() : void 0;
|
|
82013
|
+
const line = typeof item.line === "number" && Number.isInteger(item.line) && item.line >= 1 ? item.line : void 0;
|
|
82014
|
+
const categoryRaw = typeof item.category === "string" ? item.category.toLowerCase() : "";
|
|
82015
|
+
const category = CATEGORIES.has(categoryRaw) ? categoryRaw : void 0;
|
|
82016
|
+
const confidenceRaw = typeof item.confidence === "string" ? item.confidence.toLowerCase() : "";
|
|
82017
|
+
const confidence = CONFIDENCES.has(confidenceRaw) ? confidenceRaw : void 0;
|
|
82018
|
+
return {
|
|
82019
|
+
severity,
|
|
82020
|
+
...file ? { file } : {},
|
|
82021
|
+
...line !== void 0 ? { line } : {},
|
|
82022
|
+
...category ? { category } : {},
|
|
82023
|
+
...confidence ? { confidence } : {},
|
|
82024
|
+
title,
|
|
82025
|
+
...typeof item.description === "string" && item.description.trim().length > 0 ? { description: item.description.trim() } : {},
|
|
82026
|
+
...typeof item.suggestedFix === "string" && item.suggestedFix.trim().length > 0 ? { suggestedFix: item.suggestedFix.trim() } : {}
|
|
82027
|
+
};
|
|
82028
|
+
}
|
|
81781
82029
|
var SUGGEST_LINE = /^\s*(?:→|->|=>)\s*(.+)$/;
|
|
81782
82030
|
var DURATION_LINE = /^Duration:\s*(\d+)s\s*$/im;
|
|
81783
82031
|
function parseChimeraReviewReport(reportText, context = {}) {
|
|
81784
82032
|
if (!reportText || reportText.trim().length === 0) {
|
|
81785
82033
|
return { findings: [], unparseableCount: 0 };
|
|
81786
82034
|
}
|
|
82035
|
+
const structured = extractStructuredFindingsBlock(reportText);
|
|
82036
|
+
if (structured) {
|
|
82037
|
+
const reportId2 = context.reportId ?? randomUUID40();
|
|
82038
|
+
const findings2 = structured.findings.map(
|
|
82039
|
+
(item) => buildFindingFromStructuredItem(item, { ...context, reportId: reportId2 })
|
|
82040
|
+
);
|
|
82041
|
+
return {
|
|
82042
|
+
findings: findings2,
|
|
82043
|
+
unparseableCount: 0,
|
|
82044
|
+
...structured.durationSeconds !== void 0 ? { durationSeconds: structured.durationSeconds } : {},
|
|
82045
|
+
structured: true
|
|
82046
|
+
};
|
|
82047
|
+
}
|
|
81787
82048
|
const findings = [];
|
|
81788
82049
|
const reportId = context.reportId ?? randomUUID40();
|
|
81789
82050
|
let unparseableCount = 0;
|
|
@@ -81915,14 +82176,92 @@ function normalizeFindingSource(reviewType) {
|
|
|
81915
82176
|
return "chimera";
|
|
81916
82177
|
}
|
|
81917
82178
|
}
|
|
82179
|
+
function buildFindingFromStructuredItem(item, context) {
|
|
82180
|
+
const file = item.file;
|
|
82181
|
+
const line = item.line;
|
|
82182
|
+
const title = item.title;
|
|
82183
|
+
const description = item.description ?? title;
|
|
82184
|
+
return {
|
|
82185
|
+
id: randomUUID40(),
|
|
82186
|
+
fingerprint: computeFindingFingerprint(file ?? "", line ?? null, title),
|
|
82187
|
+
severity: item.severity,
|
|
82188
|
+
source: normalizeFindingSource(context.reviewType),
|
|
82189
|
+
...file ? { location: { file, ...line !== void 0 ? { line } : {} } } : {},
|
|
82190
|
+
...item.category ? { category: item.category } : {},
|
|
82191
|
+
...item.confidence ? { confidence: item.confidence } : {},
|
|
82192
|
+
title,
|
|
82193
|
+
description,
|
|
82194
|
+
...item.suggestedFix ? { suggestedFix: item.suggestedFix } : {},
|
|
82195
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
82196
|
+
status: "active",
|
|
82197
|
+
originReport: {
|
|
82198
|
+
reportId: context.reportId ?? randomUUID40(),
|
|
82199
|
+
sessionId: context.sessionId ?? "",
|
|
82200
|
+
agentId: context.agentId ?? "",
|
|
82201
|
+
reviewerModel: context.reviewerModel ?? ""
|
|
82202
|
+
}
|
|
82203
|
+
};
|
|
82204
|
+
}
|
|
81918
82205
|
|
|
81919
82206
|
// src/plugins/review-report-integration.ts
|
|
81920
82207
|
init_review_finding_store();
|
|
82208
|
+
|
|
82209
|
+
// src/plugins/review-finding-integration.ts
|
|
82210
|
+
init_review_finding_store();
|
|
82211
|
+
function classifyChimeraReviewSource(bundle) {
|
|
82212
|
+
const cascadeDepth = bundle.cascadeDepth ?? 0;
|
|
82213
|
+
if (cascadeDepth > 0) return "cascade";
|
|
82214
|
+
const cascadeOn = bundle.cascadeOn;
|
|
82215
|
+
if (cascadeOn !== void 0 && cascadeOn !== "off") return "auto";
|
|
82216
|
+
return "chimera";
|
|
82217
|
+
}
|
|
82218
|
+
async function integrateFindings(payload, projectDir, reportId) {
|
|
82219
|
+
if ((!payload.reviewText || payload.reviewText.trim().length === 0) && !payload.parsedReport) {
|
|
82220
|
+
return { created: 0, relinked: 0, reopened: 0, totalFindings: 0, unparseableCount: 0 };
|
|
82221
|
+
}
|
|
82222
|
+
const store = new JsonlFindingStore(projectDir);
|
|
82223
|
+
const source = classifyChimeraReviewSource(payload.bundle);
|
|
82224
|
+
const agentId = payload.bundle.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review";
|
|
82225
|
+
const sessionId = payload.sessionId ?? payload.cwd;
|
|
82226
|
+
const model = payload.bundle.config.model;
|
|
82227
|
+
const parsed = payload.parsedReport ?? parseChimeraReviewReport(payload.reviewText, {
|
|
82228
|
+
sessionId,
|
|
82229
|
+
agentId,
|
|
82230
|
+
reviewerModel: model,
|
|
82231
|
+
reviewType: source,
|
|
82232
|
+
reportId
|
|
82233
|
+
});
|
|
82234
|
+
if (parsed.findings.length === 0) {
|
|
82235
|
+
return {
|
|
82236
|
+
created: 0,
|
|
82237
|
+
relinked: 0,
|
|
82238
|
+
reopened: 0,
|
|
82239
|
+
totalFindings: 0,
|
|
82240
|
+
unparseableCount: parsed.unparseableCount
|
|
82241
|
+
};
|
|
82242
|
+
}
|
|
82243
|
+
const result = await store.upsert(parsed.findings, {
|
|
82244
|
+
sessionId,
|
|
82245
|
+
reportId,
|
|
82246
|
+
agentId,
|
|
82247
|
+
model
|
|
82248
|
+
});
|
|
82249
|
+
return {
|
|
82250
|
+
created: result.created,
|
|
82251
|
+
relinked: result.relinked,
|
|
82252
|
+
reopened: result.reopened,
|
|
82253
|
+
reportId,
|
|
82254
|
+
totalFindings: parsed.findings.length,
|
|
82255
|
+
unparseableCount: parsed.unparseableCount
|
|
82256
|
+
};
|
|
82257
|
+
}
|
|
82258
|
+
|
|
82259
|
+
// src/plugins/review-report-integration.ts
|
|
81921
82260
|
init_review_report_store();
|
|
81922
82261
|
async function persistReviewReport(payload, reportId, projectDir) {
|
|
81923
82262
|
const store = new JsonlReportStore(projectDir);
|
|
81924
82263
|
const existed = await store.get(reportId);
|
|
81925
|
-
const source =
|
|
82264
|
+
const source = classifyChimeraReviewSource(payload.bundle);
|
|
81926
82265
|
const agentId = payload.bundle.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review";
|
|
81927
82266
|
const sessionId = payload.sessionId ?? payload.cwd;
|
|
81928
82267
|
const model = payload.bundle.config.model;
|
|
@@ -81932,7 +82271,13 @@ async function persistReviewReport(payload, reportId, projectDir) {
|
|
|
81932
82271
|
status: f.status
|
|
81933
82272
|
}));
|
|
81934
82273
|
const reviewStatus = payload.status === "success" ? "success" : "failed";
|
|
81935
|
-
const parsed = reviewStatus === "success" ? parseChimeraReviewReport(payload.reviewText, {
|
|
82274
|
+
const parsed = reviewStatus === "success" ? payload.parsedReport ?? parseChimeraReviewReport(payload.reviewText, {
|
|
82275
|
+
sessionId,
|
|
82276
|
+
agentId,
|
|
82277
|
+
reviewerModel: model,
|
|
82278
|
+
reviewType: source,
|
|
82279
|
+
reportId
|
|
82280
|
+
}) : { findings: [], unparseableCount: 0, durationSeconds: void 0 };
|
|
81936
82281
|
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
81937
82282
|
for (const finding of parsed.findings) {
|
|
81938
82283
|
counts[finding.severity]++;
|
|
@@ -81950,7 +82295,12 @@ async function persistReviewReport(payload, reportId, projectDir) {
|
|
|
81950
82295
|
unparseableCount: parsed.unparseableCount,
|
|
81951
82296
|
durationSeconds: parsed.durationSeconds,
|
|
81952
82297
|
rawText: payload.reviewText,
|
|
81953
|
-
...cascadeDepth !== void 0 ? { cascadeDepth } : {}
|
|
82298
|
+
...cascadeDepth !== void 0 ? { cascadeDepth } : {},
|
|
82299
|
+
// P0-3: carry the cascade evidence verification (status + per-check
|
|
82300
|
+
// comparisons) so the persisted report is auditable. Absent on initial
|
|
82301
|
+
// reviews — no cascade step produced evidence yet.
|
|
82302
|
+
...payload.bundle.evidenceStatus !== void 0 ? { evidenceStatus: payload.bundle.evidenceStatus } : {},
|
|
82303
|
+
...payload.bundle.evidenceChecks !== void 0 ? { evidenceChecks: payload.bundle.evidenceChecks } : {}
|
|
81954
82304
|
};
|
|
81955
82305
|
await store.persist(input);
|
|
81956
82306
|
if (reviewStatus === "success" && parsed.findings.length === 0 && parsed.unparseableCount === 0 && isExplicitAllClearReview(payload.reviewText)) {
|
|
@@ -82013,13 +82363,6 @@ async function syncReportReopen(reportId, projectDir, actor, reason) {
|
|
|
82013
82363
|
});
|
|
82014
82364
|
return { reportId, reopened: true, previousLifecycle: report.lifecycle };
|
|
82015
82365
|
}
|
|
82016
|
-
function classifySource(payload) {
|
|
82017
|
-
const cascadeDepth = payload.bundle.cascadeDepth ?? 0;
|
|
82018
|
-
if (cascadeDepth > 0) return "cascade";
|
|
82019
|
-
const cascadeOn = payload.bundle.cascadeOn;
|
|
82020
|
-
if (cascadeOn !== void 0 && cascadeOn !== "off") return "auto";
|
|
82021
|
-
return "chimera";
|
|
82022
|
-
}
|
|
82023
82366
|
|
|
82024
82367
|
// src/plugins/review-finding-commands.ts
|
|
82025
82368
|
async function executeFindingCommand(args, ctx) {
|
|
@@ -82349,6 +82692,18 @@ async function showReport(id, ctx) {
|
|
|
82349
82692
|
`**Review status:** ${report.reviewStatus}`,
|
|
82350
82693
|
...report.cascadeDepth !== void 0 ? [`**Cascade depth:** ${report.cascadeDepth}`] : [],
|
|
82351
82694
|
...report.durationSeconds !== void 0 ? [`**Duration:** ${report.durationSeconds}s`] : [],
|
|
82695
|
+
...report.evidenceStatus !== void 0 ? [
|
|
82696
|
+
`**Evidence:** ${report.evidenceStatus === "verified" ? "\u2705 verified" : report.evidenceStatus === "failed" ? "\u274C failed" : "\u26A0\uFE0F missing"}`,
|
|
82697
|
+
...report.evidenceChecks && report.evidenceChecks.length > 0 ? [
|
|
82698
|
+
"",
|
|
82699
|
+
...report.evidenceChecks.map((check) => {
|
|
82700
|
+
const mark = check.ok ? "\u2713" : "\u2717";
|
|
82701
|
+
const claimed = check.claimedExitCode ?? "\u2014";
|
|
82702
|
+
const actual = check.actualExitCode ?? "\u2014";
|
|
82703
|
+
return ` ${mark} \`${check.name}\` \u2014 \`${check.command}\` (claimed ${claimed}, observed ${actual})`;
|
|
82704
|
+
})
|
|
82705
|
+
] : []
|
|
82706
|
+
] : [],
|
|
82352
82707
|
"",
|
|
82353
82708
|
"**Severity counts:**",
|
|
82354
82709
|
` \u{1F534} Critical: ${report.counts.critical}`,
|
|
@@ -82457,7 +82812,9 @@ function resolveChimeraConfig(cfg, sessionProvider, sessionModel) {
|
|
|
82457
82812
|
maxFiles: cfg.maxFiles ?? DEFAULT_MAX_FILES,
|
|
82458
82813
|
autoFix: cfg.autoFix ?? "off",
|
|
82459
82814
|
cascadeOn: cfg.cascadeOn ?? DEFAULT_CASCADE_ON,
|
|
82460
|
-
maxCascadeDepth: cfg.maxCascadeDepth ?? DEFAULT_MAX_CASCADE_DEPTH2
|
|
82815
|
+
maxCascadeDepth: cfg.maxCascadeDepth ?? DEFAULT_MAX_CASCADE_DEPTH2,
|
|
82816
|
+
fallbackModels: cfg.fallbackModels ? [...cfg.fallbackModels] : [],
|
|
82817
|
+
fallbackProfile: cfg.fallbackProfile
|
|
82461
82818
|
};
|
|
82462
82819
|
}
|
|
82463
82820
|
var CHIMERA_REVIEW_PROMPT = readBundledInstructionText("llm/chimera-review.md");
|
|
@@ -84121,49 +84478,6 @@ function dim(s) {
|
|
|
84121
84478
|
return `\x1B[2m${s}\x1B[0m`;
|
|
84122
84479
|
}
|
|
84123
84480
|
|
|
84124
|
-
// src/plugins/review-finding-integration.ts
|
|
84125
|
-
init_review_finding_store();
|
|
84126
|
-
async function integrateFindings(payload, projectDir, reportId) {
|
|
84127
|
-
if (!payload.reviewText || payload.reviewText.trim().length === 0) {
|
|
84128
|
-
return { created: 0, relinked: 0, reopened: 0, totalFindings: 0, unparseableCount: 0 };
|
|
84129
|
-
}
|
|
84130
|
-
const store = new JsonlFindingStore(projectDir);
|
|
84131
|
-
const source = (payload.bundle.cascadeDepth ?? 0) > 0 ? "cascade" : payload.bundle.cascadeOn !== void 0 && payload.bundle.cascadeOn !== "off" ? "auto" : "chimera";
|
|
84132
|
-
const agentId = payload.bundle.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review";
|
|
84133
|
-
const sessionId = payload.sessionId ?? payload.cwd;
|
|
84134
|
-
const model = payload.bundle.config.model;
|
|
84135
|
-
const parsed = parseChimeraReviewReport(payload.reviewText, {
|
|
84136
|
-
sessionId,
|
|
84137
|
-
agentId,
|
|
84138
|
-
reviewerModel: model,
|
|
84139
|
-
reviewType: source,
|
|
84140
|
-
reportId
|
|
84141
|
-
});
|
|
84142
|
-
if (parsed.findings.length === 0) {
|
|
84143
|
-
return {
|
|
84144
|
-
created: 0,
|
|
84145
|
-
relinked: 0,
|
|
84146
|
-
reopened: 0,
|
|
84147
|
-
totalFindings: 0,
|
|
84148
|
-
unparseableCount: parsed.unparseableCount
|
|
84149
|
-
};
|
|
84150
|
-
}
|
|
84151
|
-
const result = await store.upsert(parsed.findings, {
|
|
84152
|
-
sessionId,
|
|
84153
|
-
reportId,
|
|
84154
|
-
agentId,
|
|
84155
|
-
model
|
|
84156
|
-
});
|
|
84157
|
-
return {
|
|
84158
|
-
created: result.created,
|
|
84159
|
-
relinked: result.relinked,
|
|
84160
|
-
reopened: result.reopened,
|
|
84161
|
-
reportId,
|
|
84162
|
-
totalFindings: parsed.findings.length,
|
|
84163
|
-
unparseableCount: parsed.unparseableCount
|
|
84164
|
-
};
|
|
84165
|
-
}
|
|
84166
|
-
|
|
84167
84481
|
// src/index.ts
|
|
84168
84482
|
init_review_finding_store();
|
|
84169
84483
|
init_review_report_store();
|
|
@@ -92758,7 +93072,7 @@ function createFallbackChainManageTool(opts) {
|
|
|
92758
93072
|
name: FALLBACK_CHAIN_MANAGE_TOOL_NAME,
|
|
92759
93073
|
description: "View or change the active rate-limit fallback chain. When the primary model is overloaded (429/5xx), the agent rotates through this chain in order. Every new entry must be a FAVORITE model \u2014 add it via favorite_manage first. Use insert to place a fallback at a specific position; use remove to delete an entry.",
|
|
92760
93074
|
usageHint: '"list" to see the current chain. "add" with a favorite model to append. "insert" with an index (1-based) to place before that position. "remove" with index or model ref. "clear" to empty the chain (auto fallback takes over).',
|
|
92761
|
-
category: "
|
|
93075
|
+
category: "config",
|
|
92762
93076
|
inputSchema: FALLBACK_CHAIN_SCHEMA,
|
|
92763
93077
|
permission: "auto",
|
|
92764
93078
|
mutating: true,
|
|
@@ -92908,7 +93222,7 @@ function createFavoriteManageTool(opts) {
|
|
|
92908
93222
|
name: FAVORITE_MANAGE_TOOL_NAME,
|
|
92909
93223
|
description: "Manage your favorite provider/model list. Favorites are the only models that can be added to fallback chains and profiles. The LLM uses this tool to curate which models are available for fallback and role assignment.",
|
|
92910
93224
|
usageHint: 'Start with "list" to see current favorites. Use "add <provider/model>" to add. Use "remove <index|ref>" to remove.',
|
|
92911
|
-
category: "
|
|
93225
|
+
category: "config",
|
|
92912
93226
|
inputSchema: FAVORITE_MANAGE_SCHEMA,
|
|
92913
93227
|
permission: "auto",
|
|
92914
93228
|
mutating: true,
|
|
@@ -92988,7 +93302,7 @@ async function storeProviderKey(providers, input, keyValue, opts) {
|
|
|
92988
93302
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
92989
93303
|
});
|
|
92990
93304
|
entry.apiKeys = existingKeys;
|
|
92991
|
-
entry.apiKey
|
|
93305
|
+
delete entry.apiKey;
|
|
92992
93306
|
if (input.setActive !== false) {
|
|
92993
93307
|
entry.activeKey = label;
|
|
92994
93308
|
}
|
|
@@ -93031,7 +93345,7 @@ function createSystemConfigViewTool(opts) {
|
|
|
93031
93345
|
name: SYSTEM_CONFIG_VIEW_TOOL_NAME,
|
|
93032
93346
|
description: "Get a comprehensive view of all provider, model, fallback, and matrix configuration. Shows the complete state across all configurable areas so you can see what is available and make informed decisions when assigning models, creating fallback profiles, or managing providers. Use the section parameter to focus on specific areas.",
|
|
93033
93347
|
usageHint: '"section: all" for everything. "section: providers" for configured providers and key status. "section: models" for leader model and favorites. "section: fallbacks" for chains, profiles, and toggles. "section: matrix" for per-role assignments. "section: refiner" for goal refinement config.',
|
|
93034
|
-
category: "
|
|
93348
|
+
category: "config",
|
|
93035
93349
|
inputSchema: SYSTEM_CONFIG_VIEW_SCHEMA,
|
|
93036
93350
|
permission: "auto",
|
|
93037
93351
|
mutating: false,
|
|
@@ -93335,7 +93649,7 @@ function createFallbackProfileManageTool(opts) {
|
|
|
93335
93649
|
name: FALLBACK_PROFILE_MANAGE_TOOL_NAME,
|
|
93336
93650
|
description: "Manage named fallback profiles. A profile is a reusable, ordered list of model references that can be assigned to agent roles. Every entry in a profile must be a FAVORITE model \u2014 add it via favorite_manage first. Use /setmodel or agent_model_assign to assign a profile to a role.",
|
|
93337
93651
|
usageHint: '"list" to see all profiles. "set" with name and chain (array of model refs) to create or replace a profile. "delete" with name to remove a profile.',
|
|
93338
|
-
category: "
|
|
93652
|
+
category: "config",
|
|
93339
93653
|
inputSchema: FALLBACK_PROFILE_SCHEMA,
|
|
93340
93654
|
permission: "auto",
|
|
93341
93655
|
mutating: true,
|
|
@@ -93446,7 +93760,7 @@ function createAgentModelAssignTool(opts) {
|
|
|
93446
93760
|
name: AGENT_MODEL_ASSIGN_TOOL_NAME,
|
|
93447
93761
|
description: "Assign a provider/model or a fallback profile to a specific agent role, phase, or the fleet-wide default. This is the LLM-accessible equivalent of /setmodel set. The provider+model combination must be in your favorites list (unless only clearing). Resolution precedence: exact role \u2192 phase \u2192 * \u2192 leader model.",
|
|
93448
93762
|
usageHint: 'Use "list" as role to see current assignments. Set with role + model, or role + provider + model, or role + profile. Set role + clear=true to remove a matrix entry. The provider/model must be a favorite.',
|
|
93449
|
-
category: "
|
|
93763
|
+
category: "config",
|
|
93450
93764
|
inputSchema: AGENT_MODEL_ASSIGN_SCHEMA,
|
|
93451
93765
|
permission: "auto",
|
|
93452
93766
|
mutating: true,
|
|
@@ -93594,7 +93908,7 @@ function createProviderManageTool(opts) {
|
|
|
93594
93908
|
name: PROVIDER_MANAGE_TOOL_NAME,
|
|
93595
93909
|
description: "View or configure provider entries. List all configured providers with their type, model lists, base URL, and key status. Add new providers, update their settings, or remove unused ones. API keys should be set via provider_key_set instead of passing them here \u2014 they are visible in the LLM output.",
|
|
93596
93910
|
usageHint: '"list" to see all providers. "add" with provider id and type to create. "configure" to update models, baseUrl, family, or envVars. "remove" to delete a provider. Use provider_key_set for API key management.',
|
|
93597
|
-
category: "
|
|
93911
|
+
category: "config",
|
|
93598
93912
|
inputSchema: PROVIDER_MANAGE_SCHEMA,
|
|
93599
93913
|
permission: "auto",
|
|
93600
93914
|
mutating: true,
|
|
@@ -93743,9 +94057,11 @@ function createProviderKeySetTool(opts) {
|
|
|
93743
94057
|
name: PROVIDER_KEY_SET_TOOL_NAME,
|
|
93744
94058
|
description: "Set the API key for a provider. For security, prefer using envVar (reads from environment variable, value never visible to the LLM) over passing the key directly. When neither key nor envVar is provided, the tool returns a prompt for interactive key entry \u2014 the UI will present an input field and the key is stored without LLM visibility.\n\nAfter setting a key, the provider becomes usable for model assignments and fallback chains. Add its models to favorites with favorite_manage to unlock them for fallback/profile use.",
|
|
93745
94059
|
usageHint: 'Preferred: provider_key_set({ provider: "openai", envVar: "OPENAI_API_KEY" }). For interactive input: provider_key_set({ provider: "openai" }) \u2014 the UI will prompt. Direct key: provider_key_set({ provider: "openai", key: "sk-..." }) \u2014 visible to LLM.',
|
|
93746
|
-
category: "
|
|
94060
|
+
category: "config",
|
|
93747
94061
|
inputSchema: PROVIDER_KEY_SET_SCHEMA,
|
|
93748
|
-
|
|
94062
|
+
// 'confirm', not 'auto' — this tool writes credentials to disk (and can
|
|
94063
|
+
// read arbitrary env vars into the config file), so the user must see it.
|
|
94064
|
+
permission: "confirm",
|
|
93749
94065
|
mutating: true,
|
|
93750
94066
|
riskTier: "standard",
|
|
93751
94067
|
icon: "settings",
|
|
@@ -93841,7 +94157,7 @@ function createLeaderModelSetTool(opts) {
|
|
|
93841
94157
|
name: LEADER_MODEL_SET_TOOL_NAME,
|
|
93842
94158
|
description: 'View or change the leader provider/model and system toggles. The leader is the primary model used for the main agent interactions. "set" changes it directly. "profile" derives it from a named fallback profile (first entry becomes leader, rest become the fallback chain). "toggle" controls fallbackAuto (smart default fallback) and favoriteModelsOnly (restrict auto-fallback to favorites only).',
|
|
93843
94159
|
usageHint: '"show" to see current state. "set" with provider+model to change. "profile" with name to derive from a profile. "toggle" with toggle name and value to change a boolean setting.',
|
|
93844
|
-
category: "
|
|
94160
|
+
category: "config",
|
|
93845
94161
|
inputSchema: LEADER_MODEL_SET_SCHEMA,
|
|
93846
94162
|
permission: "auto",
|
|
93847
94163
|
mutating: true,
|
|
@@ -93865,11 +94181,21 @@ function createLeaderModelSetTool(opts) {
|
|
|
93865
94181
|
if (!input.provider || !input.model) {
|
|
93866
94182
|
return { status: "error", message: 'Provide "provider" and "model" for the leader.' };
|
|
93867
94183
|
}
|
|
94184
|
+
if (opts.switchProviderAndModel) {
|
|
94185
|
+
const switchError = await opts.switchProviderAndModel(input.provider, input.model);
|
|
94186
|
+
if (switchError) {
|
|
94187
|
+
return {
|
|
94188
|
+
status: "error",
|
|
94189
|
+
message: `Could not switch to ${input.provider}/${input.model}: ${switchError}. Config was not changed.`
|
|
94190
|
+
};
|
|
94191
|
+
}
|
|
94192
|
+
}
|
|
93868
94193
|
await opts.updateConfig((cfg) => {
|
|
93869
94194
|
cfg.provider = input.provider;
|
|
93870
94195
|
cfg.model = input.model;
|
|
93871
94196
|
});
|
|
93872
|
-
|
|
94197
|
+
const liveNote = opts.switchProviderAndModel ? "" : " (config updated \u2014 the live session keeps its current model until restart or /setmodel)";
|
|
94198
|
+
return { status: "ok", message: `\u2713 Leader \u2192 ${input.provider}/${input.model}${liveNote}` };
|
|
93873
94199
|
}
|
|
93874
94200
|
if (input.action === "profile") {
|
|
93875
94201
|
if (!input.profile) {
|
|
@@ -93888,15 +94214,25 @@ function createLeaderModelSetTool(opts) {
|
|
|
93888
94214
|
return { status: "error", message: `Cannot parse "${first}" as a valid model reference.` };
|
|
93889
94215
|
}
|
|
93890
94216
|
const rest = chain.slice(1);
|
|
94217
|
+
if (opts.switchProviderAndModel) {
|
|
94218
|
+
const switchError = await opts.switchProviderAndModel(provider, model);
|
|
94219
|
+
if (switchError) {
|
|
94220
|
+
return {
|
|
94221
|
+
status: "error",
|
|
94222
|
+
message: `Could not switch to ${provider}/${model}: ${switchError}. Config was not changed.`
|
|
94223
|
+
};
|
|
94224
|
+
}
|
|
94225
|
+
}
|
|
93891
94226
|
await opts.updateConfig((cfg) => {
|
|
93892
94227
|
cfg.provider = provider;
|
|
93893
94228
|
cfg.model = model;
|
|
93894
94229
|
cfg.fallbackModels = rest;
|
|
93895
94230
|
});
|
|
94231
|
+
const profileLiveNote = opts.switchProviderAndModel ? "" : "\n (config updated \u2014 the live session keeps its current model until restart or /setmodel)";
|
|
93896
94232
|
return {
|
|
93897
94233
|
status: "ok",
|
|
93898
94234
|
message: `\u2713 Leader \u2192 ${provider}/${model} (profile: ${input.profile})` + (rest.length > 0 ? `
|
|
93899
|
-
Fallback chain: ${rest.join(" \u2192 ")}` : "")
|
|
94235
|
+
Fallback chain: ${rest.join(" \u2192 ")}` : "") + profileLiveNote
|
|
93900
94236
|
};
|
|
93901
94237
|
}
|
|
93902
94238
|
if (input.action === "toggle") {
|
|
@@ -94067,20 +94403,22 @@ async function runEnable(name, deps) {
|
|
|
94067
94403
|
const known = Object.keys(all).join(", ");
|
|
94068
94404
|
return `Unknown server "${name}". Available presets: ${known}`;
|
|
94069
94405
|
}
|
|
94070
|
-
|
|
94406
|
+
const persistEnabled = () => updateJsonObjectFile(deps.configPath, (full) => {
|
|
94071
94407
|
const current = isMcpServerRecord(full.mcpServers) ? full.mcpServers : {};
|
|
94072
94408
|
setJsonPath(full, ["mcpServers", name], { ...current[name], ...cfg, enabled: true });
|
|
94073
94409
|
});
|
|
94074
94410
|
try {
|
|
94075
94411
|
const live = deps.registry.describe().find((s) => s.name === name);
|
|
94076
94412
|
if (live && live.state === "connected") {
|
|
94077
|
-
|
|
94413
|
+
await persistEnabled();
|
|
94414
|
+
return `Server "${name}" is already running (${live.toolCount} tools registered).`;
|
|
94078
94415
|
}
|
|
94079
94416
|
await deps.registry.start({ ...cfg, enabled: true });
|
|
94417
|
+
await persistEnabled();
|
|
94080
94418
|
const updated = deps.registry.describe().find((s) => s.name === name);
|
|
94081
|
-
return
|
|
94419
|
+
return `Enabled and started "${name}"${updated ? ` (${updated.toolCount} tools registered).` : "."}`;
|
|
94082
94420
|
} catch (err) {
|
|
94083
|
-
return
|
|
94421
|
+
return `Failed to start "${name}": ${toErrorMessage(err)}. Config was left unchanged (server stays disabled).`;
|
|
94084
94422
|
}
|
|
94085
94423
|
}
|
|
94086
94424
|
async function runDisable(name, deps) {
|
|
@@ -94154,34 +94492,34 @@ function isMcpServerRecord(value) {
|
|
|
94154
94492
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
94155
94493
|
}
|
|
94156
94494
|
function bold(s) {
|
|
94157
|
-
return
|
|
94495
|
+
return s;
|
|
94158
94496
|
}
|
|
94159
94497
|
function dim2(s) {
|
|
94160
|
-
return
|
|
94498
|
+
return s;
|
|
94161
94499
|
}
|
|
94162
94500
|
function green(s) {
|
|
94163
|
-
return
|
|
94501
|
+
return s;
|
|
94164
94502
|
}
|
|
94165
94503
|
function yellow(s) {
|
|
94166
|
-
return
|
|
94504
|
+
return s;
|
|
94167
94505
|
}
|
|
94168
94506
|
function red(s) {
|
|
94169
|
-
return
|
|
94507
|
+
return s;
|
|
94170
94508
|
}
|
|
94171
94509
|
function badge(state) {
|
|
94172
94510
|
switch (state) {
|
|
94173
94511
|
case "connected":
|
|
94174
|
-
return
|
|
94512
|
+
return "\u25CF connected";
|
|
94175
94513
|
case "connecting":
|
|
94176
|
-
return
|
|
94514
|
+
return "\u25D0 connecting";
|
|
94177
94515
|
case "reconnecting":
|
|
94178
|
-
return
|
|
94516
|
+
return "\u25D1 reconnecting";
|
|
94179
94517
|
case "disconnected":
|
|
94180
|
-
return
|
|
94518
|
+
return "\u25CB disconnected";
|
|
94181
94519
|
case "failed":
|
|
94182
|
-
return
|
|
94520
|
+
return "\u2717 failed";
|
|
94183
94521
|
default:
|
|
94184
|
-
return
|
|
94522
|
+
return state;
|
|
94185
94523
|
}
|
|
94186
94524
|
}
|
|
94187
94525
|
|
|
@@ -94223,13 +94561,19 @@ function createMcpUseTool(opts) {
|
|
|
94223
94561
|
const servers = registry2.describe();
|
|
94224
94562
|
const serverInfo = servers.find((s) => s.name === serverName);
|
|
94225
94563
|
if (!serverInfo) {
|
|
94226
|
-
|
|
94564
|
+
throw new Error(
|
|
94565
|
+
`Server "${serverName}" not found. Available: ${servers.map((s) => s.name).join(", ") || "none"}.`
|
|
94566
|
+
);
|
|
94227
94567
|
}
|
|
94228
94568
|
if (serverInfo.state !== "connected") {
|
|
94229
|
-
|
|
94569
|
+
throw new Error(
|
|
94570
|
+
`Server "${serverName}" is not connected (state: ${serverInfo.state}). Use \`mcp_control({ action: "enable", server: "${serverName}" })\` first.`
|
|
94571
|
+
);
|
|
94230
94572
|
}
|
|
94231
|
-
|
|
94232
|
-
|
|
94573
|
+
const alreadyActive = registry2.isActivated?.(serverName) === true;
|
|
94574
|
+
const didActivate = !alreadyActive && Boolean(registry2.activateServer);
|
|
94575
|
+
if (didActivate) {
|
|
94576
|
+
registry2.activateServer?.(serverName);
|
|
94233
94577
|
}
|
|
94234
94578
|
try {
|
|
94235
94579
|
const qualifiedName = mcpQualifiedToolName(serverName, toolName);
|
|
@@ -94237,7 +94581,7 @@ function createMcpUseTool(opts) {
|
|
|
94237
94581
|
if (!mcpTool) {
|
|
94238
94582
|
const allTools = toolRegistry.list().filter((t2) => t2.name.startsWith(mcpServerToolPrefix(serverName))).map((t2) => t2.name.replace(mcpServerToolPrefix(serverName), ""));
|
|
94239
94583
|
const hint = allTools.length > 0 ? `Available tools on "${serverName}": ${allTools.join(", ")}.` : `No tools found on "${serverName}". The server may not have published any tools.`;
|
|
94240
|
-
|
|
94584
|
+
throw new Error(`Tool "${toolName}" not found on server "${serverName}". ${hint}`);
|
|
94241
94585
|
}
|
|
94242
94586
|
const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
94243
94587
|
if (typeof governedExecute !== "function") {
|
|
@@ -94247,7 +94591,7 @@ function createMcpUseTool(opts) {
|
|
|
94247
94591
|
if (!result.success) throw new Error(result.error ?? "MCP tool execution failed");
|
|
94248
94592
|
return result.result;
|
|
94249
94593
|
} finally {
|
|
94250
|
-
if (registry2.deactivateServer) {
|
|
94594
|
+
if (didActivate && registry2.deactivateServer) {
|
|
94251
94595
|
registry2.deactivateServer(serverName);
|
|
94252
94596
|
}
|
|
94253
94597
|
}
|
|
@@ -94257,6 +94601,7 @@ function createMcpUseTool(opts) {
|
|
|
94257
94601
|
|
|
94258
94602
|
// src/tools/one-shot-llm-tool.ts
|
|
94259
94603
|
var ONE_SHOT_LLM_TOOL_NAME = "llm";
|
|
94604
|
+
var MAX_TIMEOUT_MS2 = 12e4;
|
|
94260
94605
|
var INPUT_SCHEMA2 = {
|
|
94261
94606
|
type: "object",
|
|
94262
94607
|
properties: {
|
|
@@ -94333,9 +94678,10 @@ var INPUT_SCHEMA2 = {
|
|
|
94333
94678
|
},
|
|
94334
94679
|
timeoutMs: {
|
|
94335
94680
|
type: "number",
|
|
94336
|
-
description:
|
|
94681
|
+
description: `Hard timeout in ms (default 30s, clamped to a maximum of ${MAX_TIMEOUT_MS2}).`
|
|
94337
94682
|
}
|
|
94338
|
-
}
|
|
94683
|
+
},
|
|
94684
|
+
additionalProperties: false
|
|
94339
94685
|
};
|
|
94340
94686
|
function createOneShotLLMTool(opts) {
|
|
94341
94687
|
const orchestrator = new OneShotOrchestrator({
|
|
@@ -94343,6 +94689,7 @@ function createOneShotLLMTool(opts) {
|
|
|
94343
94689
|
getConfig: opts.getConfig,
|
|
94344
94690
|
fallbackProfileManager: opts.fallbackProfileManager,
|
|
94345
94691
|
modelRouter: opts.modelRouter,
|
|
94692
|
+
statusTracker: opts.statusTracker,
|
|
94346
94693
|
logger: opts.logger,
|
|
94347
94694
|
wrapProviderCall: opts.wrapProviderCall
|
|
94348
94695
|
});
|
|
@@ -94351,8 +94698,23 @@ function createOneShotLLMTool(opts) {
|
|
|
94351
94698
|
description: "Make a one-shot LLM call with a system prompt and user input. Supports provider selection, model routing by role, fallback chains, and timeout. Returns the response text, model info, token usage, and whether a fallback was used. Use this for summarization, classification, extraction, and any single-turn LLM task.",
|
|
94352
94699
|
usageHint: "Provide `system` for the instruction and `userPrompt` for the input. Either set `model`+`providerId`, or have defaults configured on the tool. Set `fallbackModels` for resilience. Check `error` on the result for failure details.",
|
|
94353
94700
|
inputSchema: INPUT_SCHEMA2,
|
|
94701
|
+
// Metadata mirrors council-tool.ts — both are read-only meta tools that
|
|
94702
|
+
// spend tokens but never touch the workspace.
|
|
94703
|
+
category: "meta",
|
|
94354
94704
|
permission: "auto",
|
|
94355
94705
|
mutating: false,
|
|
94706
|
+
riskTier: "safe",
|
|
94707
|
+
maxOutputBytes: 262144,
|
|
94708
|
+
validate(input) {
|
|
94709
|
+
const hasPrompt = typeof input.userPrompt === "string" && input.userPrompt.trim().length > 0;
|
|
94710
|
+
const hasMessages = Array.isArray(input.messages) && input.messages.length > 0;
|
|
94711
|
+
if (!hasPrompt && !hasMessages) {
|
|
94712
|
+
return [
|
|
94713
|
+
"Provide `userPrompt` (a single user turn) or `messages` (a conversation array) \u2014 without either the llm tool has nothing to send to the model."
|
|
94714
|
+
];
|
|
94715
|
+
}
|
|
94716
|
+
return [];
|
|
94717
|
+
},
|
|
94356
94718
|
async execute(input, _ctx, { signal }) {
|
|
94357
94719
|
if (!input.model && !input.providerId && !opts.defaultModel && !opts.defaultProvider) {
|
|
94358
94720
|
return {
|
|
@@ -94369,7 +94731,11 @@ function createOneShotLLMTool(opts) {
|
|
|
94369
94731
|
...input,
|
|
94370
94732
|
signal: input.signal ? AbortSignal.any([input.signal, signal]) : signal,
|
|
94371
94733
|
model: input.model ?? opts.defaultModel,
|
|
94372
|
-
providerId: input.providerId ?? opts.defaultProvider
|
|
94734
|
+
providerId: input.providerId ?? opts.defaultProvider,
|
|
94735
|
+
// Clamp runaway timeouts (documented on the schema). Non-positive
|
|
94736
|
+
// values fall back to the orchestrator default rather than making the
|
|
94737
|
+
// call instantly un-completable.
|
|
94738
|
+
...typeof input.timeoutMs === "number" && input.timeoutMs > 0 ? { timeoutMs: Math.min(input.timeoutMs, MAX_TIMEOUT_MS2) } : { timeoutMs: void 0 }
|
|
94373
94739
|
};
|
|
94374
94740
|
return orchestrator.call(effectiveInput);
|
|
94375
94741
|
}
|
|
@@ -96346,6 +96712,8 @@ export {
|
|
|
96346
96712
|
logHqAuthAudit,
|
|
96347
96713
|
logicalCalendarTarget,
|
|
96348
96714
|
mailboxIdentityBase,
|
|
96715
|
+
mailboxProjectServerEndpoint,
|
|
96716
|
+
mailboxProjectServerMetadataPath,
|
|
96349
96717
|
mailboxSessionTag,
|
|
96350
96718
|
makeAgentSubagentRunner,
|
|
96351
96719
|
makeAskResultTool,
|
|
@@ -96361,7 +96729,6 @@ export {
|
|
|
96361
96729
|
makeDesignStudioRequestMiddleware,
|
|
96362
96730
|
makeDesignVerifyToolCallMiddleware,
|
|
96363
96731
|
makeDirectorSessionFactory,
|
|
96364
|
-
makeDomainGlossaryContributor,
|
|
96365
96732
|
makeFleetEmitTool,
|
|
96366
96733
|
makeFleetStatusTool,
|
|
96367
96734
|
makeFleetTool,
|