@sema-agent/core 5.14.0 → 5.16.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 +174 -0
- package/dist/agents/subagent.js +3 -2
- package/dist/brain/errors.js +21 -1
- package/dist/brain/retry.d.ts +5 -0
- package/dist/brain/retry.js +16 -4
- package/dist/brain/stream-engine.js +7 -3
- package/dist/core/checkpoint-store.d.ts +1 -0
- package/dist/core/checkpoint-store.js +48 -26
- package/dist/core/memory-engine/engine.d.ts +3 -1
- package/dist/core/memory-engine/engine.js +4 -3
- package/dist/core/memory-recall.d.ts +1 -1
- package/dist/core/memory-recall.js +3 -2
- package/dist/core/runner/prepare-memory.d.ts +1 -0
- package/dist/core/runner/prepare-memory.js +3 -3
- package/dist/core/runner/prepare-task.d.ts +3 -0
- package/dist/core/runner/prepare-task.js +43 -12
- package/dist/core/runner/runtask.js +124 -36
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +65 -16
- package/dist/core/runner/turn-attachments.d.ts +4 -0
- package/dist/core/runner/turn-attachments.js +15 -2
- package/dist/core/task-registry-agent.d.ts +2 -2
- package/dist/core/task-registry-agent.js +119 -5
- package/dist/core/task-registry.d.ts +2 -2
- package/dist/core/task-tool-shape.js +4 -3
- package/dist/core/trace.d.ts +6 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/prompts/default.d.ts +3 -3
- package/dist/prompts/default.js +2 -2
- package/dist/prompts/supervisor.d.ts +2 -2
- package/dist/prompts/supervisor.js +5 -4
- package/dist/tools/fs/fs-bash.d.ts +1 -0
- package/dist/tools/fs/fs-bash.js +1 -1
- package/dist/tools/fs/gh-rate-limit.d.ts +1 -1
- package/dist/tools/fs/gh-rate-limit.js +4 -3
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +1 -0
- package/package.json +1 -1
|
@@ -38,7 +38,7 @@ import { pathToUri } from "../lsp-protocol.js";
|
|
|
38
38
|
import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
|
|
39
39
|
import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
|
|
40
40
|
import { compileOutputSchema } from "./strict-output-schema.js";
|
|
41
|
-
import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, } from "./tool-disclosure.js";
|
|
41
|
+
import { TOOL_SEARCH_NAME, buildDeferredRegistry, classifyDeferred, extractDiscoveredToolNames, createPlaceholderTool, createToolSearchTool, staticSchemaRenderable, } from "./tool-disclosure.js";
|
|
42
42
|
import { composeMemoryBlock } from "../memory.js";
|
|
43
43
|
import { preflightLockedConfig } from "../locked-config.js";
|
|
44
44
|
import { COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies } from "../compliance.js";
|
|
@@ -629,6 +629,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
629
629
|
if (extraBodyCollisions.length > 0) {
|
|
630
630
|
deps.onError?.(new Error(`model.extraBody contains reserved key(s) [${extraBodyCollisions.join(", ")}] that core owns and sets itself — they are ignored. Remove them from extraBody.`), { phase: "config", sessionId });
|
|
631
631
|
}
|
|
632
|
+
if (model.extraBody !== undefined && Object.prototype.hasOwnProperty.call(model.extraBody, "max_completion_tokens")) {
|
|
633
|
+
const e = new Error(`model.extraBody supplies "max_completion_tokens" — this spelling is engine-owned (the overflow retry ` +
|
|
634
|
+
`adjusts the per-request output cap, and a constant extraBody value would override that adjustment on ` +
|
|
635
|
+
`every retry). Set the cap via limits.maxOutputTokens (per request) or model.maxTokens instead.`);
|
|
636
|
+
e.code = "config.extra_body_output_cap";
|
|
637
|
+
throw e;
|
|
638
|
+
}
|
|
632
639
|
const nestedStats = { tokens: 0, turns: 0, tasks: 0, costMicroUsd: 0, anyUnpriced: false };
|
|
633
640
|
if (resume) {
|
|
634
641
|
nestedStats.tokens = resume.seed.nestedStats.tokens;
|
|
@@ -1592,6 +1599,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1592
1599
|
return memoryWriteGateRef.current?.(w);
|
|
1593
1600
|
},
|
|
1594
1601
|
mountBackgroundTaskTools: false,
|
|
1602
|
+
monitorToolActive: backgroundTaskToolsActive && !(toolFaceSnapshot.exclude?.includes("Monitor") ?? false),
|
|
1595
1603
|
});
|
|
1596
1604
|
{
|
|
1597
1605
|
const bandNames = new Set(band.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
@@ -1844,6 +1852,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1844
1852
|
sessionId,
|
|
1845
1853
|
taskRootPath,
|
|
1846
1854
|
memoryWriteGateRef,
|
|
1855
|
+
writeToolsMounted: tools.some((t) => t.name === "Write") && !(toolFaceSnapshot.exclude?.includes("Write") ?? false),
|
|
1847
1856
|
admissionCtx: {
|
|
1848
1857
|
orgMemoryDenied: complianceDenies.has("org_memory_mount"),
|
|
1849
1858
|
complianceDegraded,
|
|
@@ -2282,6 +2291,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2282
2291
|
.map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
|
|
2283
2292
|
let toolsDeltaRef;
|
|
2284
2293
|
let toolMaterializeStatic = false;
|
|
2294
|
+
const staticFaceForRef = {};
|
|
2285
2295
|
if (deferred.size > 0 || failedMcpServers.length > 0) {
|
|
2286
2296
|
toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
|
|
2287
2297
|
}
|
|
@@ -2293,6 +2303,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2293
2303
|
throw e;
|
|
2294
2304
|
}
|
|
2295
2305
|
const registry = buildDeferredRegistry(deferred, tools);
|
|
2306
|
+
const envStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
|
|
2307
|
+
if (envStrategy !== undefined && envStrategy !== "swap" && envStrategy !== "static") {
|
|
2308
|
+
const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(envStrategy)}).`);
|
|
2309
|
+
e.code = "config.tool_materialize_invalid";
|
|
2310
|
+
throw e;
|
|
2311
|
+
}
|
|
2312
|
+
const requestedStrategy = spec.toolMaterializeStrategy ?? envStrategy ?? "swap";
|
|
2313
|
+
const laneDegrade = requestedStrategy === "static" && spec.deferSelfResolve === false;
|
|
2314
|
+
const materializeStatic = requestedStrategy === "static" && !laneDegrade;
|
|
2315
|
+
toolMaterializeStatic = materializeStatic;
|
|
2316
|
+
promptManifest.toolDisclosure = {
|
|
2317
|
+
deferredTools: deferred.size,
|
|
2318
|
+
strategy: materializeStatic ? "static" : "swap",
|
|
2319
|
+
source: laneDegrade ? "degraded_no_direct_lane" : spec.toolMaterializeStrategy !== undefined ? "spec" : envStrategy !== undefined ? "env" : "default",
|
|
2320
|
+
};
|
|
2321
|
+
const everExempt = new Set();
|
|
2322
|
+
const staticFaceFor = (name) => {
|
|
2323
|
+
if (!materializeStatic)
|
|
2324
|
+
return false;
|
|
2325
|
+
if (everExempt.has(name))
|
|
2326
|
+
return false;
|
|
2327
|
+
if (staticSchemaRenderable(tools.find((t) => t.name === name)?.parameters))
|
|
2328
|
+
return true;
|
|
2329
|
+
everExempt.add(name);
|
|
2330
|
+
return false;
|
|
2331
|
+
};
|
|
2332
|
+
staticFaceForRef.current = staticFaceFor;
|
|
2296
2333
|
let activationChain = Promise.resolve();
|
|
2297
2334
|
const serializeActivation = (section) => {
|
|
2298
2335
|
const p = activationChain.then(section);
|
|
@@ -2314,6 +2351,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2314
2351
|
};
|
|
2315
2352
|
},
|
|
2316
2353
|
...(executionMode !== undefined ? { executionMode } : {}),
|
|
2354
|
+
staticFace: () => staticFaceFor(name),
|
|
2317
2355
|
activate: async () => serializeActivation(async () => {
|
|
2318
2356
|
if (activeTools.has(name))
|
|
2319
2357
|
return undefined;
|
|
@@ -2358,16 +2396,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2358
2396
|
};
|
|
2359
2397
|
offloadReachableToolsRef.current = callableToolNames;
|
|
2360
2398
|
let toolSearch;
|
|
2361
|
-
const envStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
|
|
2362
|
-
if (envStrategy !== undefined && envStrategy !== "swap" && envStrategy !== "static") {
|
|
2363
|
-
const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(envStrategy)}).`);
|
|
2364
|
-
e.code = "config.tool_materialize_invalid";
|
|
2365
|
-
throw e;
|
|
2366
|
-
}
|
|
2367
|
-
const materializeStatic = (spec.toolMaterializeStrategy ?? envStrategy ?? "static") === "static" && spec.deferSelfResolve !== false;
|
|
2368
|
-
toolMaterializeStatic = materializeStatic;
|
|
2369
2399
|
const buildToolList = (active) => {
|
|
2370
|
-
const list = tools.map((t) => (deferred.has(t.name) && (
|
|
2400
|
+
const list = tools.map((t) => (deferred.has(t.name) && (staticFaceFor(t.name) || !active.has(t.name)) ? placeholders.get(t.name) : t));
|
|
2371
2401
|
list.push(toolSearch);
|
|
2372
2402
|
return list;
|
|
2373
2403
|
};
|
|
@@ -2408,8 +2438,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2408
2438
|
listingRide: (newly) => listingRideRef.current?.(newly),
|
|
2409
2439
|
mountedNames: callableToolNames,
|
|
2410
2440
|
directCallEnabled: spec.deferSelfResolve !== false,
|
|
2441
|
+
isMounted: (name) => tools.some((t) => t.name === name),
|
|
2411
2442
|
...(materializeStatic
|
|
2412
|
-
? { staticSchemaFor: (name) => tools.find((t) => t.name === name)?.parameters }
|
|
2443
|
+
? { staticSchemaFor: (name) => (staticFaceFor(name) ? tools.find((t) => t.name === name)?.parameters : undefined) }
|
|
2413
2444
|
: {}),
|
|
2414
2445
|
serializeActivation,
|
|
2415
2446
|
});
|
|
@@ -4108,7 +4139,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4108
4139
|
: undefined;
|
|
4109
4140
|
overheadState.promptChars = systemPrompt.length;
|
|
4110
4141
|
const preparedHolder = {};
|
|
4111
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4142
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4112
4143
|
const prepared = buildPrepared();
|
|
4113
4144
|
preparedHolder.current = prepared;
|
|
4114
4145
|
return prepared;
|
|
@@ -172,11 +172,12 @@ function deepJsonEqual(a, b) {
|
|
|
172
172
|
function isCanonicalIndexKey(key, length) {
|
|
173
173
|
return /^(0|[1-9]\d*)$/.test(key) && Number(key) < length;
|
|
174
174
|
}
|
|
175
|
-
function sameWinner(
|
|
176
|
-
return (
|
|
177
|
-
|
|
178
|
-
deepJsonEqual(
|
|
179
|
-
deepJsonEqual(
|
|
175
|
+
function sameWinner(incoming, persisted) {
|
|
176
|
+
return (incoming.boundCallId === persisted.boundCallId &&
|
|
177
|
+
incoming.decision === persisted.decision &&
|
|
178
|
+
deepJsonEqual(incoming.updatedInput, persisted.updatedInput) &&
|
|
179
|
+
deepJsonEqual(incoming.answer, persisted.answer) &&
|
|
180
|
+
(persisted.reason === undefined || incoming.reason === persisted.reason));
|
|
180
181
|
}
|
|
181
182
|
function pendingContentAskCallId(cp) {
|
|
182
183
|
return cp.pendingAction.kind === "tool_approval" && cp.pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME
|
|
@@ -215,6 +216,14 @@ function toolResultMsg(toolCallId, toolName, text, isError) {
|
|
|
215
216
|
timestamp: Date.now(),
|
|
216
217
|
};
|
|
217
218
|
}
|
|
219
|
+
function describeSuppliedValue(value) {
|
|
220
|
+
return typeof value === "string" ? value : value === null ? "null" : typeof value;
|
|
221
|
+
}
|
|
222
|
+
function assertOutcomeText(value, field) {
|
|
223
|
+
if (value !== undefined && typeof value !== "string") {
|
|
224
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume \`${field}\` is not a plain string (got ${typeof value}) — a decide's text payload is an operator's plain data, not a live object; refusing pre-CAS, the checkpoint stays pending`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
218
227
|
function resumeContinuation(resume) {
|
|
219
228
|
if (resume.outcome.gate === "wake") {
|
|
220
229
|
return formatHookFeedback("You were WOKEN from a parked pause by an operator. Before continuing, re-orient from the workspace: " +
|
|
@@ -575,6 +584,12 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
575
584
|
...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
|
|
576
585
|
...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
|
|
577
586
|
...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
|
|
587
|
+
...(prepared.toolMaterializeStatic && pendingTools !== undefined
|
|
588
|
+
? { newToolsSwappedUnderStatic: pendingTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
|
|
589
|
+
: {}),
|
|
590
|
+
...(prepared.toolMaterializeStatic && pendingReaddedTools !== undefined
|
|
591
|
+
? { readdedToolsSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
|
|
592
|
+
: {}),
|
|
578
593
|
...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
|
|
579
594
|
...(rs.attach.agentListingOn && prepared.agentListing !== undefined
|
|
580
595
|
? {
|
|
@@ -593,6 +608,12 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
593
608
|
const exact = renderToolsDelta({
|
|
594
609
|
...(pendingTools !== undefined ? { added: pendingTools } : {}),
|
|
595
610
|
...(prepared.toolMaterializeStatic ? { staticFace: true } : {}),
|
|
611
|
+
...(prepared.toolMaterializeStatic && pendingTools !== undefined
|
|
612
|
+
? { swappedUnderStatic: pendingTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
|
|
613
|
+
: {}),
|
|
614
|
+
...(prepared.toolMaterializeStatic && pendingReaddedTools !== undefined
|
|
615
|
+
? { readdedSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
|
|
616
|
+
: {}),
|
|
596
617
|
...(mcpToolsDelta ?? {}),
|
|
597
618
|
});
|
|
598
619
|
if (exact !== undefined && due.some((a) => a.source === "tools_delta" && a.body === exact)) {
|
|
@@ -2193,6 +2214,7 @@ export class Runner {
|
|
|
2193
2214
|
...(prepared.promptManifest.tools ? { tools: prepared.promptManifest.tools } : {}),
|
|
2194
2215
|
...(prepared.promptManifest.snapshot ? { snapshot: prepared.promptManifest.snapshot } : {}),
|
|
2195
2216
|
...(prepared.promptManifest.lowering ? { lowering: prepared.promptManifest.lowering } : {}),
|
|
2217
|
+
...(prepared.promptManifest.toolDisclosure ? { toolDisclosure: prepared.promptManifest.toolDisclosure } : {}),
|
|
2196
2218
|
totalChars: prepared.promptManifest.blocks.reduce((n, b) => n + b.chars, 0),
|
|
2197
2219
|
ts: rs.telemetry.taskStart,
|
|
2198
2220
|
}));
|
|
@@ -3376,6 +3398,9 @@ export class Runner {
|
|
|
3376
3398
|
return stream.result();
|
|
3377
3399
|
}
|
|
3378
3400
|
async resumeStream(token, outcome, taskConfig, internals) {
|
|
3401
|
+
if (outcome === null || typeof outcome !== "object") {
|
|
3402
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume outcome must be an object naming its gate (got ${outcome === null ? "null" : typeof outcome})`);
|
|
3403
|
+
}
|
|
3379
3404
|
const store = resolveCheckpointStore(taskConfig, this.deps);
|
|
3380
3405
|
if (!store) {
|
|
3381
3406
|
throw new CheckpointError("checkpoint.not_found", "no CheckpointStore wired — cannot resume (set RunnerDeps.checkpointStore or taskConfig.checkpointStore)");
|
|
@@ -3384,6 +3409,13 @@ export class Runner {
|
|
|
3384
3409
|
if (!cp) {
|
|
3385
3410
|
throw new CheckpointError("checkpoint.not_found", "no checkpoint found for the supplied token");
|
|
3386
3411
|
}
|
|
3412
|
+
if (cp.status !== "pending") {
|
|
3413
|
+
const live = await store.get(token);
|
|
3414
|
+
if (live?.status === "pending") {
|
|
3415
|
+
throw new CheckpointError("checkpoint.reopened_concurrently", "checkpoint changed concurrently (a resolve/reopen cycle landed between this resume's read of the row and its state check) — nothing was validated or executed; re-resume against the current state");
|
|
3416
|
+
}
|
|
3417
|
+
throw new CheckpointError("checkpoint.already_resolved", `checkpoint is ${describeSuppliedValue(live?.status ?? cp.status)} — the token is consumed, so this resume was neither validated nor executed (idempotent); a different outcome cannot redeem it`);
|
|
3418
|
+
}
|
|
3387
3419
|
if ((internals?.inheritedGate?.parentConstraints?.length ?? 0) === 0) {
|
|
3388
3420
|
const parked = this.parentConstraintRegistry.get(token);
|
|
3389
3421
|
if (parked !== undefined) {
|
|
@@ -3394,7 +3426,10 @@ export class Runner {
|
|
|
3394
3426
|
}
|
|
3395
3427
|
}
|
|
3396
3428
|
let wakeMessage;
|
|
3397
|
-
|
|
3429
|
+
const outcomeGate = outcome.gate;
|
|
3430
|
+
let suppliedMessage;
|
|
3431
|
+
if (outcomeGate === "wake") {
|
|
3432
|
+
suppliedMessage = outcome.message;
|
|
3398
3433
|
const gk = cp.gate.kind;
|
|
3399
3434
|
const decideEntry = gk === "human" || gk === "irreversible_ask"
|
|
3400
3435
|
? 'a `policy_ask` outcome (decision allow/deny bound to the pending tool call)'
|
|
@@ -3427,46 +3462,95 @@ export class Runner {
|
|
|
3427
3462
|
`(newer-version row?) — fail-closed: wake only serves a pure park (pendingAction "task_done")`);
|
|
3428
3463
|
}
|
|
3429
3464
|
}
|
|
3430
|
-
if (
|
|
3431
|
-
wakeMessage = validatePendingSteer(
|
|
3465
|
+
if (suppliedMessage !== undefined) {
|
|
3466
|
+
wakeMessage = validatePendingSteer(suppliedMessage);
|
|
3432
3467
|
}
|
|
3433
3468
|
else if (readPendingSteerQueue(cp.state).length === 0) {
|
|
3434
3469
|
throw new CheckpointError("wake.nothing_to_deliver", "cannot wake: no message was supplied and the checkpoint holds no parked pendingSteer — an empty " +
|
|
3435
3470
|
"wake would burn the checkpoint on a blank continuation; supply `message` or park a steer first");
|
|
3436
3471
|
}
|
|
3437
3472
|
}
|
|
3438
|
-
const gateMatch =
|
|
3439
|
-
(cp.gate.kind === "human" &&
|
|
3440
|
-
(cp.gate.kind === "irreversible_ask" &&
|
|
3441
|
-
(cp.gate.kind === "resource_limit" &&
|
|
3442
|
-
(cp.gate.kind === "needs_review" &&
|
|
3443
|
-
(cp.gate.kind === "plan_review" &&
|
|
3473
|
+
const gateMatch = outcomeGate === "wake" ||
|
|
3474
|
+
(cp.gate.kind === "human" && outcomeGate === "policy_ask") ||
|
|
3475
|
+
(cp.gate.kind === "irreversible_ask" && outcomeGate === "policy_ask") ||
|
|
3476
|
+
(cp.gate.kind === "resource_limit" && outcomeGate === "resource_limit") ||
|
|
3477
|
+
(cp.gate.kind === "needs_review" && outcomeGate === "dry_run_review") ||
|
|
3478
|
+
(cp.gate.kind === "plan_review" && outcomeGate === "plan_review");
|
|
3444
3479
|
if (!gateMatch) {
|
|
3445
|
-
throw new CheckpointError("checkpoint.gate_mismatch", `resume outcome (gate "${
|
|
3480
|
+
throw new CheckpointError("checkpoint.gate_mismatch", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") does not match checkpoint gate "${cp.gate.kind}" — resume serves human/policy_ask, irreversible_ask/policy_ask, resource_limit/resource_limit, needs_review/dry_run_review, and plan_review/plan_review`);
|
|
3446
3481
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3482
|
+
let plainReviewOutcome;
|
|
3483
|
+
if (cp.gate.kind === "plan_review") {
|
|
3484
|
+
const decide = outcome;
|
|
3485
|
+
const decision = decide.decision;
|
|
3486
|
+
const editedPlan = decide.editedPlan;
|
|
3487
|
+
const reason = decide.reason;
|
|
3488
|
+
if (decision !== "approve" && decision !== "edit" && decision !== "reject") {
|
|
3489
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${describeSuppliedValue(decision)}" is outside the plan_review domain — a plan review is exactly "approve", "edit" or "reject"; refusing pre-CAS, the checkpoint stays pending`);
|
|
3490
|
+
}
|
|
3491
|
+
assertOutcomeText(editedPlan, "editedPlan");
|
|
3492
|
+
assertOutcomeText(reason, "reason");
|
|
3493
|
+
plainReviewOutcome = {
|
|
3494
|
+
gate: "plan_review",
|
|
3495
|
+
decision,
|
|
3496
|
+
...(editedPlan !== undefined ? { editedPlan } : {}),
|
|
3497
|
+
...(reason !== undefined ? { reason } : {}),
|
|
3498
|
+
};
|
|
3451
3499
|
}
|
|
3452
|
-
if (
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3500
|
+
else if (cp.gate.kind === "needs_review") {
|
|
3501
|
+
const decide = outcome;
|
|
3502
|
+
const decision = decide.decision;
|
|
3503
|
+
const reason = decide.reason;
|
|
3504
|
+
if (decision !== "approve" && decision !== "reject") {
|
|
3505
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${describeSuppliedValue(decision)}" is outside the dry_run_review domain — a dry-run review is exactly "approve" or "reject"; refusing pre-CAS, the checkpoint stays pending`);
|
|
3506
|
+
}
|
|
3507
|
+
assertOutcomeText(reason, "reason");
|
|
3508
|
+
plainReviewOutcome = {
|
|
3509
|
+
gate: "dry_run_review",
|
|
3510
|
+
decision,
|
|
3511
|
+
...(reason !== undefined ? { reason } : {}),
|
|
3512
|
+
};
|
|
3457
3513
|
}
|
|
3458
|
-
if (
|
|
3459
|
-
const
|
|
3460
|
-
if (
|
|
3461
|
-
throw new CheckpointError("checkpoint.
|
|
3514
|
+
if (plainReviewOutcome !== undefined) {
|
|
3515
|
+
const reason = plainReviewOutcome.reason;
|
|
3516
|
+
if (plainReviewOutcome.decision === "reject" && reason && sanitizeUntrustedText(reason) !== reason) {
|
|
3517
|
+
throw new CheckpointError("checkpoint.invalid_outcome", "resume deny/reject reason must not contain a </system-reminder> tag");
|
|
3518
|
+
}
|
|
3519
|
+
const capturedPlan = plainReviewOutcome.gate === "plan_review" ? plainReviewOutcome.editedPlan : undefined;
|
|
3520
|
+
if (plainReviewOutcome.decision === "edit" &&
|
|
3521
|
+
capturedPlan &&
|
|
3522
|
+
sanitizeUntrustedText(capturedPlan) !== capturedPlan) {
|
|
3523
|
+
throw new CheckpointError("checkpoint.invalid_outcome", "resume editedPlan must not contain a </system-reminder> tag");
|
|
3462
3524
|
}
|
|
3463
|
-
if (
|
|
3464
|
-
|
|
3525
|
+
if (cp.reopenReason === "env_failed") {
|
|
3526
|
+
const winner = cp.resolvedOutcome;
|
|
3527
|
+
if (winner === undefined) {
|
|
3528
|
+
throw new CheckpointError("checkpoint.reopen_revote", "review checkpoint was reopened after an env-restore failure (env_failed) but carries no persisted winner to replay — refusing to resume (inconsistent row, fail-closed)");
|
|
3529
|
+
}
|
|
3530
|
+
if (winner.decision !== plainReviewOutcome.decision) {
|
|
3531
|
+
throw new CheckpointError("checkpoint.reopen_revote", `an env_failed reopen replays the ALREADY-RECORDED review decision ("${winner.decision}") — refusing a re-vote with a different decision ("${plainReviewOutcome.decision}")`);
|
|
3532
|
+
}
|
|
3533
|
+
if (winner.updatedInput !== capturedPlan) {
|
|
3534
|
+
throw new CheckpointError("checkpoint.reopen_revote", "an env_failed reopen replays the ALREADY-RECORDED review decision — refusing a re-vote whose edited plan differs from the recorded one");
|
|
3535
|
+
}
|
|
3536
|
+
if (winner.reason !== undefined && winner.reason !== plainReviewOutcome.reason) {
|
|
3537
|
+
throw new CheckpointError("checkpoint.reopen_revote", "an env_failed reopen replays the ALREADY-RECORDED review decision — refusing a re-vote whose reviewer note differs from the recorded one");
|
|
3538
|
+
}
|
|
3465
3539
|
}
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3540
|
+
}
|
|
3541
|
+
let plainParkOutcome;
|
|
3542
|
+
if (outcomeGate === "wake") {
|
|
3543
|
+
plainParkOutcome = {
|
|
3544
|
+
gate: "wake",
|
|
3545
|
+
...(wakeMessage !== undefined ? { message: wakeMessage } : {}),
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
else if (outcomeGate === "resource_limit") {
|
|
3549
|
+
const decision = outcome.decision;
|
|
3550
|
+
if (decision !== "continue") {
|
|
3551
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${describeSuppliedValue(decision)}" is outside the resource_limit domain — a slice resume is exactly "continue"; refusing pre-CAS, the checkpoint stays pending`);
|
|
3469
3552
|
}
|
|
3553
|
+
plainParkOutcome = { gate: "resource_limit", decision };
|
|
3470
3554
|
}
|
|
3471
3555
|
let suppliedAnswer;
|
|
3472
3556
|
let redeemedAnswer;
|
|
@@ -3498,9 +3582,10 @@ export class Runner {
|
|
|
3498
3582
|
}
|
|
3499
3583
|
const decision = decide.decision;
|
|
3500
3584
|
if (decision !== "allow" && decision !== "deny") {
|
|
3501
|
-
throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${
|
|
3585
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume decision "${describeSuppliedValue(decision)}" is outside the policy_ask domain — a decide is exactly "allow" or "deny"; refusing pre-CAS, the checkpoint stays pending`);
|
|
3502
3586
|
}
|
|
3503
3587
|
const reason = decide.reason;
|
|
3588
|
+
assertOutcomeText(reason, "reason");
|
|
3504
3589
|
plainPolicyOutcome = {
|
|
3505
3590
|
gate: "policy_ask",
|
|
3506
3591
|
boundCallId: decide.boundCallId,
|
|
@@ -3588,7 +3673,10 @@ export class Runner {
|
|
|
3588
3673
|
"rejected pre-CAS (the checkpoint stays pending) — re-resume with the full original chain");
|
|
3589
3674
|
}
|
|
3590
3675
|
}
|
|
3591
|
-
const outcomeForStore = plainPolicyOutcome ??
|
|
3676
|
+
const outcomeForStore = plainPolicyOutcome ?? plainReviewOutcome ?? plainParkOutcome;
|
|
3677
|
+
if (outcomeForStore === undefined) {
|
|
3678
|
+
throw new CheckpointError("checkpoint.invalid_outcome", `resume outcome (gate "${describeSuppliedValue(outcomeGate)}") matched the checkpoint gate but no lane captured it — refusing pre-CAS rather than passing the caller's live object to the store and the resumed run`);
|
|
3679
|
+
}
|
|
3592
3680
|
const won = await store.resolve(token, cp.scope, outcomeForStore, { rev: cp.rev ?? 0 });
|
|
3593
3681
|
if (!won) {
|
|
3594
3682
|
const live = await store.get(token);
|
|
@@ -3643,7 +3731,7 @@ export class Runner {
|
|
|
3643
3731
|
? new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run AND the store refused to reopen the checkpoint — the approval is terminally consumed and the suspended work was not executed; a retry needs a fresh approval")
|
|
3644
3732
|
: new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run and the reopen attempt FAILED IN FLIGHT — the checkpoint's state is unprovable from here: it may already be pending again. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead (the approved action did NOT run either way)");
|
|
3645
3733
|
}
|
|
3646
|
-
return this.runTaskStream(spec, { cp, outcome:
|
|
3734
|
+
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, onEnvRestoreFailed, ...(wakeMessage !== undefined ? { wakeMessage } : {}) }, internals);
|
|
3647
3735
|
}
|
|
3648
3736
|
async applyResumeDecision(prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
|
|
3649
3737
|
const { pendingAction } = resume.cp;
|
|
@@ -5,6 +5,8 @@ import type { ToolSpec } from "../types.js";
|
|
|
5
5
|
import type { ToolFingerprintInput } from "../cache-break-detector.js";
|
|
6
6
|
export declare const TOOL_SEARCH_NAME = "ToolSearch";
|
|
7
7
|
export declare const TOOL_SEARCH_DEFAULT_MAX_RESULTS = 5;
|
|
8
|
+
export declare const DEFERRED_NO_PROGRESS_LIMIT = 3;
|
|
9
|
+
export declare const DEFERRED_SCHEMA_INCOMPATIBLE_CODE = "tool.deferred_schema_incompatible";
|
|
8
10
|
export interface DeferredToolInfo {
|
|
9
11
|
name: string;
|
|
10
12
|
hint: string;
|
|
@@ -28,12 +30,14 @@ export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, too
|
|
|
28
30
|
export interface PlaceholderDirectCall {
|
|
29
31
|
resolveReal: () => PlaceholderDirectTarget | undefined;
|
|
30
32
|
executionMode?: ToolExecutionMode;
|
|
33
|
+
staticFace?: () => boolean;
|
|
31
34
|
activate: () => Promise<string | undefined>;
|
|
32
35
|
}
|
|
33
36
|
export interface PlaceholderDirectTarget {
|
|
34
37
|
parameters: TSchema;
|
|
35
38
|
invoke: (toolCallId: string, params: unknown, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<unknown>) => Promise<AgentToolResult<unknown>>;
|
|
36
39
|
}
|
|
40
|
+
export declare function staticSchemaRenderable(schema: TSchema | undefined): boolean;
|
|
37
41
|
export declare function createPlaceholderTool(info: DeferredToolInfo, direct?: PlaceholderDirectCall): AgentTool;
|
|
38
42
|
export declare function scoreToolMatch(query: string, info: DeferredToolInfo): number;
|
|
39
43
|
export interface ToolSearchArgs {
|
|
@@ -53,6 +57,7 @@ export declare function createToolSearchTool(opts: {
|
|
|
53
57
|
listingRide?: (newlyActivated: readonly string[]) => string | undefined;
|
|
54
58
|
mountedNames?: () => ReadonlySet<string>;
|
|
55
59
|
directCallEnabled?: boolean;
|
|
60
|
+
isMounted?: (name: string) => boolean;
|
|
56
61
|
serializeActivation?: <T>(section: () => Promise<T>) => Promise<T>;
|
|
57
62
|
staticSchemaFor?: (name: string) => TSchema | undefined;
|
|
58
63
|
}): AgentTool;
|
|
@@ -7,6 +7,8 @@ const DEFER_AUTO_FRACTION = 0.1;
|
|
|
7
7
|
const CHARS_PER_TOKEN = 4;
|
|
8
8
|
export const TOOL_SEARCH_DEFAULT_MAX_RESULTS = 5;
|
|
9
9
|
const MAX_QUERY_RESULTS = 25;
|
|
10
|
+
export const DEFERRED_NO_PROGRESS_LIMIT = 3;
|
|
11
|
+
export const DEFERRED_SCHEMA_INCOMPATIBLE_CODE = "tool.deferred_schema_incompatible";
|
|
10
12
|
const EMPTY_PARAMS = Type.Object({});
|
|
11
13
|
export function deferHint(description, max = 120) {
|
|
12
14
|
const first = (description.split("\n").find((l) => l.trim() !== "") ?? "").trim();
|
|
@@ -70,7 +72,13 @@ function renderSchemaForModel(schema) {
|
|
|
70
72
|
catch {
|
|
71
73
|
return undefined;
|
|
72
74
|
}
|
|
73
|
-
|
|
75
|
+
if (json === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
const text = truncateError(json);
|
|
78
|
+
return { text, complete: text === json };
|
|
79
|
+
}
|
|
80
|
+
export function staticSchemaRenderable(schema) {
|
|
81
|
+
return schema !== undefined && renderSchemaForModel(schema)?.complete === true;
|
|
74
82
|
}
|
|
75
83
|
export function createPlaceholderTool(info, direct) {
|
|
76
84
|
const sn = safeName(info.name);
|
|
@@ -78,10 +86,36 @@ export function createPlaceholderTool(info, direct) {
|
|
|
78
86
|
throw new Error(`Tool "${sn}" is not active yet. Call ${TOOL_SEARCH_NAME} with {"query":"select:${sn}"} ` +
|
|
79
87
|
`(or a keyword \`query\`) to load its full schema, then call ${sn} with the proper arguments.`);
|
|
80
88
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
let lastFailureShape;
|
|
90
|
+
let repeats = 0;
|
|
91
|
+
const invalidArgumentsRejection = (target, params, schema, ride) => {
|
|
92
|
+
const completeness = schema.complete ? "its full parameter schema" : "an ABRIDGED copy of its parameter schema (too large to inline in full)";
|
|
93
|
+
const durability = !schema.complete
|
|
94
|
+
? `${completeness} is below; the tools list carries the complete declaration. `
|
|
95
|
+
: direct?.staticFace?.() === true
|
|
96
|
+
? `${completeness} is below and is carried by THIS result only — the tools list keeps the compact ` +
|
|
97
|
+
`placeholder entry, so re-run ${TOOL_SEARCH_NAME} with {"query":"select:${sn}"} if you need the schema again later. `
|
|
98
|
+
: `${completeness} is below, and the next request's tools list will advertise it too. `;
|
|
99
|
+
const shape = formatZodValidationError(target.parameters, params);
|
|
100
|
+
repeats = shape === lastFailureShape ? repeats + 1 : 1;
|
|
101
|
+
lastFailureShape = shape;
|
|
102
|
+
if (repeats >= DEFERRED_NO_PROGRESS_LIMIT) {
|
|
103
|
+
const stuck = `\`${sn}\` rejected the same arguments ${repeats} times in a row and its parameter schema has already ` +
|
|
104
|
+
`been delivered, so re-sending them will not start working. This usually means the caller cannot ` +
|
|
105
|
+
`produce arguments outside the compact placeholder entry advertised for \`${sn}\`. \`${sn}\` is ` +
|
|
106
|
+
`finished for this run — no further call to it will be answered differently. Other tools are ` +
|
|
107
|
+
`unaffected; if this was the only work left, the run stops here. ` +
|
|
108
|
+
`(${DEFERRED_SCHEMA_INCOMPATIBLE_CODE})\nLast rejection: ${shape}`;
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: stuck }],
|
|
111
|
+
details: { invalidArguments: true, noProgress: DEFERRED_SCHEMA_INCOMPATIBLE_CODE, repeats },
|
|
112
|
+
isError: true,
|
|
113
|
+
terminate: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const text = `Invalid arguments for \`${sn}\`: ${shape} ` +
|
|
117
|
+
`\`${sn}\` is now active — ${durability}` +
|
|
118
|
+
`Call \`${sn}\` again with arguments matching it.\nParameter schema: ${schema.text}`;
|
|
85
119
|
return {
|
|
86
120
|
content: ride === undefined || ride === "" ? [{ type: "text", text }] : [{ type: "text", text }, { type: "text", text: ride }],
|
|
87
121
|
details: { invalidArguments: true },
|
|
@@ -102,17 +136,19 @@ export function createPlaceholderTool(info, direct) {
|
|
|
102
136
|
if (real === undefined)
|
|
103
137
|
return teachingRejection();
|
|
104
138
|
if (Value.Check(real.parameters, params)) {
|
|
139
|
+
lastFailureShape = undefined;
|
|
140
|
+
repeats = 0;
|
|
105
141
|
const ride = await direct.activate();
|
|
106
142
|
const result = await real.invoke(toolCallId, params, signal, onUpdate);
|
|
107
143
|
if (ride === undefined || ride === "")
|
|
108
144
|
return result;
|
|
109
145
|
return { ...result, content: [...result.content, { type: "text", text: ride }] };
|
|
110
146
|
}
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
147
|
+
const schemaRender = renderSchemaForModel(real.parameters);
|
|
148
|
+
if (schemaRender === undefined)
|
|
113
149
|
return teachingRejection();
|
|
114
150
|
const ride = await direct.activate();
|
|
115
|
-
return invalidArgumentsRejection(real, params,
|
|
151
|
+
return invalidArgumentsRejection(real, params, schemaRender, ride);
|
|
116
152
|
},
|
|
117
153
|
};
|
|
118
154
|
}
|
|
@@ -236,12 +272,13 @@ export function extractDiscoveredToolNames(messages, registry) {
|
|
|
236
272
|
return [...names];
|
|
237
273
|
}
|
|
238
274
|
export function createToolSearchTool(opts) {
|
|
239
|
-
const { registry, active, rematerialize, listingRide, mountedNames, staticSchemaFor } = opts;
|
|
275
|
+
const { registry, active, rematerialize, listingRide, mountedNames, staticSchemaFor, isMounted } = opts;
|
|
240
276
|
const directCallEnabled = opts.directCallEnabled !== false;
|
|
241
277
|
const activationPosture = directCallEnabled
|
|
242
278
|
? (staticSchemaFor !== undefined
|
|
243
279
|
? "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
|
|
280
|
+
"parameter schema in the result (the tools list keeps the compact placeholder entry — except for a " +
|
|
281
|
+
"declaration too large to inline, which goes to the tools list instead). "
|
|
245
282
|
: "Most tools start as name-only placeholders to keep requests small; activating one here loads its full " +
|
|
246
283
|
"parameter schema. ") +
|
|
247
284
|
"Until you have that schema you cannot reliably form a call, so activate a tool rather " +
|
|
@@ -286,14 +323,21 @@ export function createToolSearchTool(opts) {
|
|
|
286
323
|
`selection in \`query\` instead: {"query":"select:ToolA,ToolB"}. Nothing was activated.`);
|
|
287
324
|
}
|
|
288
325
|
const args = (raw ?? {});
|
|
289
|
-
const
|
|
326
|
+
const resolved = resolveToolSearchDetailed(args, registry);
|
|
327
|
+
const missing = resolved.missing;
|
|
328
|
+
const withdrawn = isMounted === undefined ? [] : resolved.matched.filter((n) => !isMounted(n));
|
|
329
|
+
const matched = isMounted === undefined ? resolved.matched : resolved.matched.filter((n) => isMounted(n));
|
|
290
330
|
const mounted = mountedNames?.() ?? new Set();
|
|
291
331
|
const missCallable = missing.filter((n) => mounted.has(n));
|
|
292
332
|
const missUnknown = missing.filter((n) => !mounted.has(n));
|
|
293
333
|
const callableNote = missCallable.length > 0
|
|
294
334
|
? `\nAlready available: ${missCallable.map((n) => safeName(n)).join(", ")} — ${missCallable.length > 1 ? "these tools are" : "this tool is"} not deferred; call ${missCallable.length > 1 ? "them" : "it"} directly right now (no activation needed).`
|
|
295
335
|
: "";
|
|
336
|
+
const withdrawnNote = withdrawn.length > 0
|
|
337
|
+
? `\nNo longer available: ${withdrawn.map((n) => safeName(n)).join(", ")} — ${withdrawn.length > 1 ? "these tools were" : "this tool was"} withdrawn by ${withdrawn.length > 1 ? "their providers" : "its provider"} and cannot be activated or called.`
|
|
338
|
+
: "";
|
|
296
339
|
const missingNote = callableNote +
|
|
340
|
+
withdrawnNote +
|
|
297
341
|
(missUnknown.length > 0
|
|
298
342
|
? `\nNot found: ${missUnknown.map((n) => safeName(n)).join(", ")} — not in the deferred registry under this exact name (lookup is case-sensitive). ` +
|
|
299
343
|
"(Already-active and non-deferred tools are callable directly and don't appear here.)"
|
|
@@ -311,7 +355,7 @@ export function createToolSearchTool(opts) {
|
|
|
311
355
|
matches: [],
|
|
312
356
|
query: typeof args.query === "string" ? args.query : "",
|
|
313
357
|
total_deferred_tools: registry.size,
|
|
314
|
-
...(missing.length > 0 ? { missing: missing.map(safeName) } : {}),
|
|
358
|
+
...(missing.length > 0 || withdrawn.length > 0 ? { missing: [...missing, ...withdrawn].map(safeName) } : {}),
|
|
315
359
|
},
|
|
316
360
|
};
|
|
317
361
|
}
|
|
@@ -332,6 +376,7 @@ export function createToolSearchTool(opts) {
|
|
|
332
376
|
return { newly, ride: newly.length > 0 ? listingRide?.(newly) : undefined };
|
|
333
377
|
});
|
|
334
378
|
const { newly, ride } = await section;
|
|
379
|
+
let missingSchemaLines = false;
|
|
335
380
|
const lines = matched.map((n) => {
|
|
336
381
|
const info = registry.get(n);
|
|
337
382
|
const tag = newly.includes(n) ? "activated" : "already active";
|
|
@@ -340,12 +385,16 @@ export function createToolSearchTool(opts) {
|
|
|
340
385
|
return base;
|
|
341
386
|
const schema = staticSchemaFor(n);
|
|
342
387
|
const json = schema === undefined ? undefined : renderSchemaForModel(schema);
|
|
343
|
-
|
|
388
|
+
if (json === undefined || !json.complete) {
|
|
389
|
+
missingSchemaLines = true;
|
|
390
|
+
return `${base}\n parameters: not inlined — this tool's full declaration is in the tools list.`;
|
|
391
|
+
}
|
|
392
|
+
return `${base}\n parameters: ${json.text}`;
|
|
344
393
|
});
|
|
345
394
|
const head = staticSchemaFor !== undefined
|
|
346
395
|
? 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
|
-
:
|
|
396
|
+
? `Activated ${newly.length} tool(s) — call them directly with arguments matching the parameter schemas below (the tools list keeps compact placeholder entries${missingSchemaLines ? ", except where a line says otherwise" : ""}):`
|
|
397
|
+
: `These tools are already active — call them directly; their parameter schemas are repeated below${missingSchemaLines ? " where they can be inlined" : ""}:`
|
|
349
398
|
: newly.length > 0
|
|
350
399
|
? `Activated ${newly.length} tool(s); they are now available with full parameters — call them directly:`
|
|
351
400
|
: "These tools are already active — call them directly:";
|
|
@@ -358,7 +407,7 @@ export function createToolSearchTool(opts) {
|
|
|
358
407
|
matches: matched.map(safeName),
|
|
359
408
|
query: typeof args.query === "string" ? args.query : "",
|
|
360
409
|
total_deferred_tools: registry.size,
|
|
361
|
-
...(missing.length > 0 ? { missing: missing.map(safeName) } : {}),
|
|
410
|
+
...(missing.length > 0 || withdrawn.length > 0 ? { missing: [...missing, ...withdrawn].map(safeName) } : {}),
|
|
362
411
|
},
|
|
363
412
|
};
|
|
364
413
|
},
|
|
@@ -101,6 +101,8 @@ export interface AttachmentInputs {
|
|
|
101
101
|
backgroundTasks?: ReadonlyArray<BackgroundTaskSnapshot>;
|
|
102
102
|
newTools?: readonly string[];
|
|
103
103
|
newToolsStaticFace?: boolean;
|
|
104
|
+
newToolsSwappedUnderStatic?: readonly string[];
|
|
105
|
+
readdedToolsSwappedUnderStatic?: readonly string[];
|
|
104
106
|
mcpToolsDelta?: McpToolsDeltaFacts;
|
|
105
107
|
agentListing?: ReadonlyArray<AgentListingEntry>;
|
|
106
108
|
agentToolName?: string;
|
|
@@ -150,6 +152,8 @@ export interface McpToolsDeltaFacts {
|
|
|
150
152
|
export declare function renderToolsDelta(input: {
|
|
151
153
|
added?: readonly string[];
|
|
152
154
|
staticFace?: boolean;
|
|
155
|
+
swappedUnderStatic?: readonly string[];
|
|
156
|
+
readdedSwappedUnderStatic?: readonly string[];
|
|
153
157
|
} & McpToolsDeltaFacts): string | undefined;
|
|
154
158
|
export declare const AGENT_TOOLS_NOTE_DEFAULT = "All tools";
|
|
155
159
|
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.";
|