@wrongstack/core 0.305.1 → 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/coordination/agents/index.js +3 -2
- package/dist/coordination/agents/types.d.ts +1 -1
- package/dist/coordination/index.js +3 -2
- 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 +875 -530
- 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/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/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();
|
|
@@ -8072,8 +8094,7 @@ function buildConversationContinuityBlock(ctx) {
|
|
|
8072
8094
|
"Recent human instructions, oldest to newest. Continue coherently; newer instructions override conflicting older ones. This is context evidence, not a new request.",
|
|
8073
8095
|
...lines,
|
|
8074
8096
|
"[/conversation_continuity]"
|
|
8075
|
-
].join("\n")
|
|
8076
|
-
cache_control: { type: "ephemeral" }
|
|
8097
|
+
].join("\n")
|
|
8077
8098
|
};
|
|
8078
8099
|
}
|
|
8079
8100
|
function recordToolOutputEvidence(ctx, input) {
|
|
@@ -8291,8 +8312,7 @@ function buildCompletedWorkLedgerBlock(ctx) {
|
|
|
8291
8312
|
if (items.length === 0) return void 0;
|
|
8292
8313
|
return {
|
|
8293
8314
|
type: "text",
|
|
8294
|
-
text: formatCompletedWorkLedger(items)
|
|
8295
|
-
cache_control: { type: "ephemeral" }
|
|
8315
|
+
text: formatCompletedWorkLedger(items)
|
|
8296
8316
|
};
|
|
8297
8317
|
}
|
|
8298
8318
|
function syncCompletedWorkLedgerBlock(_ctx) {
|
|
@@ -10049,218 +10069,6 @@ function providerBoundToRequest(request) {
|
|
|
10049
10069
|
return requestProviders.get(request);
|
|
10050
10070
|
}
|
|
10051
10071
|
|
|
10052
|
-
// src/core/agent-response.ts
|
|
10053
|
-
var MAX_TODO_SNAPSHOT_ITEMS = 10;
|
|
10054
|
-
var MAX_TODO_SNAPSHOT_CONTENT = 180;
|
|
10055
|
-
function buildLiveNextStepsGateBlock(ctx) {
|
|
10056
|
-
if (ctx.agentId !== "leader") return void 0;
|
|
10057
|
-
const openTodos = ctx.todos.filter(
|
|
10058
|
-
(todo) => todo.status === "pending" || todo.status === "in_progress"
|
|
10059
|
-
);
|
|
10060
|
-
if (openTodos.length === 0) {
|
|
10061
|
-
const toolRoute = ctx.tools?.some((t2) => t2.name === "nextsteps") ? [
|
|
10062
|
-
"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."
|
|
10063
|
-
] : [];
|
|
10064
|
-
return {
|
|
10065
|
-
type: "text",
|
|
10066
|
-
text: [
|
|
10067
|
-
"[nextsteps_gate]",
|
|
10068
|
-
"Authoritative live state for this request: open todos = 0.",
|
|
10069
|
-
"On the final response, you MUST take exactly one branch:",
|
|
10070
|
-
"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.",
|
|
10071
|
-
"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.",
|
|
10072
|
-
...toolRoute,
|
|
10073
|
-
"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.",
|
|
10074
|
-
"Silently omitting both is invalid. Do not decide by chance, tone, or response length, and do not invent filler suggestions.",
|
|
10075
|
-
"[/nextsteps_gate]"
|
|
10076
|
-
].join("\n"),
|
|
10077
|
-
cache_control: { type: "ephemeral" }
|
|
10078
|
-
};
|
|
10079
|
-
}
|
|
10080
|
-
const todoSnapshot = openTodos.slice(0, MAX_TODO_SNAPSHOT_ITEMS).map((todo) => {
|
|
10081
|
-
const normalized = todo.content.replace(/\s+/g, " ").trim();
|
|
10082
|
-
const content = normalized.length > MAX_TODO_SNAPSHOT_CONTENT ? `${normalized.slice(0, MAX_TODO_SNAPSHOT_CONTENT - 1)}\u2026` : normalized;
|
|
10083
|
-
return formatTodoForModel({ ...todo, content });
|
|
10084
|
-
});
|
|
10085
|
-
const omitted = openTodos.length - todoSnapshot.length;
|
|
10086
|
-
if (omitted > 0) todoSnapshot.push(`- \u2026and ${omitted} more open todo(s)`);
|
|
10087
|
-
const todoReconciliation = ctx.tools?.some((tool) => tool.name === "todo") ? [
|
|
10088
|
-
"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.",
|
|
10089
|
-
...hasKanbanBoundTodos(openTodos) ? [
|
|
10090
|
-
"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."
|
|
10091
|
-
] : []
|
|
10092
|
-
] : [];
|
|
10093
|
-
return {
|
|
10094
|
-
type: "text",
|
|
10095
|
-
text: [
|
|
10096
|
-
"[nextsteps_gate]",
|
|
10097
|
-
`Authoritative live state for this request: open todos = ${openTodos.length}.`,
|
|
10098
|
-
"You MUST omit <nextsteps> entirely while these todos remain open. Continue or finish the tracked work; do not propose unrelated follow-on work.",
|
|
10099
|
-
...todoReconciliation,
|
|
10100
|
-
"Open todo snapshot:",
|
|
10101
|
-
...todoSnapshot,
|
|
10102
|
-
"[/nextsteps_gate]"
|
|
10103
|
-
].join("\n"),
|
|
10104
|
-
cache_control: { type: "ephemeral" }
|
|
10105
|
-
};
|
|
10106
|
-
}
|
|
10107
|
-
var MAX_MEMORY_EVIDENCE_CHARS = 12e3;
|
|
10108
|
-
function buildMemoryEvidenceBlocks(ctx) {
|
|
10109
|
-
const blocks = [];
|
|
10110
|
-
let remaining = MAX_MEMORY_EVIDENCE_CHARS;
|
|
10111
|
-
for (const entry of ctx.memoryEvidence) {
|
|
10112
|
-
if (remaining <= 0) break;
|
|
10113
|
-
const text2 = entry.text.trim();
|
|
10114
|
-
if (!text2) continue;
|
|
10115
|
-
const source = entry.source.replace(/[^a-z0-9_.-]+/gi, "-").slice(0, 80) || "memory";
|
|
10116
|
-
const bounded = text2.slice(0, remaining);
|
|
10117
|
-
remaining -= bounded.length;
|
|
10118
|
-
blocks.push({
|
|
10119
|
-
type: "text",
|
|
10120
|
-
text: `[memory_evidence source="${source}"]
|
|
10121
|
-
${bounded}
|
|
10122
|
-
[/memory_evidence]`,
|
|
10123
|
-
cache_control: { type: "ephemeral" }
|
|
10124
|
-
});
|
|
10125
|
-
}
|
|
10126
|
-
return blocks;
|
|
10127
|
-
}
|
|
10128
|
-
function createAgentResponseHandler(a) {
|
|
10129
|
-
const stabilizedPromptEpochs = /* @__PURE__ */ new WeakSet();
|
|
10130
|
-
function stabilizePromptEpoch() {
|
|
10131
|
-
const prompt = a.ctx.systemPrompt;
|
|
10132
|
-
if (stabilizedPromptEpochs.has(prompt)) return;
|
|
10133
|
-
for (const block of prompt) {
|
|
10134
|
-
if (block.cache_control) Object.freeze(block.cache_control);
|
|
10135
|
-
Object.freeze(block);
|
|
10136
|
-
}
|
|
10137
|
-
Object.freeze(prompt);
|
|
10138
|
-
stabilizedPromptEpochs.add(prompt);
|
|
10139
|
-
}
|
|
10140
|
-
async function buildAndRunRequestPipeline(opts) {
|
|
10141
|
-
if (a.ctx.toolAdjacencyDirty) {
|
|
10142
|
-
const repaired = repairToolUseAdjacency(a.ctx.messages);
|
|
10143
|
-
a.ctx.toolAdjacencyDirty = false;
|
|
10144
|
-
if (repaired.report.changed) {
|
|
10145
|
-
a.ctx.state.replaceMessages(repaired.messages);
|
|
10146
|
-
a.events.emit("context.repaired", {
|
|
10147
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
10148
|
-
ctx: a.ctx,
|
|
10149
|
-
...repaired.report
|
|
10150
|
-
});
|
|
10151
|
-
a.logger.warn(
|
|
10152
|
-
`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)`
|
|
10153
|
-
);
|
|
10154
|
-
}
|
|
10155
|
-
}
|
|
10156
|
-
stabilizePromptEpoch();
|
|
10157
|
-
const volatileLedger = buildCompletedWorkLedgerBlock(a.ctx);
|
|
10158
|
-
const continuity = buildConversationContinuityBlock(a.ctx);
|
|
10159
|
-
const liveNextStepsGate = buildLiveNextStepsGateBlock(a.ctx);
|
|
10160
|
-
const memoryEvidence = buildMemoryEvidenceBlocks(a.ctx);
|
|
10161
|
-
const volatileBlocks = [
|
|
10162
|
-
volatileLedger,
|
|
10163
|
-
continuity,
|
|
10164
|
-
liveNextStepsGate,
|
|
10165
|
-
...memoryEvidence
|
|
10166
|
-
].filter((block) => block !== void 0);
|
|
10167
|
-
const system = volatileBlocks.length > 0 ? [...a.ctx.systemPrompt, ...volatileBlocks] : a.ctx.systemPrompt;
|
|
10168
|
-
await a.ctx.waitForModelTransition();
|
|
10169
|
-
const provider = a.ctx.provider;
|
|
10170
|
-
const baseReq = {
|
|
10171
|
-
model: opts.model ?? a.ctx.model,
|
|
10172
|
-
system,
|
|
10173
|
-
messages: a.ctx.messages,
|
|
10174
|
-
tools: a.tools.listForProvider(),
|
|
10175
|
-
// `maxTokens` is deliberately NOT set here. The provider adapter
|
|
10176
|
-
// resolves the ceiling from the catalog entry for the model in
|
|
10177
|
-
// `req.model`, which is the only source that stays correct across a
|
|
10178
|
-
// `/model` switch, a fallback hop, or a subagent on a model-matrix
|
|
10179
|
-
// entry — `provider.capabilities` is resolved once, for the model the
|
|
10180
|
-
// session booted on, and pinning it here would override the accurate
|
|
10181
|
-
// per-request value with a stale one. Callers that genuinely want a
|
|
10182
|
-
// smaller response (one-shot LLM helpers, compaction, the brain) still
|
|
10183
|
-
// set `maxTokens` on their own Request and keep priority over the
|
|
10184
|
-
// catalog.
|
|
10185
|
-
// Provider-agnostic cache-partition key from the stable prompt epoch.
|
|
10186
|
-
// Wires that support prompt caching (OpenAI `prompt_cache_key`) read it;
|
|
10187
|
-
// the config `ttl` is merged over this by the ModelRuntime middleware.
|
|
10188
|
-
cache: { key: deriveCachePrefixKey(a.ctx.systemPrompt) }
|
|
10189
|
-
};
|
|
10190
|
-
const request = await a.pipelines.request.run(baseReq);
|
|
10191
|
-
bindRequestProvider(request, provider);
|
|
10192
|
-
return { request, provider };
|
|
10193
|
-
}
|
|
10194
|
-
async function processResponse(raw, req, requestProvider = a.ctx.provider) {
|
|
10195
|
-
let res = raw;
|
|
10196
|
-
res = await a.pipelines.response.run(res);
|
|
10197
|
-
res = maybeAppendPendingNextSteps(a.ctx, res);
|
|
10198
|
-
a.events.emit("provider.response", {
|
|
10199
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
10200
|
-
ctx: a.ctx,
|
|
10201
|
-
model: req.model,
|
|
10202
|
-
content: res.content,
|
|
10203
|
-
usage: res.usage,
|
|
10204
|
-
stopReason: res.stopReason
|
|
10205
|
-
});
|
|
10206
|
-
a.ctx.tokenCounter.account(res.usage, req.model, requestProvider.id);
|
|
10207
|
-
if (hasMeaningfulContent(res.content)) {
|
|
10208
|
-
await a.ctx.session.append({
|
|
10209
|
-
type: "llm_response",
|
|
10210
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10211
|
-
content: res.content,
|
|
10212
|
-
stopReason: res.stopReason,
|
|
10213
|
-
usage: res.usage
|
|
10214
|
-
});
|
|
10215
|
-
a.ctx.state.appendMessage({ role: "assistant", content: res.content });
|
|
10216
|
-
if (!a.ctx.toolAdjacencyDirty) {
|
|
10217
|
-
for (const block of res.content) {
|
|
10218
|
-
if (block.type === "tool_use") {
|
|
10219
|
-
a.ctx.toolAdjacencyDirty = true;
|
|
10220
|
-
break;
|
|
10221
|
-
}
|
|
10222
|
-
}
|
|
10223
|
-
}
|
|
10224
|
-
try {
|
|
10225
|
-
await a.ctx.flushConversationJournal();
|
|
10226
|
-
await a.ctx.session.flush();
|
|
10227
|
-
} catch (err) {
|
|
10228
|
-
(a.logger.debug ?? a.logger.warn)?.(`LLM response flush failed: ${toErrorMessage(err)}`);
|
|
10229
|
-
}
|
|
10230
|
-
} else {
|
|
10231
|
-
a.logger.warn("Empty assistant response \u2014 not appended to context or session", {
|
|
10232
|
-
model: req.model,
|
|
10233
|
-
stopReason: res.stopReason,
|
|
10234
|
-
aborted: a.ctx.signal.aborted
|
|
10235
|
-
});
|
|
10236
|
-
}
|
|
10237
|
-
if (a.ctx.signal.aborted) {
|
|
10238
|
-
const parts2 = [];
|
|
10239
|
-
for (const block of res.content) {
|
|
10240
|
-
if (isTextBlock(block)) parts2.push(block.text);
|
|
10241
|
-
}
|
|
10242
|
-
return { finalText: parts2.join(""), aborted: true, done: false };
|
|
10243
|
-
}
|
|
10244
|
-
const parts = [];
|
|
10245
|
-
const streamed = requestProvider.capabilities.streaming;
|
|
10246
|
-
for (const block of res.content) {
|
|
10247
|
-
if (isTextBlock(block)) {
|
|
10248
|
-
const rendered = await a.pipelines.assistantOutput.run(block);
|
|
10249
|
-
parts.push(rendered.text);
|
|
10250
|
-
if (!streamed) a.renderer?.write(rendered);
|
|
10251
|
-
}
|
|
10252
|
-
}
|
|
10253
|
-
const finalText = parts.join("");
|
|
10254
|
-
markAssistantReferencedEvidence(a.ctx, finalText);
|
|
10255
|
-
let directive = "none";
|
|
10256
|
-
if (finalText) {
|
|
10257
|
-
directive = parseContinueDirective(finalText);
|
|
10258
|
-
}
|
|
10259
|
-
return { finalText, aborted: false, done: false, directive };
|
|
10260
|
-
}
|
|
10261
|
-
return { buildAndRunRequestPipeline, processResponse };
|
|
10262
|
-
}
|
|
10263
|
-
|
|
10264
10072
|
// src/types/runtime-capability-manifest.ts
|
|
10265
10073
|
var PLAYWRIGHT_ALIASES = {
|
|
10266
10074
|
playwright_navigate: "browser_navigate",
|
|
@@ -10543,6 +10351,493 @@ function runtimeToolReferencesFromText(text2) {
|
|
|
10543
10351
|
return [...references];
|
|
10544
10352
|
}
|
|
10545
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
|
+
|
|
10546
10841
|
// src/types/system-prompt.ts
|
|
10547
10842
|
function flattenSystemPromptRegions(regions) {
|
|
10548
10843
|
return [...regions.core, ...regions.session, ...regions.volatile];
|
|
@@ -10723,138 +11018,6 @@ function firstExistingDirSync(candidates) {
|
|
|
10723
11018
|
return candidates[0] ?? "";
|
|
10724
11019
|
}
|
|
10725
11020
|
|
|
10726
|
-
// src/core/instruction-template.ts
|
|
10727
|
-
var CANONICAL_TOOL_NAMES = new Set(
|
|
10728
|
-
RUNTIME_CAPABILITY_MANIFEST.flatMap((entry) => [...entry.tools])
|
|
10729
|
-
);
|
|
10730
|
-
var DIRECTIVE_RE = /[ \t]*<!--\s*ws:(if|else|end)\b([^>]*?)-->[ \t]*(?:\r?\n)?/g;
|
|
10731
|
-
var PLACEHOLDER_RE = /\{\{\s*(tools:)?\s*([a-zA-Z0-9_.,\s-]+?)\s*\}\}/g;
|
|
10732
|
-
function renderInstructionLayer(text2, ctx) {
|
|
10733
|
-
if (!text2) return text2;
|
|
10734
|
-
const hasDirectives = text2.includes("<!--ws:") || text2.includes("<!-- ws:");
|
|
10735
|
-
const hasPlaceholders = text2.includes("{{");
|
|
10736
|
-
if (!hasDirectives && !hasPlaceholders) return text2;
|
|
10737
|
-
const rendered = hasDirectives ? emit(parse2(text2), ctx) : text2;
|
|
10738
|
-
const substituted = hasPlaceholders ? substitute(rendered, ctx) : rendered;
|
|
10739
|
-
const guarded = ctx?.strictToolReferences ? dropLinesWithUnavailableToolReferences(
|
|
10740
|
-
substituted,
|
|
10741
|
-
ctx,
|
|
10742
|
-
/* @__PURE__ */ new Set([...CANONICAL_TOOL_NAMES, ...declaredToolNames(text2)])
|
|
10743
|
-
) : substituted;
|
|
10744
|
-
return tidy(guarded);
|
|
10745
|
-
}
|
|
10746
|
-
function declaredToolNames(text2) {
|
|
10747
|
-
const names = /* @__PURE__ */ new Set();
|
|
10748
|
-
for (const marker of text2.matchAll(/<!--\s*ws:if\b([^>]*?)-->/g)) {
|
|
10749
|
-
for (const attr of (marker[1] ?? "").matchAll(/!?tool=([A-Za-z0-9_.,-]+)/g)) {
|
|
10750
|
-
for (const name of (attr[1] ?? "").split(",")) if (name.trim()) names.add(name.trim());
|
|
10751
|
-
}
|
|
10752
|
-
}
|
|
10753
|
-
for (const placeholder of text2.matchAll(/\{\{\s*tools:\s*([^}]+)}}/g)) {
|
|
10754
|
-
for (const name of (placeholder[1] ?? "").split(",")) if (name.trim()) names.add(name.trim());
|
|
10755
|
-
}
|
|
10756
|
-
return names;
|
|
10757
|
-
}
|
|
10758
|
-
function dropLinesWithUnavailableToolReferences(text2, ctx, declared) {
|
|
10759
|
-
const unavailable = [...declared].filter((name) => !ctx.toolNames.has(name));
|
|
10760
|
-
if (unavailable.length === 0) return text2;
|
|
10761
|
-
return text2.split(/(?<=\n)/).filter((line) => !unavailable.some((name) => formattedToolMention(line, name))).join("");
|
|
10762
|
-
}
|
|
10763
|
-
function formattedToolMention(line, name) {
|
|
10764
|
-
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10765
|
-
const token = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`);
|
|
10766
|
-
if (line.split("`").some((segment, index) => {
|
|
10767
|
-
if (index % 2 !== 1) return false;
|
|
10768
|
-
if (segment.includes(`<${name}`) || segment.includes(`</${name}`)) return false;
|
|
10769
|
-
return token.test(segment);
|
|
10770
|
-
})) {
|
|
10771
|
-
return true;
|
|
10772
|
-
}
|
|
10773
|
-
return line.split("**").some((segment, index) => index % 2 === 1 && segment.trim() === name);
|
|
10774
|
-
}
|
|
10775
|
-
function parse2(text2) {
|
|
10776
|
-
const root = [];
|
|
10777
|
-
const stack = [];
|
|
10778
|
-
const current = () => {
|
|
10779
|
-
const frame = stack[stack.length - 1];
|
|
10780
|
-
if (!frame) return root;
|
|
10781
|
-
return frame.branches[frame.branches.length - 1];
|
|
10782
|
-
};
|
|
10783
|
-
const pushText = (value) => {
|
|
10784
|
-
if (value) current().push({ kind: "text", value });
|
|
10785
|
-
};
|
|
10786
|
-
DIRECTIVE_RE.lastIndex = 0;
|
|
10787
|
-
let cursor = 0;
|
|
10788
|
-
for (let m = DIRECTIVE_RE.exec(text2); m !== null; m = DIRECTIVE_RE.exec(text2)) {
|
|
10789
|
-
pushText(text2.slice(cursor, m.index));
|
|
10790
|
-
cursor = m.index + m[0].length;
|
|
10791
|
-
const keyword = m[1];
|
|
10792
|
-
if (keyword === "if") {
|
|
10793
|
-
stack.push({ test: parseCondition(m[2] ?? ""), branches: [[]] });
|
|
10794
|
-
} else if (keyword === "else") {
|
|
10795
|
-
const frame = stack[stack.length - 1];
|
|
10796
|
-
if (frame && frame.branches.length === 1) frame.branches.push([]);
|
|
10797
|
-
} else {
|
|
10798
|
-
const frame = stack.pop();
|
|
10799
|
-
if (frame) current().push({ kind: "if", test: frame.test, body: frame.branches });
|
|
10800
|
-
}
|
|
10801
|
-
}
|
|
10802
|
-
pushText(text2.slice(cursor));
|
|
10803
|
-
while (stack.length > 0) {
|
|
10804
|
-
const frame = stack.pop();
|
|
10805
|
-
current().push(...frame.branches.flat());
|
|
10806
|
-
}
|
|
10807
|
-
return root;
|
|
10808
|
-
}
|
|
10809
|
-
function parseCondition(raw) {
|
|
10810
|
-
const tokens = raw.trim().split(/\s+/).filter(Boolean);
|
|
10811
|
-
if (tokens.length === 0) return null;
|
|
10812
|
-
const attrs = [];
|
|
10813
|
-
for (const token of tokens) {
|
|
10814
|
-
const m = /^(!?)([a-zA-Z]+)=(.+)$/.exec(token);
|
|
10815
|
-
if (!m) return null;
|
|
10816
|
-
const key = (m[2] ?? "").toLowerCase();
|
|
10817
|
-
if (key !== "tool" && key !== "tier" && key !== "role") return null;
|
|
10818
|
-
const values = (m[3] ?? "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
10819
|
-
if (values.length === 0) return null;
|
|
10820
|
-
attrs.push({ key, negated: m[1] === "!", values });
|
|
10821
|
-
}
|
|
10822
|
-
return attrs;
|
|
10823
|
-
}
|
|
10824
|
-
function evaluate(test, ctx) {
|
|
10825
|
-
if (test === null || !ctx) return true;
|
|
10826
|
-
return test.every((attr) => {
|
|
10827
|
-
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");
|
|
10828
|
-
return attr.negated ? !matched : matched;
|
|
10829
|
-
});
|
|
10830
|
-
}
|
|
10831
|
-
function emit(nodes, ctx) {
|
|
10832
|
-
let out = "";
|
|
10833
|
-
for (const node of nodes) {
|
|
10834
|
-
if (node.kind === "text") {
|
|
10835
|
-
out += node.value;
|
|
10836
|
-
continue;
|
|
10837
|
-
}
|
|
10838
|
-
const branch = evaluate(node.test, ctx) ? node.body[0] : node.body[1];
|
|
10839
|
-
if (branch) out += emit(branch, ctx);
|
|
10840
|
-
}
|
|
10841
|
-
return out;
|
|
10842
|
-
}
|
|
10843
|
-
function substitute(text2, ctx) {
|
|
10844
|
-
PLACEHOLDER_RE.lastIndex = 0;
|
|
10845
|
-
return text2.replace(PLACEHOLDER_RE, (match, toolsPrefix, body) => {
|
|
10846
|
-
if (toolsPrefix) {
|
|
10847
|
-
const names = body.split(",").map((n) => n.trim()).filter(Boolean).filter((n) => !ctx || ctx.toolNames.has(n));
|
|
10848
|
-
return names.map((n) => `\`${n}\``).join(", ");
|
|
10849
|
-
}
|
|
10850
|
-
const value = ctx?.vars?.[body.trim()];
|
|
10851
|
-
return value === void 0 ? match : String(value);
|
|
10852
|
-
});
|
|
10853
|
-
}
|
|
10854
|
-
function tidy(text2) {
|
|
10855
|
-
return text2.replace(/(\r?\n){3,}/g, "$1$1");
|
|
10856
|
-
}
|
|
10857
|
-
|
|
10858
11021
|
// src/core/modes/default.ts
|
|
10859
11022
|
import { readFileSync as readFileSync5, statSync as statSync4 } from "node:fs";
|
|
10860
11023
|
import * as path23 from "node:path";
|
|
@@ -10889,61 +11052,6 @@ function isDirectory(candidate) {
|
|
|
10889
11052
|
}
|
|
10890
11053
|
}
|
|
10891
11054
|
|
|
10892
|
-
// src/core/system-prompt-blocks.ts
|
|
10893
|
-
var SYSTEM_BLOCK_SOURCE = /* @__PURE__ */ new WeakMap();
|
|
10894
|
-
function tagBlock(block, source) {
|
|
10895
|
-
SYSTEM_BLOCK_SOURCE.set(block, source);
|
|
10896
|
-
return block;
|
|
10897
|
-
}
|
|
10898
|
-
function shortSessionId(sessionId) {
|
|
10899
|
-
const leaf = sessionId.split("/").pop() ?? sessionId;
|
|
10900
|
-
return leaf.length > 12 ? `${leaf.slice(0, 12)}\u2026` : leaf;
|
|
10901
|
-
}
|
|
10902
|
-
function instructionSection(bundle, key, vars = {}, tplCtx) {
|
|
10903
|
-
const template = bundle.sections?.[key];
|
|
10904
|
-
if (!template) return "";
|
|
10905
|
-
return renderInstructionLayer(
|
|
10906
|
-
template,
|
|
10907
|
-
tplCtx ? { ...tplCtx, vars: { ...tplCtx.vars, ...vars } } : void 0
|
|
10908
|
-
).replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, name) => {
|
|
10909
|
-
const value = vars[name];
|
|
10910
|
-
return value === void 0 ? match : String(value);
|
|
10911
|
-
});
|
|
10912
|
-
}
|
|
10913
|
-
function renderToolSelectionBoundary(tool) {
|
|
10914
|
-
const selection = tool.selection;
|
|
10915
|
-
if (!selection?.doNotUseWhen.trim()) return "";
|
|
10916
|
-
const alternatives = selection.useInstead?.filter(Boolean) ?? [];
|
|
10917
|
-
const instead = alternatives.length > 0 ? ` Use ${alternatives.map((name) => `\`${name}\``).join(" or ")} instead.` : "";
|
|
10918
|
-
return `Do not use when ${selection.doNotUseWhen.trim()}${instead}`;
|
|
10919
|
-
}
|
|
10920
|
-
function agentsFingerprint(agents) {
|
|
10921
|
-
if (!agents || agents.length === 0) return "0";
|
|
10922
|
-
let h = 2166136261;
|
|
10923
|
-
for (const a of agents) {
|
|
10924
|
-
const fields = [
|
|
10925
|
-
a.agentId,
|
|
10926
|
-
a.name,
|
|
10927
|
-
a.source,
|
|
10928
|
-
a.sessionId,
|
|
10929
|
-
a.status,
|
|
10930
|
-
a.currentTask,
|
|
10931
|
-
a.currentTool,
|
|
10932
|
-
a.online ? "1" : "0"
|
|
10933
|
-
];
|
|
10934
|
-
for (const field of fields) {
|
|
10935
|
-
const value = field ?? "";
|
|
10936
|
-
for (let i = 0; i < value.length; i++) {
|
|
10937
|
-
h ^= value.charCodeAt(i);
|
|
10938
|
-
h = Math.imul(h, 16777619) >>> 0;
|
|
10939
|
-
}
|
|
10940
|
-
h ^= 255;
|
|
10941
|
-
h = Math.imul(h, 16777619) >>> 0;
|
|
10942
|
-
}
|
|
10943
|
-
}
|
|
10944
|
-
return `${agents.length}:${h.toString(36)}`;
|
|
10945
|
-
}
|
|
10946
|
-
|
|
10947
11055
|
// src/core/system-prompt-environment.ts
|
|
10948
11056
|
import * as os6 from "node:os";
|
|
10949
11057
|
import * as path25 from "node:path";
|
|
@@ -11231,24 +11339,14 @@ async function renderDomainGlossary(ctx, memory, options = {}) {
|
|
|
11231
11339
|
);
|
|
11232
11340
|
return lines.join("\n");
|
|
11233
11341
|
}
|
|
11234
|
-
function makeDomainGlossaryContributor(glossary) {
|
|
11235
|
-
return async (ctx) => {
|
|
11236
|
-
const text2 = await renderDomainGlossary(ctx, glossary.memory);
|
|
11237
|
-
if (!text2) return [];
|
|
11238
|
-
return [{ type: "text", text: text2 }];
|
|
11239
|
-
};
|
|
11240
|
-
}
|
|
11241
11342
|
function parseTermEntry(text2) {
|
|
11242
11343
|
const trimmed = text2.trim();
|
|
11243
|
-
const
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
definition: trimmed.slice(idx + sep10.length).trim()
|
|
11250
|
-
};
|
|
11251
|
-
}
|
|
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
|
+
};
|
|
11252
11350
|
}
|
|
11253
11351
|
return { term: trimmed, definition: "" };
|
|
11254
11352
|
}
|
|
@@ -11792,7 +11890,13 @@ var DefaultSystemPromptBuilder = class {
|
|
|
11792
11890
|
_lastCatalogTools;
|
|
11793
11891
|
/** Cached rendered online agents string, keyed by content fingerprint. */
|
|
11794
11892
|
_lastOnlineAgents;
|
|
11795
|
-
/**
|
|
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
|
+
*/
|
|
11796
11900
|
_toolsUsageCache;
|
|
11797
11901
|
_instructionBundle;
|
|
11798
11902
|
/**
|
|
@@ -11957,6 +12061,26 @@ var DefaultSystemPromptBuilder = class {
|
|
|
11957
12061
|
volatile.push(tagBlock({ type: "text", text: glossary }, "glossary"));
|
|
11958
12062
|
}
|
|
11959
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
|
+
}
|
|
11960
12084
|
if (!ctx.subagent) {
|
|
11961
12085
|
session.push(
|
|
11962
12086
|
tagBlock(
|
|
@@ -12051,9 +12175,8 @@ var DefaultSystemPromptBuilder = class {
|
|
|
12051
12175
|
const instructions = await this.instructions();
|
|
12052
12176
|
const tpl = tplCtx ?? this.templateContext(ctx);
|
|
12053
12177
|
const section = (key, vars = {}) => instructionSection(instructions, key, vars, tpl);
|
|
12054
|
-
const agentsHash = agentsFingerprint(ctx.onlineAgents);
|
|
12055
12178
|
const tier = this.tier;
|
|
12056
|
-
if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.
|
|
12179
|
+
if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.tier === tier) {
|
|
12057
12180
|
return this._toolsUsageCache.text;
|
|
12058
12181
|
}
|
|
12059
12182
|
const byCat = /* @__PURE__ */ new Map();
|
|
@@ -12131,7 +12254,7 @@ ${hint.trim()}`);
|
|
|
12131
12254
|
(t2) => t2.name === "mailbox" || t2.name === "mail_send" || t2.name === "mail_inbox"
|
|
12132
12255
|
);
|
|
12133
12256
|
if (hasMailbox) {
|
|
12134
|
-
const onlineAgentsInfo =
|
|
12257
|
+
const onlineAgentsInfo = "";
|
|
12135
12258
|
const hasMailboxPowerTool = tools.some((t2) => t2.name === "mailbox");
|
|
12136
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";
|
|
12137
12260
|
const mailInboxCommand = tools.some((t2) => t2.name === "mail_inbox") ? "`mail_inbox`" : "`mailbox action=check`";
|
|
@@ -12180,7 +12303,7 @@ ${hint.trim()}`);
|
|
|
12180
12303
|
}
|
|
12181
12304
|
}
|
|
12182
12305
|
const text2 = lines.join("\n");
|
|
12183
|
-
this._toolsUsageCache = { toolsRef: tools,
|
|
12306
|
+
this._toolsUsageCache = { toolsRef: tools, tier, text: text2 };
|
|
12184
12307
|
return text2;
|
|
12185
12308
|
}
|
|
12186
12309
|
renderOnlineAgents(agents) {
|
|
@@ -12240,6 +12363,8 @@ var SYSTEM_BLOCK_SOURCES = [
|
|
|
12240
12363
|
"leader-after-task",
|
|
12241
12364
|
"contributor",
|
|
12242
12365
|
"ledger",
|
|
12366
|
+
"glossary",
|
|
12367
|
+
"peers",
|
|
12243
12368
|
"nextsteps",
|
|
12244
12369
|
"other"
|
|
12245
12370
|
];
|
|
@@ -23658,6 +23783,7 @@ var TOOLS = {
|
|
|
23658
23783
|
"glob",
|
|
23659
23784
|
"search",
|
|
23660
23785
|
"tree",
|
|
23786
|
+
"diff",
|
|
23661
23787
|
"write",
|
|
23662
23788
|
"edit",
|
|
23663
23789
|
"replace",
|
|
@@ -24689,7 +24815,7 @@ var VERIFY_AGENTS = [
|
|
|
24689
24815
|
id: "e2e",
|
|
24690
24816
|
name: "E2E",
|
|
24691
24817
|
role: "e2e",
|
|
24692
|
-
tools: [...TOOLS.build, "fetch", ...SPECIALIST_TOOLS.browser],
|
|
24818
|
+
tools: [...TOOLS.build, "fetch", "e2e_plan", ...SPECIALIST_TOOLS.browser],
|
|
24693
24819
|
prompt: agentPrompt("e2e")
|
|
24694
24820
|
},
|
|
24695
24821
|
budget: HEAVY_BUDGET,
|
|
@@ -25202,7 +25328,7 @@ var DOMAIN_AGENTS = [
|
|
|
25202
25328
|
id: "designer",
|
|
25203
25329
|
name: "Designer",
|
|
25204
25330
|
role: "designer",
|
|
25205
|
-
tools: [...TOOLS.docs],
|
|
25331
|
+
tools: [...TOOLS.docs, "design"],
|
|
25206
25332
|
prompt: agentPrompt("designer")
|
|
25207
25333
|
},
|
|
25208
25334
|
budget: MEDIUM_BUDGET,
|
|
@@ -62878,7 +63004,10 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
62878
63004
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
62879
63005
|
// The separator group is optional so the attached form (the common one) matches.
|
|
62880
63006
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
62881
|
-
|
|
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,
|
|
62882
63011
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
62883
63012
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
62884
63013
|
// redaction function (false positive = cosmetic noise; false negative = leak).
|
|
@@ -62886,8 +63015,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
62886
63015
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
62887
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,
|
|
62888
63017
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
62889
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
62890
|
-
|
|
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
|
|
62891
63021
|
];
|
|
62892
63022
|
function redactCommand(cmd) {
|
|
62893
63023
|
let result = cmd;
|
|
@@ -80134,7 +80264,10 @@ var DefaultPluginAPI = class {
|
|
|
80134
80264
|
}
|
|
80135
80265
|
};
|
|
80136
80266
|
this.tools = {
|
|
80137
|
-
register: (t2) =>
|
|
80267
|
+
register: (t2) => {
|
|
80268
|
+
tr.register(t2, owner);
|
|
80269
|
+
tr.exposeToProvider(t2.name);
|
|
80270
|
+
},
|
|
80138
80271
|
unregister: (name) => {
|
|
80139
80272
|
assertCanMutateTool(name, "unregister");
|
|
80140
80273
|
return tr.unregister(name);
|
|
@@ -81212,6 +81345,15 @@ function resolveAutoReviewConfig(cfg, sessionConfig) {
|
|
|
81212
81345
|
maxCascadeDepth: cfg.maxCascadeDepth ?? DEFAULT_MAX_CASCADE_DEPTH
|
|
81213
81346
|
};
|
|
81214
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
|
+
}
|
|
81215
81357
|
function parseReviewSeverity(text2) {
|
|
81216
81358
|
const result = { critical: 0, high: 0, medium: 0 };
|
|
81217
81359
|
if (!text2) return result;
|
|
@@ -81228,9 +81370,20 @@ function parseReviewSeverity(text2) {
|
|
|
81228
81370
|
}
|
|
81229
81371
|
return result;
|
|
81230
81372
|
}
|
|
81231
|
-
function decideCascadeAgents(text2, severities) {
|
|
81373
|
+
function decideCascadeAgents(text2, severities, findings) {
|
|
81232
81374
|
const agents = /* @__PURE__ */ new Set();
|
|
81233
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
|
+
}
|
|
81234
81387
|
const securityKeywords = [
|
|
81235
81388
|
"injection",
|
|
81236
81389
|
"xss",
|
|
@@ -81603,7 +81756,9 @@ function createAutoReviewPlugin() {
|
|
|
81603
81756
|
maxFiles: cfg.maxFilesPerBatch,
|
|
81604
81757
|
autoFix: "off",
|
|
81605
81758
|
cascadeOn: "off",
|
|
81606
|
-
maxCascadeDepth: 0
|
|
81759
|
+
maxCascadeDepth: 0,
|
|
81760
|
+
fallbackModels: [...cfg.fallbackModels],
|
|
81761
|
+
fallbackProfile: void 0
|
|
81607
81762
|
},
|
|
81608
81763
|
files: filesWithContent,
|
|
81609
81764
|
activeTodos: ctxTodos,
|
|
@@ -81708,7 +81863,9 @@ function createAutoReviewPlugin() {
|
|
|
81708
81863
|
maxFiles: cfg.maxFilesPerBatch,
|
|
81709
81864
|
autoFix: "off",
|
|
81710
81865
|
cascadeOn: "off",
|
|
81711
|
-
maxCascadeDepth: 0
|
|
81866
|
+
maxCascadeDepth: 0,
|
|
81867
|
+
fallbackModels: [...cfg.fallbackModels],
|
|
81868
|
+
fallbackProfile: void 0
|
|
81712
81869
|
},
|
|
81713
81870
|
files: filesWithContent,
|
|
81714
81871
|
cascadeOn: cfg.cascadeOn,
|
|
@@ -81750,23 +81907,31 @@ function createAutoReviewPlugin() {
|
|
|
81750
81907
|
if (!p.reviewText) return;
|
|
81751
81908
|
const cascadeOn = p.bundle.cascadeOn ?? "off";
|
|
81752
81909
|
if (cascadeOn === "off") return;
|
|
81753
|
-
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);
|
|
81754
81913
|
const threshold = shouldCascade(cascadeOn, severities);
|
|
81755
81914
|
if (!threshold) return;
|
|
81756
|
-
const agents = decideCascadeAgents(
|
|
81915
|
+
const agents = decideCascadeAgents(
|
|
81916
|
+
p.reviewText,
|
|
81917
|
+
severities,
|
|
81918
|
+
parsed ? verifiedFindings : void 0
|
|
81919
|
+
);
|
|
81757
81920
|
if (agents.length === 0) {
|
|
81758
81921
|
return;
|
|
81759
81922
|
}
|
|
81760
81923
|
const cascadePayload = {
|
|
81761
81924
|
bundle: p.bundle,
|
|
81925
|
+
...p.reportId ? { reportId: p.reportId } : {},
|
|
81762
81926
|
reviewText: p.reviewText,
|
|
81763
81927
|
severities,
|
|
81764
81928
|
threshold,
|
|
81765
|
-
agents
|
|
81929
|
+
agents,
|
|
81930
|
+
...parsed ? { verifiedFindings } : {}
|
|
81766
81931
|
};
|
|
81767
81932
|
api.emitCustom("chimera.cascade_needed", cascadePayload);
|
|
81768
81933
|
api.log.info(
|
|
81769
|
-
`[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))` : ""}`
|
|
81770
81935
|
);
|
|
81771
81936
|
} catch (err) {
|
|
81772
81937
|
api.log.warn(
|
|
@@ -81798,12 +81963,88 @@ init_review_finding_store();
|
|
|
81798
81963
|
// src/plugins/review-finding-parser.ts
|
|
81799
81964
|
init_review_finding_types();
|
|
81800
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
|
+
}
|
|
81801
82029
|
var SUGGEST_LINE = /^\s*(?:→|->|=>)\s*(.+)$/;
|
|
81802
82030
|
var DURATION_LINE = /^Duration:\s*(\d+)s\s*$/im;
|
|
81803
82031
|
function parseChimeraReviewReport(reportText, context = {}) {
|
|
81804
82032
|
if (!reportText || reportText.trim().length === 0) {
|
|
81805
82033
|
return { findings: [], unparseableCount: 0 };
|
|
81806
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
|
+
}
|
|
81807
82048
|
const findings = [];
|
|
81808
82049
|
const reportId = context.reportId ?? randomUUID40();
|
|
81809
82050
|
let unparseableCount = 0;
|
|
@@ -81935,14 +82176,92 @@ function normalizeFindingSource(reviewType) {
|
|
|
81935
82176
|
return "chimera";
|
|
81936
82177
|
}
|
|
81937
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
|
+
}
|
|
81938
82205
|
|
|
81939
82206
|
// src/plugins/review-report-integration.ts
|
|
81940
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
|
|
81941
82260
|
init_review_report_store();
|
|
81942
82261
|
async function persistReviewReport(payload, reportId, projectDir) {
|
|
81943
82262
|
const store = new JsonlReportStore(projectDir);
|
|
81944
82263
|
const existed = await store.get(reportId);
|
|
81945
|
-
const source =
|
|
82264
|
+
const source = classifyChimeraReviewSource(payload.bundle);
|
|
81946
82265
|
const agentId = payload.bundle.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review";
|
|
81947
82266
|
const sessionId = payload.sessionId ?? payload.cwd;
|
|
81948
82267
|
const model = payload.bundle.config.model;
|
|
@@ -81952,7 +82271,13 @@ async function persistReviewReport(payload, reportId, projectDir) {
|
|
|
81952
82271
|
status: f.status
|
|
81953
82272
|
}));
|
|
81954
82273
|
const reviewStatus = payload.status === "success" ? "success" : "failed";
|
|
81955
|
-
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 };
|
|
81956
82281
|
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
81957
82282
|
for (const finding of parsed.findings) {
|
|
81958
82283
|
counts[finding.severity]++;
|
|
@@ -81970,7 +82295,12 @@ async function persistReviewReport(payload, reportId, projectDir) {
|
|
|
81970
82295
|
unparseableCount: parsed.unparseableCount,
|
|
81971
82296
|
durationSeconds: parsed.durationSeconds,
|
|
81972
82297
|
rawText: payload.reviewText,
|
|
81973
|
-
...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 } : {}
|
|
81974
82304
|
};
|
|
81975
82305
|
await store.persist(input);
|
|
81976
82306
|
if (reviewStatus === "success" && parsed.findings.length === 0 && parsed.unparseableCount === 0 && isExplicitAllClearReview(payload.reviewText)) {
|
|
@@ -82033,13 +82363,6 @@ async function syncReportReopen(reportId, projectDir, actor, reason) {
|
|
|
82033
82363
|
});
|
|
82034
82364
|
return { reportId, reopened: true, previousLifecycle: report.lifecycle };
|
|
82035
82365
|
}
|
|
82036
|
-
function classifySource(payload) {
|
|
82037
|
-
const cascadeDepth = payload.bundle.cascadeDepth ?? 0;
|
|
82038
|
-
if (cascadeDepth > 0) return "cascade";
|
|
82039
|
-
const cascadeOn = payload.bundle.cascadeOn;
|
|
82040
|
-
if (cascadeOn !== void 0 && cascadeOn !== "off") return "auto";
|
|
82041
|
-
return "chimera";
|
|
82042
|
-
}
|
|
82043
82366
|
|
|
82044
82367
|
// src/plugins/review-finding-commands.ts
|
|
82045
82368
|
async function executeFindingCommand(args, ctx) {
|
|
@@ -82369,6 +82692,18 @@ async function showReport(id, ctx) {
|
|
|
82369
82692
|
`**Review status:** ${report.reviewStatus}`,
|
|
82370
82693
|
...report.cascadeDepth !== void 0 ? [`**Cascade depth:** ${report.cascadeDepth}`] : [],
|
|
82371
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
|
+
] : [],
|
|
82372
82707
|
"",
|
|
82373
82708
|
"**Severity counts:**",
|
|
82374
82709
|
` \u{1F534} Critical: ${report.counts.critical}`,
|
|
@@ -82477,7 +82812,9 @@ function resolveChimeraConfig(cfg, sessionProvider, sessionModel) {
|
|
|
82477
82812
|
maxFiles: cfg.maxFiles ?? DEFAULT_MAX_FILES,
|
|
82478
82813
|
autoFix: cfg.autoFix ?? "off",
|
|
82479
82814
|
cascadeOn: cfg.cascadeOn ?? DEFAULT_CASCADE_ON,
|
|
82480
|
-
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
|
|
82481
82818
|
};
|
|
82482
82819
|
}
|
|
82483
82820
|
var CHIMERA_REVIEW_PROMPT = readBundledInstructionText("llm/chimera-review.md");
|
|
@@ -84141,49 +84478,6 @@ function dim(s) {
|
|
|
84141
84478
|
return `\x1B[2m${s}\x1B[0m`;
|
|
84142
84479
|
}
|
|
84143
84480
|
|
|
84144
|
-
// src/plugins/review-finding-integration.ts
|
|
84145
|
-
init_review_finding_store();
|
|
84146
|
-
async function integrateFindings(payload, projectDir, reportId) {
|
|
84147
|
-
if (!payload.reviewText || payload.reviewText.trim().length === 0) {
|
|
84148
|
-
return { created: 0, relinked: 0, reopened: 0, totalFindings: 0, unparseableCount: 0 };
|
|
84149
|
-
}
|
|
84150
|
-
const store = new JsonlFindingStore(projectDir);
|
|
84151
|
-
const source = (payload.bundle.cascadeDepth ?? 0) > 0 ? "cascade" : payload.bundle.cascadeOn !== void 0 && payload.bundle.cascadeOn !== "off" ? "auto" : "chimera";
|
|
84152
|
-
const agentId = payload.bundle.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review";
|
|
84153
|
-
const sessionId = payload.sessionId ?? payload.cwd;
|
|
84154
|
-
const model = payload.bundle.config.model;
|
|
84155
|
-
const parsed = parseChimeraReviewReport(payload.reviewText, {
|
|
84156
|
-
sessionId,
|
|
84157
|
-
agentId,
|
|
84158
|
-
reviewerModel: model,
|
|
84159
|
-
reviewType: source,
|
|
84160
|
-
reportId
|
|
84161
|
-
});
|
|
84162
|
-
if (parsed.findings.length === 0) {
|
|
84163
|
-
return {
|
|
84164
|
-
created: 0,
|
|
84165
|
-
relinked: 0,
|
|
84166
|
-
reopened: 0,
|
|
84167
|
-
totalFindings: 0,
|
|
84168
|
-
unparseableCount: parsed.unparseableCount
|
|
84169
|
-
};
|
|
84170
|
-
}
|
|
84171
|
-
const result = await store.upsert(parsed.findings, {
|
|
84172
|
-
sessionId,
|
|
84173
|
-
reportId,
|
|
84174
|
-
agentId,
|
|
84175
|
-
model
|
|
84176
|
-
});
|
|
84177
|
-
return {
|
|
84178
|
-
created: result.created,
|
|
84179
|
-
relinked: result.relinked,
|
|
84180
|
-
reopened: result.reopened,
|
|
84181
|
-
reportId,
|
|
84182
|
-
totalFindings: parsed.findings.length,
|
|
84183
|
-
unparseableCount: parsed.unparseableCount
|
|
84184
|
-
};
|
|
84185
|
-
}
|
|
84186
|
-
|
|
84187
84481
|
// src/index.ts
|
|
84188
84482
|
init_review_finding_store();
|
|
84189
84483
|
init_review_report_store();
|
|
@@ -92778,7 +93072,7 @@ function createFallbackChainManageTool(opts) {
|
|
|
92778
93072
|
name: FALLBACK_CHAIN_MANAGE_TOOL_NAME,
|
|
92779
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.",
|
|
92780
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).',
|
|
92781
|
-
category: "
|
|
93075
|
+
category: "config",
|
|
92782
93076
|
inputSchema: FALLBACK_CHAIN_SCHEMA,
|
|
92783
93077
|
permission: "auto",
|
|
92784
93078
|
mutating: true,
|
|
@@ -92928,7 +93222,7 @@ function createFavoriteManageTool(opts) {
|
|
|
92928
93222
|
name: FAVORITE_MANAGE_TOOL_NAME,
|
|
92929
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.",
|
|
92930
93224
|
usageHint: 'Start with "list" to see current favorites. Use "add <provider/model>" to add. Use "remove <index|ref>" to remove.',
|
|
92931
|
-
category: "
|
|
93225
|
+
category: "config",
|
|
92932
93226
|
inputSchema: FAVORITE_MANAGE_SCHEMA,
|
|
92933
93227
|
permission: "auto",
|
|
92934
93228
|
mutating: true,
|
|
@@ -93008,7 +93302,7 @@ async function storeProviderKey(providers, input, keyValue, opts) {
|
|
|
93008
93302
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
93009
93303
|
});
|
|
93010
93304
|
entry.apiKeys = existingKeys;
|
|
93011
|
-
entry.apiKey
|
|
93305
|
+
delete entry.apiKey;
|
|
93012
93306
|
if (input.setActive !== false) {
|
|
93013
93307
|
entry.activeKey = label;
|
|
93014
93308
|
}
|
|
@@ -93051,7 +93345,7 @@ function createSystemConfigViewTool(opts) {
|
|
|
93051
93345
|
name: SYSTEM_CONFIG_VIEW_TOOL_NAME,
|
|
93052
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.",
|
|
93053
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.',
|
|
93054
|
-
category: "
|
|
93348
|
+
category: "config",
|
|
93055
93349
|
inputSchema: SYSTEM_CONFIG_VIEW_SCHEMA,
|
|
93056
93350
|
permission: "auto",
|
|
93057
93351
|
mutating: false,
|
|
@@ -93355,7 +93649,7 @@ function createFallbackProfileManageTool(opts) {
|
|
|
93355
93649
|
name: FALLBACK_PROFILE_MANAGE_TOOL_NAME,
|
|
93356
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.",
|
|
93357
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.',
|
|
93358
|
-
category: "
|
|
93652
|
+
category: "config",
|
|
93359
93653
|
inputSchema: FALLBACK_PROFILE_SCHEMA,
|
|
93360
93654
|
permission: "auto",
|
|
93361
93655
|
mutating: true,
|
|
@@ -93466,7 +93760,7 @@ function createAgentModelAssignTool(opts) {
|
|
|
93466
93760
|
name: AGENT_MODEL_ASSIGN_TOOL_NAME,
|
|
93467
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.",
|
|
93468
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.',
|
|
93469
|
-
category: "
|
|
93763
|
+
category: "config",
|
|
93470
93764
|
inputSchema: AGENT_MODEL_ASSIGN_SCHEMA,
|
|
93471
93765
|
permission: "auto",
|
|
93472
93766
|
mutating: true,
|
|
@@ -93614,7 +93908,7 @@ function createProviderManageTool(opts) {
|
|
|
93614
93908
|
name: PROVIDER_MANAGE_TOOL_NAME,
|
|
93615
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.",
|
|
93616
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.',
|
|
93617
|
-
category: "
|
|
93911
|
+
category: "config",
|
|
93618
93912
|
inputSchema: PROVIDER_MANAGE_SCHEMA,
|
|
93619
93913
|
permission: "auto",
|
|
93620
93914
|
mutating: true,
|
|
@@ -93763,9 +94057,11 @@ function createProviderKeySetTool(opts) {
|
|
|
93763
94057
|
name: PROVIDER_KEY_SET_TOOL_NAME,
|
|
93764
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.",
|
|
93765
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.',
|
|
93766
|
-
category: "
|
|
94060
|
+
category: "config",
|
|
93767
94061
|
inputSchema: PROVIDER_KEY_SET_SCHEMA,
|
|
93768
|
-
|
|
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",
|
|
93769
94065
|
mutating: true,
|
|
93770
94066
|
riskTier: "standard",
|
|
93771
94067
|
icon: "settings",
|
|
@@ -93861,7 +94157,7 @@ function createLeaderModelSetTool(opts) {
|
|
|
93861
94157
|
name: LEADER_MODEL_SET_TOOL_NAME,
|
|
93862
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).',
|
|
93863
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.',
|
|
93864
|
-
category: "
|
|
94160
|
+
category: "config",
|
|
93865
94161
|
inputSchema: LEADER_MODEL_SET_SCHEMA,
|
|
93866
94162
|
permission: "auto",
|
|
93867
94163
|
mutating: true,
|
|
@@ -93885,11 +94181,21 @@ function createLeaderModelSetTool(opts) {
|
|
|
93885
94181
|
if (!input.provider || !input.model) {
|
|
93886
94182
|
return { status: "error", message: 'Provide "provider" and "model" for the leader.' };
|
|
93887
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
|
+
}
|
|
93888
94193
|
await opts.updateConfig((cfg) => {
|
|
93889
94194
|
cfg.provider = input.provider;
|
|
93890
94195
|
cfg.model = input.model;
|
|
93891
94196
|
});
|
|
93892
|
-
|
|
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}` };
|
|
93893
94199
|
}
|
|
93894
94200
|
if (input.action === "profile") {
|
|
93895
94201
|
if (!input.profile) {
|
|
@@ -93908,15 +94214,25 @@ function createLeaderModelSetTool(opts) {
|
|
|
93908
94214
|
return { status: "error", message: `Cannot parse "${first}" as a valid model reference.` };
|
|
93909
94215
|
}
|
|
93910
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
|
+
}
|
|
93911
94226
|
await opts.updateConfig((cfg) => {
|
|
93912
94227
|
cfg.provider = provider;
|
|
93913
94228
|
cfg.model = model;
|
|
93914
94229
|
cfg.fallbackModels = rest;
|
|
93915
94230
|
});
|
|
94231
|
+
const profileLiveNote = opts.switchProviderAndModel ? "" : "\n (config updated \u2014 the live session keeps its current model until restart or /setmodel)";
|
|
93916
94232
|
return {
|
|
93917
94233
|
status: "ok",
|
|
93918
94234
|
message: `\u2713 Leader \u2192 ${provider}/${model} (profile: ${input.profile})` + (rest.length > 0 ? `
|
|
93919
|
-
Fallback chain: ${rest.join(" \u2192 ")}` : "")
|
|
94235
|
+
Fallback chain: ${rest.join(" \u2192 ")}` : "") + profileLiveNote
|
|
93920
94236
|
};
|
|
93921
94237
|
}
|
|
93922
94238
|
if (input.action === "toggle") {
|
|
@@ -94087,20 +94403,22 @@ async function runEnable(name, deps) {
|
|
|
94087
94403
|
const known = Object.keys(all).join(", ");
|
|
94088
94404
|
return `Unknown server "${name}". Available presets: ${known}`;
|
|
94089
94405
|
}
|
|
94090
|
-
|
|
94406
|
+
const persistEnabled = () => updateJsonObjectFile(deps.configPath, (full) => {
|
|
94091
94407
|
const current = isMcpServerRecord(full.mcpServers) ? full.mcpServers : {};
|
|
94092
94408
|
setJsonPath(full, ["mcpServers", name], { ...current[name], ...cfg, enabled: true });
|
|
94093
94409
|
});
|
|
94094
94410
|
try {
|
|
94095
94411
|
const live = deps.registry.describe().find((s) => s.name === name);
|
|
94096
94412
|
if (live && live.state === "connected") {
|
|
94097
|
-
|
|
94413
|
+
await persistEnabled();
|
|
94414
|
+
return `Server "${name}" is already running (${live.toolCount} tools registered).`;
|
|
94098
94415
|
}
|
|
94099
94416
|
await deps.registry.start({ ...cfg, enabled: true });
|
|
94417
|
+
await persistEnabled();
|
|
94100
94418
|
const updated = deps.registry.describe().find((s) => s.name === name);
|
|
94101
|
-
return
|
|
94419
|
+
return `Enabled and started "${name}"${updated ? ` (${updated.toolCount} tools registered).` : "."}`;
|
|
94102
94420
|
} catch (err) {
|
|
94103
|
-
return
|
|
94421
|
+
return `Failed to start "${name}": ${toErrorMessage(err)}. Config was left unchanged (server stays disabled).`;
|
|
94104
94422
|
}
|
|
94105
94423
|
}
|
|
94106
94424
|
async function runDisable(name, deps) {
|
|
@@ -94174,34 +94492,34 @@ function isMcpServerRecord(value) {
|
|
|
94174
94492
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
94175
94493
|
}
|
|
94176
94494
|
function bold(s) {
|
|
94177
|
-
return
|
|
94495
|
+
return s;
|
|
94178
94496
|
}
|
|
94179
94497
|
function dim2(s) {
|
|
94180
|
-
return
|
|
94498
|
+
return s;
|
|
94181
94499
|
}
|
|
94182
94500
|
function green(s) {
|
|
94183
|
-
return
|
|
94501
|
+
return s;
|
|
94184
94502
|
}
|
|
94185
94503
|
function yellow(s) {
|
|
94186
|
-
return
|
|
94504
|
+
return s;
|
|
94187
94505
|
}
|
|
94188
94506
|
function red(s) {
|
|
94189
|
-
return
|
|
94507
|
+
return s;
|
|
94190
94508
|
}
|
|
94191
94509
|
function badge(state) {
|
|
94192
94510
|
switch (state) {
|
|
94193
94511
|
case "connected":
|
|
94194
|
-
return
|
|
94512
|
+
return "\u25CF connected";
|
|
94195
94513
|
case "connecting":
|
|
94196
|
-
return
|
|
94514
|
+
return "\u25D0 connecting";
|
|
94197
94515
|
case "reconnecting":
|
|
94198
|
-
return
|
|
94516
|
+
return "\u25D1 reconnecting";
|
|
94199
94517
|
case "disconnected":
|
|
94200
|
-
return
|
|
94518
|
+
return "\u25CB disconnected";
|
|
94201
94519
|
case "failed":
|
|
94202
|
-
return
|
|
94520
|
+
return "\u2717 failed";
|
|
94203
94521
|
default:
|
|
94204
|
-
return
|
|
94522
|
+
return state;
|
|
94205
94523
|
}
|
|
94206
94524
|
}
|
|
94207
94525
|
|
|
@@ -94243,13 +94561,19 @@ function createMcpUseTool(opts) {
|
|
|
94243
94561
|
const servers = registry2.describe();
|
|
94244
94562
|
const serverInfo = servers.find((s) => s.name === serverName);
|
|
94245
94563
|
if (!serverInfo) {
|
|
94246
|
-
|
|
94564
|
+
throw new Error(
|
|
94565
|
+
`Server "${serverName}" not found. Available: ${servers.map((s) => s.name).join(", ") || "none"}.`
|
|
94566
|
+
);
|
|
94247
94567
|
}
|
|
94248
94568
|
if (serverInfo.state !== "connected") {
|
|
94249
|
-
|
|
94569
|
+
throw new Error(
|
|
94570
|
+
`Server "${serverName}" is not connected (state: ${serverInfo.state}). Use \`mcp_control({ action: "enable", server: "${serverName}" })\` first.`
|
|
94571
|
+
);
|
|
94250
94572
|
}
|
|
94251
|
-
|
|
94252
|
-
|
|
94573
|
+
const alreadyActive = registry2.isActivated?.(serverName) === true;
|
|
94574
|
+
const didActivate = !alreadyActive && Boolean(registry2.activateServer);
|
|
94575
|
+
if (didActivate) {
|
|
94576
|
+
registry2.activateServer?.(serverName);
|
|
94253
94577
|
}
|
|
94254
94578
|
try {
|
|
94255
94579
|
const qualifiedName = mcpQualifiedToolName(serverName, toolName);
|
|
@@ -94257,7 +94581,7 @@ function createMcpUseTool(opts) {
|
|
|
94257
94581
|
if (!mcpTool) {
|
|
94258
94582
|
const allTools = toolRegistry.list().filter((t2) => t2.name.startsWith(mcpServerToolPrefix(serverName))).map((t2) => t2.name.replace(mcpServerToolPrefix(serverName), ""));
|
|
94259
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.`;
|
|
94260
|
-
|
|
94584
|
+
throw new Error(`Tool "${toolName}" not found on server "${serverName}". ${hint}`);
|
|
94261
94585
|
}
|
|
94262
94586
|
const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
94263
94587
|
if (typeof governedExecute !== "function") {
|
|
@@ -94267,7 +94591,7 @@ function createMcpUseTool(opts) {
|
|
|
94267
94591
|
if (!result.success) throw new Error(result.error ?? "MCP tool execution failed");
|
|
94268
94592
|
return result.result;
|
|
94269
94593
|
} finally {
|
|
94270
|
-
if (registry2.deactivateServer) {
|
|
94594
|
+
if (didActivate && registry2.deactivateServer) {
|
|
94271
94595
|
registry2.deactivateServer(serverName);
|
|
94272
94596
|
}
|
|
94273
94597
|
}
|
|
@@ -94277,6 +94601,7 @@ function createMcpUseTool(opts) {
|
|
|
94277
94601
|
|
|
94278
94602
|
// src/tools/one-shot-llm-tool.ts
|
|
94279
94603
|
var ONE_SHOT_LLM_TOOL_NAME = "llm";
|
|
94604
|
+
var MAX_TIMEOUT_MS2 = 12e4;
|
|
94280
94605
|
var INPUT_SCHEMA2 = {
|
|
94281
94606
|
type: "object",
|
|
94282
94607
|
properties: {
|
|
@@ -94353,9 +94678,10 @@ var INPUT_SCHEMA2 = {
|
|
|
94353
94678
|
},
|
|
94354
94679
|
timeoutMs: {
|
|
94355
94680
|
type: "number",
|
|
94356
|
-
description:
|
|
94681
|
+
description: `Hard timeout in ms (default 30s, clamped to a maximum of ${MAX_TIMEOUT_MS2}).`
|
|
94357
94682
|
}
|
|
94358
|
-
}
|
|
94683
|
+
},
|
|
94684
|
+
additionalProperties: false
|
|
94359
94685
|
};
|
|
94360
94686
|
function createOneShotLLMTool(opts) {
|
|
94361
94687
|
const orchestrator = new OneShotOrchestrator({
|
|
@@ -94363,6 +94689,7 @@ function createOneShotLLMTool(opts) {
|
|
|
94363
94689
|
getConfig: opts.getConfig,
|
|
94364
94690
|
fallbackProfileManager: opts.fallbackProfileManager,
|
|
94365
94691
|
modelRouter: opts.modelRouter,
|
|
94692
|
+
statusTracker: opts.statusTracker,
|
|
94366
94693
|
logger: opts.logger,
|
|
94367
94694
|
wrapProviderCall: opts.wrapProviderCall
|
|
94368
94695
|
});
|
|
@@ -94371,8 +94698,23 @@ function createOneShotLLMTool(opts) {
|
|
|
94371
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.",
|
|
94372
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.",
|
|
94373
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",
|
|
94374
94704
|
permission: "auto",
|
|
94375
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
|
+
},
|
|
94376
94718
|
async execute(input, _ctx, { signal }) {
|
|
94377
94719
|
if (!input.model && !input.providerId && !opts.defaultModel && !opts.defaultProvider) {
|
|
94378
94720
|
return {
|
|
@@ -94389,7 +94731,11 @@ function createOneShotLLMTool(opts) {
|
|
|
94389
94731
|
...input,
|
|
94390
94732
|
signal: input.signal ? AbortSignal.any([input.signal, signal]) : signal,
|
|
94391
94733
|
model: input.model ?? opts.defaultModel,
|
|
94392
|
-
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 }
|
|
94393
94739
|
};
|
|
94394
94740
|
return orchestrator.call(effectiveInput);
|
|
94395
94741
|
}
|
|
@@ -96383,7 +96729,6 @@ export {
|
|
|
96383
96729
|
makeDesignStudioRequestMiddleware,
|
|
96384
96730
|
makeDesignVerifyToolCallMiddleware,
|
|
96385
96731
|
makeDirectorSessionFactory,
|
|
96386
|
-
makeDomainGlossaryContributor,
|
|
96387
96732
|
makeFleetEmitTool,
|
|
96388
96733
|
makeFleetStatusTool,
|
|
96389
96734
|
makeFleetTool,
|