@sema-agent/core 5.9.0 → 5.11.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/CHANGELOG.md +63 -0
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/send-message-tool.js +6 -0
- package/dist/agents/subagent.d.ts +31 -1
- package/dist/agents/subagent.js +67 -21
- package/dist/agents/teacher.js +15 -3
- package/dist/agents/team.js +10 -0
- package/dist/agents/verify.js +7 -0
- package/dist/brain/anthropic.js +27 -10
- package/dist/brain/open-responses.js +19 -4
- package/dist/brain/openai.js +32 -5
- package/dist/core/a2a.js +1 -1
- package/dist/core/background-agent-store.d.ts +2 -1
- package/dist/core/background-agent-store.js +1 -0
- package/dist/core/checkpoint-store.d.ts +2 -0
- package/dist/core/checkpoint-store.js +1 -0
- package/dist/core/memory-recall.js +8 -3
- package/dist/core/memory.d.ts +5 -0
- package/dist/core/memory.js +6 -4
- package/dist/core/runner/assemble-result.js +9 -0
- package/dist/core/runner/prepare-task.d.ts +12 -1
- package/dist/core/runner/prepare-task.js +110 -38
- package/dist/core/runner/runtask.d.ts +12 -0
- package/dist/core/runner/runtask.js +169 -37
- package/dist/core/runner/session-file-state-replay.d.ts +7 -0
- package/dist/core/runner/session-file-state-replay.js +56 -0
- package/dist/core/runner/synthetic-tools.js +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +1 -0
- package/dist/core/runner/tool-disclosure.js +24 -9
- package/dist/core/runner/tool-output-projection.js +5 -4
- package/dist/core/runner/turn-attachments.d.ts +2 -0
- package/dist/core/runner/turn-attachments.js +14 -5
- package/dist/core/session-reconcile.d.ts +7 -3
- package/dist/core/session-reconcile.js +3 -2
- package/dist/core/strategy-store.d.ts +1 -1
- package/dist/core/strategy-store.js +27 -4
- package/dist/core/task-registry-agent.d.ts +3 -0
- package/dist/core/task-registry-agent.js +9 -2
- package/dist/core/task-registry-shared.d.ts +1 -0
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/tools.js +9 -1
- package/dist/core/trace.d.ts +1 -0
- package/dist/core/types.d.ts +8 -1
- package/dist/engine/loop/agent-loop.js +168 -22
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-governance.js +19 -0
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +4 -1
- package/dist/orchestration/workflow.js +1 -1
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/coordinator.js +1 -1
- package/dist/prompts/default.d.ts +1 -0
- package/dist/prompts/default.js +3 -0
- package/dist/stores/file/memory-store.js +3 -7
- package/dist/tools/fs/fs-bash.js +7 -4
- package/dist/tools/fs/fs-shared.d.ts +1 -0
- package/dist/tools/fs/fs-shared.js +4 -0
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +7 -4
- package/dist/tools/web.d.ts +21 -1
- package/dist/tools/web.js +126 -11
- package/package.json +2 -2
|
@@ -24,7 +24,7 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
|
24
24
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
25
25
|
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
|
|
26
26
|
import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated } from "./compaction-call-options.js";
|
|
27
|
-
import { prepareTask } from "./prepare-task.js";
|
|
27
|
+
import { prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
28
28
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
29
29
|
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
30
30
|
import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
@@ -123,6 +123,9 @@ function toolEndBodyFrom(result, isError) {
|
|
|
123
123
|
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
|
+
export function reconciledToolEndBody(orphan) {
|
|
127
|
+
return toolEndBodyFrom({ content: orphan.text, details: { code: orphan.errorKind } }, true);
|
|
128
|
+
}
|
|
126
129
|
function deepJsonEqual(a, b) {
|
|
127
130
|
if (a === b)
|
|
128
131
|
return true;
|
|
@@ -240,6 +243,42 @@ function resumeContinuation(resume) {
|
|
|
240
243
|
`conversation above. Do NOT restart the task or re-run any tool you already ran; continue from this ` +
|
|
241
244
|
`exact point, building on the existing results, and finish the remaining work.`);
|
|
242
245
|
}
|
|
246
|
+
const ENV_DUE_GOVERNANCE_READ_BUDGET_MS = 5_000;
|
|
247
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
248
|
+
const DEADLINE_TICK = Symbol("deadline-tick");
|
|
249
|
+
export const GOVERNANCE_READ_STALLED = Symbol("governance-read-stalled");
|
|
250
|
+
const CHARGE_SETTLE_DISCLOSE_MS = 10_000;
|
|
251
|
+
export async function awaitChargeWithSlowDisclosure(charge, onSlow, discloseAfterMs = CHARGE_SETTLE_DISCLOSE_MS) {
|
|
252
|
+
let timer = setTimeout(() => {
|
|
253
|
+
timer = undefined;
|
|
254
|
+
try {
|
|
255
|
+
onSlow();
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
}
|
|
259
|
+
}, discloseAfterMs);
|
|
260
|
+
try {
|
|
261
|
+
return await charge;
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
if (timer !== undefined)
|
|
265
|
+
clearTimeout(timer);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export async function raceUntilDeadline(p, deadline) {
|
|
269
|
+
for (let firstPass = true;; firstPass = false) {
|
|
270
|
+
const remaining = deadline - Date.now();
|
|
271
|
+
if (remaining <= 0 && !firstPass)
|
|
272
|
+
return GOVERNANCE_READ_STALLED;
|
|
273
|
+
let timer;
|
|
274
|
+
const tick = new Promise((res) => {
|
|
275
|
+
timer = setTimeout(() => res(DEADLINE_TICK), Math.max(0, Math.min(remaining, MAX_TIMER_DELAY_MS)));
|
|
276
|
+
});
|
|
277
|
+
const out = await Promise.race([p, tick]).finally(() => clearTimeout(timer));
|
|
278
|
+
if (out !== DEADLINE_TICK)
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
243
282
|
function platformLimitTerminal(reason, retryAfterMs, moment = "turn_boundary") {
|
|
244
283
|
const message = moment === "entry"
|
|
245
284
|
? `a deployment usage window is exhausted (RunnerDeps.usageWindows), so the task was refused before its first model call — nothing ran and nothing was spent. There is no checkpoint to suspend into at this point, so the caller re-submits after the window frees, in ${String(retryAfterMs ?? 0)}ms.`
|
|
@@ -267,17 +306,39 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
267
306
|
let platformRetryAfterMs;
|
|
268
307
|
const preemptWillSuspend = preemptHit && prepared.suspendForResource !== undefined;
|
|
269
308
|
if (!preemptWillSuspend && prepared.suspendRef.token === undefined && !prepared.abortController.signal.aborted) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
else if (prepared.usageGovernance !== undefined) {
|
|
309
|
+
const ledgerReadDeadline = prepared.envLifetimeSuspendAt !== undefined ? prepared.envLifetimeSuspendAt + ENV_DUE_GOVERNANCE_READ_BUDGET_MS : undefined;
|
|
310
|
+
if (prepared.usageGovernance !== undefined) {
|
|
274
311
|
const governance = prepared.usageGovernance;
|
|
275
312
|
try {
|
|
276
313
|
const at = Date.now();
|
|
277
|
-
await governance.commit(stats.tokens, at);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
314
|
+
await awaitChargeWithSlowDisclosure(governance.commit(stats.tokens, at), () => runnerHooks.onError?.(new Error("the usage ledger CHARGE has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on the end-of-task flush). A wedged ledger store wedges this boundary, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
|
|
315
|
+
const windowRead = governance.check(at);
|
|
316
|
+
let answer;
|
|
317
|
+
if (ledgerReadDeadline !== undefined) {
|
|
318
|
+
answer = await raceUntilDeadline(windowRead, ledgerReadDeadline);
|
|
319
|
+
if (answer === GOVERNANCE_READ_STALLED) {
|
|
320
|
+
void windowRead.catch((lateErr) => {
|
|
321
|
+
try {
|
|
322
|
+
runnerHooks.onError?.(lateErr instanceof Error ? lateErr : new Error(String(lateErr)), { phase: "config", sessionId: prepared.sessionId });
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
try {
|
|
328
|
+
runnerHooks.onError?.(new Error("the usage ledger did not answer before the execution environment's suspend deadline — stopping for the environment, the cause this run can still prove. The window state for this boundary is UNKNOWN and was not enforced."), { phase: "config", sessionId: prepared.sessionId });
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
answer = await windowRead;
|
|
336
|
+
}
|
|
337
|
+
if (answer !== GOVERNANCE_READ_STALLED) {
|
|
338
|
+
platformRetryAfterMs = answer;
|
|
339
|
+
if (platformRetryAfterMs !== undefined)
|
|
340
|
+
platformCause = "usage_window";
|
|
341
|
+
}
|
|
281
342
|
}
|
|
282
343
|
catch (govErr) {
|
|
283
344
|
rs.limits.platformTerminal = govErr instanceof Error ? govErr : new Error(String(govErr));
|
|
@@ -286,6 +347,9 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
286
347
|
return undefined;
|
|
287
348
|
}
|
|
288
349
|
}
|
|
350
|
+
if (platformCause === undefined && prepared.envLifetimeSuspendAt !== undefined && Date.now() >= prepared.envLifetimeSuspendAt) {
|
|
351
|
+
platformCause = "env_lifetime";
|
|
352
|
+
}
|
|
289
353
|
}
|
|
290
354
|
if (platformCause !== undefined) {
|
|
291
355
|
const platformSpend = { costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) };
|
|
@@ -477,6 +541,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
477
541
|
: {}),
|
|
478
542
|
...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
|
|
479
543
|
...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
|
|
544
|
+
...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
|
|
480
545
|
...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
|
|
481
546
|
...(rs.attach.agentListingOn && prepared.agentListing !== undefined
|
|
482
547
|
? {
|
|
@@ -492,7 +557,11 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
492
557
|
...(mcpDropped !== undefined ? { mcpDroppedTools: mcpDropped } : {}),
|
|
493
558
|
}));
|
|
494
559
|
if (pendingTools !== undefined || mcpToolsDelta !== undefined) {
|
|
495
|
-
const exact = renderToolsDelta({
|
|
560
|
+
const exact = renderToolsDelta({
|
|
561
|
+
...(pendingTools !== undefined ? { added: pendingTools } : {}),
|
|
562
|
+
...(prepared.toolMaterializeStatic ? { staticFace: true } : {}),
|
|
563
|
+
...(mcpToolsDelta ?? {}),
|
|
564
|
+
});
|
|
496
565
|
if (exact !== undefined && due.some((a) => a.source === "tools_delta" && a.body === exact)) {
|
|
497
566
|
const ref = prepared.toolsDeltaRef;
|
|
498
567
|
if (pendingTools !== undefined)
|
|
@@ -923,7 +992,8 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
923
992
|
: {}),
|
|
924
993
|
ts: callEndAt,
|
|
925
994
|
}));
|
|
926
|
-
|
|
995
|
+
const cacheRowUnknown = m.usageMissing === true || m.stopReason === "aborted";
|
|
996
|
+
if (prepared.cacheBreakDetector && prepared.cacheFingerprint && !cacheRowUnknown) {
|
|
927
997
|
const finding = prepared.cacheBreakDetector.observe({
|
|
928
998
|
turn: stats.turns + 1,
|
|
929
999
|
systemPrompt: prepared.cacheFingerprint.systemPrompt,
|
|
@@ -1273,6 +1343,7 @@ export class Runner {
|
|
|
1273
1343
|
return Promise.race([p, timeout]).finally(() => clearTimeout(t));
|
|
1274
1344
|
};
|
|
1275
1345
|
let reapHandle;
|
|
1346
|
+
let steerChain = Promise.resolve();
|
|
1276
1347
|
const notifyRef = {};
|
|
1277
1348
|
const manualCompactRef = { requested: false, waiters: [] };
|
|
1278
1349
|
const drainManualCompactWaiters = (outcome) => {
|
|
@@ -1313,6 +1384,10 @@ export class Runner {
|
|
|
1313
1384
|
finally {
|
|
1314
1385
|
releaseLock?.();
|
|
1315
1386
|
publishReady(handle);
|
|
1387
|
+
manualCompactRef.closed = true;
|
|
1388
|
+
if (manualCompactRef.waiters.length > 0)
|
|
1389
|
+
manualCompactRef.emitMooted?.("task_ending");
|
|
1390
|
+
manualCompactRef.emitMooted = undefined;
|
|
1316
1391
|
drainManualCompactWaiters("mooted");
|
|
1317
1392
|
}
|
|
1318
1393
|
})();
|
|
@@ -1432,24 +1507,41 @@ export class Runner {
|
|
|
1432
1507
|
return suggestionsDone.catch(() => []);
|
|
1433
1508
|
},
|
|
1434
1509
|
steer: async (text, options) => {
|
|
1435
|
-
if (resultValue)
|
|
1436
|
-
throw steeringError("the task has already finished");
|
|
1437
|
-
const h = handle ?? (await orTimeout(ready));
|
|
1438
|
-
if (!h)
|
|
1439
|
-
throw steeringError("the task is not running");
|
|
1440
1510
|
if (options?.trusted && sanitizeUntrustedText(text) !== text) {
|
|
1441
1511
|
throw steeringError("trusted steering text must not contain a </system-reminder> tag", "steering.invalid_content");
|
|
1442
1512
|
}
|
|
1443
1513
|
const payload = options?.trusted ? formatHookFeedback(text) : text;
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
if (
|
|
1449
|
-
throw steeringError("the task is
|
|
1514
|
+
const deliver = async () => {
|
|
1515
|
+
if (resultValue)
|
|
1516
|
+
throw steeringError("the task has already finished");
|
|
1517
|
+
const h = handle ?? (await orTimeout(ready));
|
|
1518
|
+
if (!h)
|
|
1519
|
+
throw steeringError("the task is not running");
|
|
1520
|
+
try {
|
|
1521
|
+
await h.harness.steer(payload, { provenance: "engine-note" });
|
|
1522
|
+
return;
|
|
1450
1523
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1524
|
+
catch (e) {
|
|
1525
|
+
if (!(e instanceof Error && e.code === "invalid_state"))
|
|
1526
|
+
throw e;
|
|
1527
|
+
}
|
|
1528
|
+
const birthDeadline = Date.now() + READY_TIMEOUT_MS;
|
|
1529
|
+
while (resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
|
|
1530
|
+
try {
|
|
1531
|
+
await h.harness.steer(payload, { provenance: "engine-note" });
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
catch (e2) {
|
|
1535
|
+
if (!(e2 instanceof Error && e2.code === "invalid_state"))
|
|
1536
|
+
throw e2;
|
|
1537
|
+
}
|
|
1538
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1539
|
+
}
|
|
1540
|
+
throw steeringError("the task is no longer running");
|
|
1541
|
+
};
|
|
1542
|
+
const p = steerChain.then(deliver);
|
|
1543
|
+
steerChain = p.then(() => undefined, () => undefined);
|
|
1544
|
+
return p;
|
|
1453
1545
|
},
|
|
1454
1546
|
notify: async (input, opts) => {
|
|
1455
1547
|
const notifyError = (msg, code) => {
|
|
@@ -1501,6 +1593,8 @@ export class Runner {
|
|
|
1501
1593
|
throw steeringError("the task is not running");
|
|
1502
1594
|
if (opts?.signal?.aborted)
|
|
1503
1595
|
return "mooted";
|
|
1596
|
+
if (manualCompactRef.closed)
|
|
1597
|
+
return "mooted";
|
|
1504
1598
|
return new Promise((resolve) => {
|
|
1505
1599
|
const signal = opts?.signal;
|
|
1506
1600
|
const entry = {
|
|
@@ -1516,6 +1610,7 @@ export class Runner {
|
|
|
1516
1610
|
if (manualCompactRef.waiters.length === 0) {
|
|
1517
1611
|
manualCompactRef.requested = false;
|
|
1518
1612
|
}
|
|
1613
|
+
manualCompactRef.emitMooted?.("cancelled");
|
|
1519
1614
|
entry.resolve("mooted");
|
|
1520
1615
|
}
|
|
1521
1616
|
};
|
|
@@ -1649,6 +1744,13 @@ export class Runner {
|
|
|
1649
1744
|
}, this);
|
|
1650
1745
|
notificationHarness = prepared.harness;
|
|
1651
1746
|
notificationSessionId = prepared.sessionId;
|
|
1747
|
+
const runSourceTaskId = spec.taskId ?? prepared.sessionId;
|
|
1748
|
+
const parentToolCallId = internals?.parentToolCallId;
|
|
1749
|
+
const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: runSourceTaskId } : { eventId: uuidv7() };
|
|
1750
|
+
notificationIdent = ident;
|
|
1751
|
+
manualCompactRef.emitMooted = (reason) => {
|
|
1752
|
+
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason, ...ident() });
|
|
1753
|
+
};
|
|
1652
1754
|
prepared.harness.onUndrainedEngineNotes = (payloads) => {
|
|
1653
1755
|
if (notificationSessionId === undefined)
|
|
1654
1756
|
return;
|
|
@@ -1667,14 +1769,15 @@ export class Runner {
|
|
|
1667
1769
|
if (pendingIdle !== undefined) {
|
|
1668
1770
|
for (const payload of discloseDroppedPending(pendingIdle)) {
|
|
1669
1771
|
deliveredAtTurnOpen.add(taskNotificationDedupKey(payload));
|
|
1670
|
-
queue.push({ type: "task_notification", notification: payload });
|
|
1772
|
+
queue.push({ type: "task_notification", notification: payload, ...ident() });
|
|
1671
1773
|
void prepared.harness.nextTurn(renderTaskNotificationXml(payload), { provenance: "engine-note", enginePayload: payload }).catch(() => {
|
|
1672
1774
|
this.pendingSessionNotifications.pend(prepared.sessionId, payload);
|
|
1673
1775
|
});
|
|
1674
1776
|
}
|
|
1675
1777
|
}
|
|
1676
1778
|
}
|
|
1677
|
-
|
|
1779
|
+
const loopLatch = { ended: false };
|
|
1780
|
+
onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch });
|
|
1678
1781
|
const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
|
|
1679
1782
|
prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
|
|
1680
1783
|
if (resume &&
|
|
@@ -1716,7 +1819,7 @@ export class Runner {
|
|
|
1716
1819
|
rs.limits.outputRetryCap = resolveOutputRetries(spec.outputRetries);
|
|
1717
1820
|
rs.limits.effectiveMaxTurns = resolveMaxTurns(spec.limits);
|
|
1718
1821
|
rs.telemetry.tracer = spec.tracer ?? this.deps.tracer;
|
|
1719
|
-
rs.telemetry.taskId =
|
|
1822
|
+
rs.telemetry.taskId = runSourceTaskId;
|
|
1720
1823
|
if (taskIdRef) {
|
|
1721
1824
|
taskIdRef.current = rs.telemetry.taskId;
|
|
1722
1825
|
taskIdRef.sessionId = prepared.sessionId;
|
|
@@ -2064,9 +2167,6 @@ export class Runner {
|
|
|
2064
2167
|
const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
|
|
2065
2168
|
rs.counters.walltimeSyncBackstopFired = false;
|
|
2066
2169
|
const timeout = startTimeout(prepared.harness, prepared.abortController, walltimeMonotonicDeadline !== undefined ? walltimeMonotonicDeadline - performance.now() : undefined, prepared.suspendForResource !== undefined);
|
|
2067
|
-
const parentToolCallId = internals?.parentToolCallId;
|
|
2068
|
-
const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: rs.telemetry.taskId } : { eventId: uuidv7() };
|
|
2069
|
-
notificationIdent = ident;
|
|
2070
2170
|
const pushContent = (e) => {
|
|
2071
2171
|
queue.push(e);
|
|
2072
2172
|
if (parentToolCallId !== undefined && internals?.onForwardEvent) {
|
|
@@ -2114,6 +2214,10 @@ export class Runner {
|
|
|
2114
2214
|
};
|
|
2115
2215
|
const withBrainSinks = (fn) => runWithStatusSink(statusEmit, () => runWithBrainTelemetry(telemetryEmit, fn));
|
|
2116
2216
|
const toolLabels = new Map(prepared.tools.flatMap((t) => (t.label !== undefined && t.label !== t.name ? [[t.name, t.label]] : [])));
|
|
2217
|
+
for (const orphan of prepared.wakeRecovered) {
|
|
2218
|
+
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan), ...ident() });
|
|
2219
|
+
emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
|
|
2220
|
+
}
|
|
2117
2221
|
const postToolBatchHook = (spec.hooks ?? this.deps.hooks)?.postToolBatch;
|
|
2118
2222
|
const batchArgs = postToolBatchHook ? new Map() : undefined;
|
|
2119
2223
|
rs.turn.toolBatch = [];
|
|
@@ -2625,9 +2729,11 @@ export class Runner {
|
|
|
2625
2729
|
}));
|
|
2626
2730
|
}
|
|
2627
2731
|
}
|
|
2732
|
+
loopLatch.ended = true;
|
|
2628
2733
|
abortedLive = prepared.abortController.signal.aborted;
|
|
2629
2734
|
}
|
|
2630
2735
|
catch (err) {
|
|
2736
|
+
loopLatch.ended = true;
|
|
2631
2737
|
if (errorCodeOf(err) === "resume.tool_unavailable") {
|
|
2632
2738
|
await settleTeardownLeg(() => prepared.mcp.dispose(), "mcp.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
|
|
2633
2739
|
await settleTeardownLeg(() => prepared.a2a?.dispose(), "a2a.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
|
|
@@ -2660,7 +2766,7 @@ export class Runner {
|
|
|
2660
2766
|
}
|
|
2661
2767
|
if (prepared.usageGovernance !== undefined) {
|
|
2662
2768
|
try {
|
|
2663
|
-
await prepared.usageGovernance.commit(stats.tokens, Date.now());
|
|
2769
|
+
await awaitChargeWithSlowDisclosure(prepared.usageGovernance.commit(stats.tokens, Date.now()), () => this.deps.onError?.(new Error("the usage ledger's FINAL charge has not settled after 10s — still waiting (a charge is never abandoned: walking away would double-charge on a later flush). A wedged ledger store wedges this teardown, now visibly."), { phase: "config", sessionId: prepared.sessionId }));
|
|
2664
2770
|
}
|
|
2665
2771
|
catch (flushErr) {
|
|
2666
2772
|
this.deps.onError?.(flushErr, { phase: "config", sessionId: prepared.sessionId });
|
|
@@ -2675,7 +2781,7 @@ export class Runner {
|
|
|
2675
2781
|
try {
|
|
2676
2782
|
const report = await reconcileInterruptedSession(prepared.session, prepared.toolEffects, undefined, startedToolCallIds);
|
|
2677
2783
|
for (const orphan of report.recovered) {
|
|
2678
|
-
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...ident() });
|
|
2784
|
+
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan), ...ident() });
|
|
2679
2785
|
emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
|
|
2680
2786
|
}
|
|
2681
2787
|
}
|
|
@@ -2873,7 +2979,7 @@ export class Runner {
|
|
|
2873
2979
|
const committedToken = prepared.suspendRef.token ?? prepared.reviewRef.token;
|
|
2874
2980
|
const committedScope = prepared.suspendRef.scope ?? prepared.reviewRef.scope;
|
|
2875
2981
|
if (committedToken !== undefined) {
|
|
2876
|
-
const store = spec
|
|
2982
|
+
const store = resolveCheckpointStore(spec, this.deps);
|
|
2877
2983
|
if (store && committedScope !== undefined) {
|
|
2878
2984
|
onSuspend({
|
|
2879
2985
|
env: prepared.ownedEnv,
|
|
@@ -2929,6 +3035,11 @@ export class Runner {
|
|
|
2929
3035
|
catch {
|
|
2930
3036
|
}
|
|
2931
3037
|
}
|
|
3038
|
+
if (manualCompactRef.waiters.length > 0) {
|
|
3039
|
+
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
|
|
3040
|
+
drainManualCompact("mooted");
|
|
3041
|
+
}
|
|
3042
|
+
manualCompactRef.emitMooted = undefined;
|
|
2932
3043
|
queue.push({ type: "done", result });
|
|
2933
3044
|
queue.close();
|
|
2934
3045
|
if (spec.rewindFiles && result.status === "completed") {
|
|
@@ -3117,7 +3228,7 @@ export class Runner {
|
|
|
3117
3228
|
return stream.result();
|
|
3118
3229
|
}
|
|
3119
3230
|
async resumeStream(token, outcome, taskConfig, internals) {
|
|
3120
|
-
const store = taskConfig
|
|
3231
|
+
const store = resolveCheckpointStore(taskConfig, this.deps);
|
|
3121
3232
|
if (!store) {
|
|
3122
3233
|
throw new CheckpointError("checkpoint.not_found", "no CheckpointStore wired — cannot resume (set RunnerDeps.checkpointStore or taskConfig.checkpointStore)");
|
|
3123
3234
|
}
|
|
@@ -3352,13 +3463,16 @@ export class Runner {
|
|
|
3352
3463
|
const completed = new Set(pendingAction.completedCallIds);
|
|
3353
3464
|
const deferredIds = pendingAction.batchToolCallIds.filter((id) => id !== pendingAction.toolCallId && !completed.has(id));
|
|
3354
3465
|
const names = new Map();
|
|
3466
|
+
const deferredArgs = new Map();
|
|
3355
3467
|
if (deferredIds.length > 0) {
|
|
3356
3468
|
const { messages } = await prepared.session.buildContext();
|
|
3357
3469
|
for (const m of messages) {
|
|
3358
3470
|
if (m.role === "assistant") {
|
|
3359
3471
|
for (const c of m.content) {
|
|
3360
|
-
if (c.type === "toolCall")
|
|
3472
|
+
if (c.type === "toolCall") {
|
|
3361
3473
|
names.set(c.id, c.name);
|
|
3474
|
+
deferredArgs.set(c.id, c.arguments);
|
|
3475
|
+
}
|
|
3362
3476
|
}
|
|
3363
3477
|
}
|
|
3364
3478
|
}
|
|
@@ -3369,7 +3483,12 @@ export class Runner {
|
|
|
3369
3483
|
}
|
|
3370
3484
|
else if (!completed.has(id)) {
|
|
3371
3485
|
const name = names.get(id) ?? "unknown";
|
|
3372
|
-
|
|
3486
|
+
const displayLabel = (() => {
|
|
3487
|
+
const l = prepared.tools.find((t) => t.name === name)?.label;
|
|
3488
|
+
return l !== undefined && l !== name ? { label: l } : {};
|
|
3489
|
+
})();
|
|
3490
|
+
emit({ type: "tool_start", toolCallId: id, toolName: name, ...displayLabel, args: deferredArgs.get(id) ?? {} });
|
|
3491
|
+
emit({ type: "tool_end", toolCallId: id, toolName: name, ...displayLabel, isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE) }, true) });
|
|
3373
3492
|
const eid = await prepared.session.appendMessage(toolResultMsg(id, name, formatHookFeedback(DEFERRED_REISSUE), true));
|
|
3374
3493
|
emitCommitted(eid, "toolResult", id);
|
|
3375
3494
|
}
|
|
@@ -3419,13 +3538,26 @@ export class Runner {
|
|
|
3419
3538
|
}
|
|
3420
3539
|
const args = resolvedArgs;
|
|
3421
3540
|
onExecuteStart?.(pendingAction.toolCallId);
|
|
3541
|
+
prepared.suspendProgressRef.executedApproved = true;
|
|
3422
3542
|
let res;
|
|
3423
3543
|
try {
|
|
3424
3544
|
res = await tool.execute(pendingAction.toolCallId, args, prepared.abortController.signal);
|
|
3425
3545
|
}
|
|
3426
3546
|
catch (err) {
|
|
3427
3547
|
const execError = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
3428
|
-
|
|
3548
|
+
const marks = (() => {
|
|
3549
|
+
if (err === null || typeof err !== "object")
|
|
3550
|
+
return undefined;
|
|
3551
|
+
const src = err;
|
|
3552
|
+
let d;
|
|
3553
|
+
if (src.details !== null && typeof src.details === "object" && !Array.isArray(src.details)) {
|
|
3554
|
+
d = { ...src.details };
|
|
3555
|
+
}
|
|
3556
|
+
if (typeof src.errorKind === "string")
|
|
3557
|
+
d = { ...(d ?? {}), errorKind: src.errorKind };
|
|
3558
|
+
return d;
|
|
3559
|
+
})();
|
|
3560
|
+
emitEnd(true, { content: execError, ...(marks !== undefined ? { details: marks } : {}) });
|
|
3429
3561
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, execError, true));
|
|
3430
3562
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3431
3563
|
return;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentMessage } from "../../internal/harness.js";
|
|
2
|
+
export interface TranscriptFileRecord {
|
|
3
|
+
path: string;
|
|
4
|
+
content: string;
|
|
5
|
+
at: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function wholeFileRecordsFromTranscript(messages: readonly AgentMessage[]): TranscriptFileRecord[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
2
|
+
const READ_TOOL = "Read";
|
|
3
|
+
const WRITE_TOOL = "Write";
|
|
4
|
+
const RETRACTING_RESULTS = [
|
|
5
|
+
{ toolName: "Edit", type: "edit", pathField: "filePath" },
|
|
6
|
+
{ toolName: "NotebookEdit", type: "notebook-edit", pathField: "notebookPath" },
|
|
7
|
+
];
|
|
8
|
+
function cardOf(details) {
|
|
9
|
+
if (typeof details !== "object" || details === null)
|
|
10
|
+
return undefined;
|
|
11
|
+
const rest = details;
|
|
12
|
+
return typeof rest.type === "string" ? { type: rest.type, rest } : undefined;
|
|
13
|
+
}
|
|
14
|
+
function wholeFileFromReadCard(rest) {
|
|
15
|
+
const file = rest.file;
|
|
16
|
+
if (typeof file !== "object" || file === null)
|
|
17
|
+
return undefined;
|
|
18
|
+
const f = file;
|
|
19
|
+
if (typeof f.content !== "string" || f.truncatedByTokenCap === true)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (f.startLine !== 1 || typeof f.numLines !== "number" || f.numLines !== f.totalLines)
|
|
22
|
+
return undefined;
|
|
23
|
+
return f.content;
|
|
24
|
+
}
|
|
25
|
+
export function wholeFileRecordsFromTranscript(messages) {
|
|
26
|
+
const byPath = new Map();
|
|
27
|
+
for (const m of messages) {
|
|
28
|
+
if (m.role !== "toolResult" || m.isError)
|
|
29
|
+
continue;
|
|
30
|
+
const card = cardOf(m.details);
|
|
31
|
+
if (card === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
const { type, rest } = card;
|
|
34
|
+
const retracts = RETRACTING_RESULTS.find((r) => r.toolName === m.toolName && r.type === type);
|
|
35
|
+
if (retracts !== undefined) {
|
|
36
|
+
const changed = rest[retracts.pathField];
|
|
37
|
+
if (typeof changed === "string")
|
|
38
|
+
byPath.delete(changed);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const isRead = m.toolName === READ_TOOL && type === "text";
|
|
42
|
+
const isWrite = m.toolName === WRITE_TOOL && (type === "create" || type === "update");
|
|
43
|
+
if (!isRead && !isWrite)
|
|
44
|
+
continue;
|
|
45
|
+
const nested = rest.file;
|
|
46
|
+
const holder = isRead ? (typeof nested === "object" && nested !== null ? nested : undefined) : rest;
|
|
47
|
+
const filePath = holder?.filePath;
|
|
48
|
+
if (typeof filePath !== "string" || !isAbsolutePathForm(filePath))
|
|
49
|
+
continue;
|
|
50
|
+
const content = isRead ? wholeFileFromReadCard(rest) : typeof rest.content === "string" ? rest.content : undefined;
|
|
51
|
+
if (content === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
byPath.set(filePath, { path: filePath, content, at: m.timestamp });
|
|
54
|
+
}
|
|
55
|
+
return [...byPath.values()];
|
|
56
|
+
}
|
|
@@ -78,7 +78,7 @@ export function createReportFindingsTool() {
|
|
|
78
78
|
const count = findings.length;
|
|
79
79
|
return {
|
|
80
80
|
content: count === 0 ? "No findings reported." : `${count} finding${count === 1 ? "" : "s"} reported.`,
|
|
81
|
-
details: { count, ...(level !== undefined ? { level } : {}), findings },
|
|
81
|
+
details: { type: "report-findings", count, ...(level !== undefined ? { level } : {}), findings },
|
|
82
82
|
};
|
|
83
83
|
},
|
|
84
84
|
});
|
|
@@ -54,4 +54,5 @@ export declare function createToolSearchTool(opts: {
|
|
|
54
54
|
mountedNames?: () => ReadonlySet<string>;
|
|
55
55
|
directCallEnabled?: boolean;
|
|
56
56
|
serializeActivation?: <T>(section: () => Promise<T>) => Promise<T>;
|
|
57
|
+
staticSchemaFor?: (name: string) => TSchema | undefined;
|
|
57
58
|
}): AgentTool;
|
|
@@ -80,7 +80,7 @@ export function createPlaceholderTool(info, direct) {
|
|
|
80
80
|
};
|
|
81
81
|
const invalidArgumentsRejection = (target, params, schemaJson, ride) => {
|
|
82
82
|
const text = `Invalid arguments for \`${sn}\`: ${formatZodValidationError(target.parameters, params)} ` +
|
|
83
|
-
`\`${sn}\` is now active — its full parameter schema is below
|
|
83
|
+
`\`${sn}\` is now active — its full parameter schema is below; use it for this and later calls. ` +
|
|
84
84
|
`Call \`${sn}\` again with arguments matching it.\nParameter schema: ${schemaJson}`;
|
|
85
85
|
return {
|
|
86
86
|
content: ride === undefined || ride === "" ? [{ type: "text", text }] : [{ type: "text", text }, { type: "text", text: ride }],
|
|
@@ -236,11 +236,15 @@ export function extractDiscoveredToolNames(messages, registry) {
|
|
|
236
236
|
return [...names];
|
|
237
237
|
}
|
|
238
238
|
export function createToolSearchTool(opts) {
|
|
239
|
-
const { registry, active, rematerialize, listingRide, mountedNames } = opts;
|
|
239
|
+
const { registry, active, rematerialize, listingRide, mountedNames, staticSchemaFor } = opts;
|
|
240
240
|
const directCallEnabled = opts.directCallEnabled !== false;
|
|
241
241
|
const activationPosture = directCallEnabled
|
|
242
|
-
?
|
|
243
|
-
"
|
|
242
|
+
? (staticSchemaFor !== undefined
|
|
243
|
+
? "Most tools start as name-only placeholders to keep requests small; activating one here returns its full " +
|
|
244
|
+
"parameter schema in the result (the tools list keeps the compact placeholder entry). "
|
|
245
|
+
: "Most tools start as name-only placeholders to keep requests small; activating one here loads its full " +
|
|
246
|
+
"parameter schema. ") +
|
|
247
|
+
"Until you have that schema you cannot reliably form a call, so activate a tool rather " +
|
|
244
248
|
"than guessing its arguments — a call that does match the real schema executes and activates the tool. " +
|
|
245
249
|
"When any instruction, reminder, or another tool's description names a deferred tool, activate it here " +
|
|
246
250
|
'with query "select:<name>". '
|
|
@@ -265,7 +269,9 @@ export function createToolSearchTool(opts) {
|
|
|
265
269
|
"a bare tool name — activates that tool directly. " +
|
|
266
270
|
"Activate every tool you expect to need in one call (select accepts a comma-separated list) " +
|
|
267
271
|
"rather than one at a time. " +
|
|
268
|
-
|
|
272
|
+
(staticSchemaFor !== undefined
|
|
273
|
+
? "Activation returns each tool's full parameter schema in this result — call the tool directly with arguments matching it."
|
|
274
|
+
: "Activated tools become callable with their full parameters on your next turn."),
|
|
269
275
|
parameters: Type.Object({
|
|
270
276
|
query: Type.Optional(Type.String({
|
|
271
277
|
description: 'Query to find deferred tools. Use "select:<tool_name>" for direct selection, or keywords to search.',
|
|
@@ -329,11 +335,20 @@ export function createToolSearchTool(opts) {
|
|
|
329
335
|
const lines = matched.map((n) => {
|
|
330
336
|
const info = registry.get(n);
|
|
331
337
|
const tag = newly.includes(n) ? "activated" : "already active";
|
|
332
|
-
|
|
338
|
+
const base = `- ${safeName(n)} (${tag})${info ? ` — ${info.hint}` : ""}`;
|
|
339
|
+
if (staticSchemaFor === undefined)
|
|
340
|
+
return base;
|
|
341
|
+
const schema = staticSchemaFor(n);
|
|
342
|
+
const json = schema === undefined ? undefined : renderSchemaForModel(schema);
|
|
343
|
+
return json === undefined ? base : `${base}\n parameters: ${json}`;
|
|
333
344
|
});
|
|
334
|
-
const head =
|
|
335
|
-
?
|
|
336
|
-
|
|
345
|
+
const head = staticSchemaFor !== undefined
|
|
346
|
+
? newly.length > 0
|
|
347
|
+
? `Activated ${newly.length} tool(s) — call them directly with arguments matching the parameter schemas below (the tools list keeps compact placeholder entries):`
|
|
348
|
+
: "These tools are already active — call them directly; their parameter schemas are repeated below:"
|
|
349
|
+
: newly.length > 0
|
|
350
|
+
? `Activated ${newly.length} tool(s); they are now available with full parameters — call them directly:`
|
|
351
|
+
: "These tools are already active — call them directly:";
|
|
337
352
|
return {
|
|
338
353
|
content: `${head}\n${lines.join("\n")}${missingNote}${ride !== undefined ? `\n\n${ride}` : ""}`,
|
|
339
354
|
details: {
|
|
@@ -57,11 +57,12 @@ export const toolOutputFrom = (result) => {
|
|
|
57
57
|
return { output: raw, truncated: true, totalChars };
|
|
58
58
|
};
|
|
59
59
|
const CC_DETAIL_TYPES = new Set([
|
|
60
|
-
"edit", "
|
|
61
|
-
"agent", "task", "task-list", "task-output", "
|
|
60
|
+
"edit", "create", "update", "bash", "notebook-edit", "notebook", "file_unchanged", "worktree", "text", "grep", "glob", "mcp",
|
|
61
|
+
"agent", "task", "task-list", "task-output", "workflow-run",
|
|
62
62
|
"web-fetch", "web-search", "todo", "cron-create", "cron-delete", "cron-list", "image",
|
|
63
|
-
"task-stop", "tool-search", "
|
|
64
|
-
"monitor-start", "path_not_in_root",
|
|
63
|
+
"task-stop", "tool-search", "repo-map", "fork", "enter-plan-mode", "exit-plan-mode",
|
|
64
|
+
"monitor-start", "path_not_in_root", "readonly_out_of_root",
|
|
65
|
+
"report-findings", "schedule-wakeup", "send-message", "agent-transcript", "a2a", "document",
|
|
65
66
|
]);
|
|
66
67
|
export const structuredFrom = (result) => {
|
|
67
68
|
const details = result !== null && typeof result === "object" ? result.details : undefined;
|
|
@@ -100,6 +100,7 @@ export interface AttachmentInputs {
|
|
|
100
100
|
}>;
|
|
101
101
|
backgroundTasks?: ReadonlyArray<BackgroundTaskSnapshot>;
|
|
102
102
|
newTools?: readonly string[];
|
|
103
|
+
newToolsStaticFace?: boolean;
|
|
103
104
|
mcpToolsDelta?: McpToolsDeltaFacts;
|
|
104
105
|
agentListing?: ReadonlyArray<AgentListingEntry>;
|
|
105
106
|
agentToolName?: string;
|
|
@@ -148,6 +149,7 @@ export interface McpToolsDeltaFacts {
|
|
|
148
149
|
}
|
|
149
150
|
export declare function renderToolsDelta(input: {
|
|
150
151
|
added?: readonly string[];
|
|
152
|
+
staticFace?: boolean;
|
|
151
153
|
} & McpToolsDeltaFacts): string | undefined;
|
|
152
154
|
export declare const AGENT_TOOLS_NOTE_DEFAULT = "All tools";
|
|
153
155
|
export declare const AGENT_CONCURRENCY_NOTE = "When you launch multiple agents for independent work, send them in a single message with multiple tool uses so they run concurrently.";
|
|
@@ -168,7 +168,11 @@ export function collectDueAttachments(state, inp) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
if (inp.config.toolsDelta) {
|
|
171
|
-
const body = renderToolsDelta({
|
|
171
|
+
const body = renderToolsDelta({
|
|
172
|
+
...(inp.newTools !== undefined ? { added: inp.newTools } : {}),
|
|
173
|
+
...(inp.newToolsStaticFace === true ? { staticFace: true } : {}),
|
|
174
|
+
...(inp.mcpToolsDelta ?? {}),
|
|
175
|
+
});
|
|
172
176
|
if (body !== undefined)
|
|
173
177
|
(out ??= []).push({ source: "tools_delta", body });
|
|
174
178
|
}
|
|
@@ -386,15 +390,20 @@ export function renderToolsDelta(input) {
|
|
|
386
390
|
const blocks = [];
|
|
387
391
|
const added = input.added ?? [];
|
|
388
392
|
if (added.length > 0) {
|
|
389
|
-
blocks.push(
|
|
390
|
-
"directly
|
|
393
|
+
blocks.push((input.staticFace === true
|
|
394
|
+
? "The following deferred tools are now active — call them directly. Their parameter schemas were " +
|
|
395
|
+
"provided in the ToolSearch result (the tools list itself keeps compact placeholder entries):\n"
|
|
396
|
+
: "The following deferred tools are now available. Their full schemas are loaded — call them " +
|
|
397
|
+
"directly like any other tool:\n") +
|
|
391
398
|
added.map((n) => `- ${n}`).join("\n"));
|
|
392
399
|
}
|
|
393
400
|
const readded = input.readded ?? [];
|
|
394
401
|
if (readded.length > 0) {
|
|
395
402
|
blocks.push(`${readded.length} deferred tool${readded.length === 1 ? " is" : "s are"} available again (MCP server reconnected — ` +
|
|
396
|
-
`names announced earlier in this conversation): ${groupByMcpServer(readded)}.
|
|
397
|
-
|
|
403
|
+
`names announced earlier in this conversation): ${groupByMcpServer(readded)}. ` +
|
|
404
|
+
(input.staticFace === true
|
|
405
|
+
? `The tools list keeps compact placeholder entries — re-run ToolSearch ("select:<name>") if you need their current parameter schemas.`
|
|
406
|
+
: `Their schemas are loaded again — call them directly.`));
|
|
398
407
|
}
|
|
399
408
|
const removed = input.removed ?? [];
|
|
400
409
|
if (removed.length > 0) {
|