@sema-agent/core 5.10.0 → 5.12.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/agents/subagent.d.ts +1 -1
  3. package/dist/agents/subagent.js +6 -2
  4. package/dist/brain/anthropic.js +1 -1
  5. package/dist/brain/openai.js +1 -1
  6. package/dist/core/auto-compaction.d.ts +12 -1
  7. package/dist/core/auto-compaction.js +3 -1
  8. package/dist/core/background-agent-store.d.ts +2 -1
  9. package/dist/core/background-agent-store.js +1 -0
  10. package/dist/core/checkpoint-store.d.ts +2 -0
  11. package/dist/core/checkpoint-store.js +1 -0
  12. package/dist/core/exec-gate.js +12 -1
  13. package/dist/core/runner/assemble-result.js +9 -0
  14. package/dist/core/runner/compaction-call-options.d.ts +13 -1
  15. package/dist/core/runner/compaction-call-options.js +85 -0
  16. package/dist/core/runner/prepare-task.d.ts +6 -0
  17. package/dist/core/runner/prepare-task.js +118 -36
  18. package/dist/core/runner/runtask.js +71 -21
  19. package/dist/core/runner/tool-disclosure.d.ts +1 -0
  20. package/dist/core/runner/tool-disclosure.js +24 -9
  21. package/dist/core/runner/turn-attachments.d.ts +2 -0
  22. package/dist/core/runner/turn-attachments.js +17 -6
  23. package/dist/core/task-registry-agent.d.ts +3 -0
  24. package/dist/core/task-registry-agent.js +9 -2
  25. package/dist/core/task-registry-shared.d.ts +1 -0
  26. package/dist/core/task-registry.d.ts +2 -0
  27. package/dist/core/trace.d.ts +1 -0
  28. package/dist/core/types.d.ts +9 -1
  29. package/dist/engine/compaction/compaction.d.ts +11 -2
  30. package/dist/engine/compaction/compaction.js +87 -9
  31. package/dist/index.d.ts +1 -1
  32. package/dist/prompt-assembly/event-registry.js +1 -1
  33. package/dist/prompts/default.d.ts +1 -0
  34. package/dist/prompts/default.js +3 -0
  35. package/dist/tools/fs/fs-bash.js +4 -1
  36. package/dist/tools/fs/index.d.ts +1 -0
  37. package/dist/tools/fs/index.js +7 -4
  38. package/dist/tools/web.d.ts +21 -1
  39. package/dist/tools/web.js +126 -10
  40. package/package.json +1 -1
@@ -4,6 +4,7 @@ import { resolve as resolveFsPath } from "node:path";
4
4
  import { AgentHarness, DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, readCompactionActiveTools, summaryOutputBudgetTokens } from "../../internal/harness.js";
5
5
  const PROMPT_HASH_SALT = randomBytes(16);
6
6
  import { sanitizeCompactionSettings } from "../auto-compaction.js";
7
+ import { projectStaleToolResults, resolveStaleToolResultOffload } from "./compaction-call-options.js";
7
8
  import { createAutoModeDecider } from "../auto-mode.js";
8
9
  import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
9
10
  import { resolveModel, resolveTaskModel, roleModelIfSet } from "../roles.js";
@@ -319,6 +320,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
319
320
  throw e;
320
321
  }
321
322
  }
323
+ if (spec.toolMaterializeStrategy !== undefined && spec.toolMaterializeStrategy !== "swap" && spec.toolMaterializeStrategy !== "static") {
324
+ const e = new Error(`toolMaterializeStrategy must be "swap" or "static" (got ${JSON.stringify(spec.toolMaterializeStrategy)}).`);
325
+ e.code = "config.tool_materialize_invalid";
326
+ throw e;
327
+ }
328
+ if (spec.toolMaterializeStrategy === "static" && spec.deferSelfResolve === false) {
329
+ const e = new Error(`toolMaterializeStrategy "static" cannot be combined with deferSelfResolve: false — with the direct-call ` +
330
+ `lane disabled a placeholder is never swapped and never self-resolves, so no deferred tool could ever be ` +
331
+ `called. Use "swap", or leave deferSelfResolve on.`);
332
+ e.code = "config.tool_materialize_unreachable";
333
+ throw e;
334
+ }
322
335
  if (resume === undefined && spec.objective.trim().length === 0) {
323
336
  const e = new Error("TaskSpec.objective is empty — a task needs an instruction (an empty user message is rejected by strict model endpoints and would fail every later request of the session).");
324
337
  e.code = "config.empty_objective";
@@ -977,6 +990,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
977
990
  alwaysLoadTools: toolFaceSnapshot.alwaysLoad,
978
991
  promptProfile,
979
992
  ...(spec.additionalDirectories !== undefined ? { additionalDirectories: Object.freeze([...spec.additionalDirectories]) } : {}),
993
+ ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
980
994
  ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
981
995
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
982
996
  parentCwd: taskRootPath,
@@ -1151,6 +1165,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1151
1165
  gates: resume?.priorHumanReview?.gates ? [...resume.priorHumanReview.gates] : [],
1152
1166
  };
1153
1167
  const priorSuspendCount = resume?.priorSuspendCount ?? 0;
1168
+ const suspendProgressRef = { executedApproved: false };
1169
+ const suspendChainBase = () => (suspendProgressRef.executedApproved ? 0 : priorSuspendCount);
1154
1170
  const maxSuspends = spec.maxSuspends ?? deps.maxSuspends ?? DEFAULT_MAX_SUSPENDS;
1155
1171
  const maxSlices = spec.resourceSuspend?.maxSlices;
1156
1172
  const priorLedger = resume?.priorLedger;
@@ -1334,34 +1350,40 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1334
1350
  const callIssuedAtRef = {};
1335
1351
  const memoryWriteGateRef = {};
1336
1352
  const additionalRootsCanonical = [];
1353
+ const additionalReadRootsCanonical = [];
1337
1354
  let attachmentRootCanonical;
1338
1355
  if (handsEnabled) {
1339
1356
  const rootRaw = taskRootPath;
1340
1357
  const canon = await executionEnv.canonicalPath(rootRaw);
1341
1358
  const rootCanonical = canon.ok ? canon.value : rootRaw;
1342
1359
  attachmentRootCanonical = rootCanonical;
1343
- for (const dir of spec.additionalDirectories ?? []) {
1344
- if (!dir || !dir.trim())
1345
- continue;
1346
- const c = await executionEnv.canonicalPath(dir);
1347
- if (c.ok) {
1348
- additionalRootsCanonical.push(c.value);
1349
- }
1350
- else {
1351
- deps.onError?.(new Error(`additionalDirectories entry skipped (cannot canonicalize): ${dir}`), {
1352
- phase: "config",
1353
- sessionId,
1354
- });
1355
- emitTrace(deps.tracer, () => ({
1356
- kind: "config.additional_directory_skipped",
1357
- version: 1,
1358
- taskId: hostTaskId,
1359
- entry: dir,
1360
- reason: `${c.error.code}: ${c.error.message}`,
1361
- ts: Date.now(),
1362
- }));
1360
+ const canonicalizeExtraDirs = async (dirs, field, sink) => {
1361
+ for (const dir of dirs ?? []) {
1362
+ if (!dir || !dir.trim())
1363
+ continue;
1364
+ const c = await executionEnv.canonicalPath(dir);
1365
+ if (c.ok) {
1366
+ sink.push(c.value);
1367
+ }
1368
+ else {
1369
+ deps.onError?.(new Error(`${field} entry skipped (cannot canonicalize): ${dir}`), {
1370
+ phase: "config",
1371
+ sessionId,
1372
+ });
1373
+ emitTrace(deps.tracer, () => ({
1374
+ kind: "config.additional_directory_skipped",
1375
+ version: 1,
1376
+ taskId: hostTaskId,
1377
+ entry: dir,
1378
+ ...(field !== "additionalDirectories" ? { field } : {}),
1379
+ reason: `${c.error.code}: ${c.error.message}`,
1380
+ ts: Date.now(),
1381
+ }));
1382
+ }
1363
1383
  }
1364
- }
1384
+ };
1385
+ await canonicalizeExtraDirs(spec.additionalDirectories, "additionalDirectories", additionalRootsCanonical);
1386
+ await canonicalizeExtraDirs(spec.additionalReadDirectories, "additionalReadDirectories", additionalReadRootsCanonical);
1365
1387
  if (spec.envFacts?.scratchpadDir) {
1366
1388
  let c = await executionEnv.canonicalPath(spec.envFacts.scratchpadDir);
1367
1389
  if (!c.ok && c.error.code === "not_found") {
@@ -1412,6 +1434,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1412
1434
  }
1413
1435
  const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
1414
1436
  ...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
1437
+ ...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
1415
1438
  includeShell: handsIncludeShell,
1416
1439
  readOnly: handsReadOnly,
1417
1440
  ...(handsCwdRef ? { cwdRef: handsCwdRef } : {}),
@@ -1453,7 +1476,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1453
1476
  irreversibilityTier.set("Bash", shellGate === "always" ? "always" : "maybe");
1454
1477
  irreversibleTools.add("Bash");
1455
1478
  const shellReadBoundary = () => ({
1456
- roots: [rootCanonical, ...additionalRootsCanonical],
1479
+ roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
1457
1480
  ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
1458
1481
  });
1459
1482
  if (shellGate === "classify")
@@ -1777,6 +1800,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1777
1800
  if (additionalRootsCanonical.length > 0) {
1778
1801
  envFacts.additionalDirectories = [...additionalRootsCanonical];
1779
1802
  }
1803
+ if (additionalReadRootsCanonical.length > 0) {
1804
+ envFacts.additionalReadDirectories = [...additionalReadRootsCanonical];
1805
+ }
1780
1806
  try {
1781
1807
  const probe = await executionEnv.exec('uname -s; uname -r; (git rev-parse --is-inside-work-tree 2>/dev/null || echo false); (git symbolic-ref --short -q HEAD 2>/dev/null || echo "HEAD (detached)"); (git rev-parse --show-toplevel 2>/dev/null || echo); (test -n "$(git status --porcelain 2>/dev/null | head -1)" && echo dirty || echo clean); (s=$(ps -p $$ -o comm= 2>/dev/null); s=${s##*/}; echo "${s#-}"); (test "$(git rev-parse --git-dir 2>/dev/null)" != "$(git rev-parse --git-common-dir 2>/dev/null)" && echo linked || echo main); (pwd -P 2>/dev/null || pwd)', { cwd: envFacts.cwd, timeout: 10 });
1782
1808
  if (probe.ok && probe.value.exitCode === 0) {
@@ -2021,6 +2047,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2021
2047
  .filter((s) => s.status === "failed")
2022
2048
  .map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
2023
2049
  let toolsDeltaRef;
2050
+ let toolMaterializeStatic = false;
2024
2051
  if (deferred.size > 0 || failedMcpServers.length > 0) {
2025
2052
  toolsDeltaRef = { pending: [], pendingRemoved: [], pendingReadded: [], pendingFailed: failedMcpServers };
2026
2053
  }
@@ -2097,8 +2124,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2097
2124
  };
2098
2125
  offloadReachableToolsRef.current = callableToolNames;
2099
2126
  let toolSearch;
2127
+ const envStrategy = process.env.SEMA_TOOL_MATERIALIZE_STRATEGY;
2128
+ if (envStrategy !== undefined && envStrategy !== "swap" && envStrategy !== "static") {
2129
+ const e = new Error(`SEMA_TOOL_MATERIALIZE_STRATEGY must be "swap" or "static" (got ${JSON.stringify(envStrategy)}).`);
2130
+ e.code = "config.tool_materialize_invalid";
2131
+ throw e;
2132
+ }
2133
+ const materializeStatic = (spec.toolMaterializeStrategy ?? envStrategy ?? "static") === "static" && spec.deferSelfResolve !== false;
2134
+ toolMaterializeStatic = materializeStatic;
2100
2135
  const buildToolList = (active) => {
2101
- const list = tools.map((t) => (deferred.has(t.name) && !active.has(t.name) ? placeholders.get(t.name) : t));
2136
+ const list = tools.map((t) => (deferred.has(t.name) && (materializeStatic || !active.has(t.name)) ? placeholders.get(t.name) : t));
2102
2137
  list.push(toolSearch);
2103
2138
  return list;
2104
2139
  };
@@ -2139,6 +2174,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2139
2174
  listingRide: (newly) => listingRideRef.current?.(newly),
2140
2175
  mountedNames: callableToolNames,
2141
2176
  directCallEnabled: spec.deferSelfResolve !== false,
2177
+ ...(materializeStatic
2178
+ ? { staticSchemaFor: (name) => tools.find((t) => t.name === name)?.parameters }
2179
+ : {}),
2142
2180
  serializeActivation,
2143
2181
  });
2144
2182
  harnessTools = buildToolList(activeTools);
@@ -2238,6 +2276,34 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2238
2276
  const dateChange = renderWithDate
2239
2277
  ? { legDate: envFacts.date, today: () => formatLocalDate(new Date(), tzValid ? userTz : undefined) }
2240
2278
  : undefined;
2279
+ const lastBrainContextRef = {};
2280
+ const requestLossyRef = { current: false };
2281
+ const staleOffloadWrittenRefs = new Set();
2282
+ const lastBrainContext = () => lastBrainContextRef.current;
2283
+ const recordBrainContext = (servedModelId, c) => {
2284
+ if (requestLossyRef.current) {
2285
+ lastBrainContextRef.current = undefined;
2286
+ return;
2287
+ }
2288
+ lastBrainContextRef.current = {
2289
+ ...(c.systemPrompt !== undefined ? { systemPrompt: c.systemPrompt } : {}),
2290
+ ...(c.systemBlocks !== undefined ? { systemBlocks: [...c.systemBlocks] } : {}),
2291
+ messages: [...c.messages],
2292
+ ...(c.tools !== undefined ? { tools: [...c.tools] } : {}),
2293
+ modelId: servedModelId,
2294
+ };
2295
+ };
2296
+ const staleOffloadCfg = resolveStaleToolResultOffload(spec.compaction?.staleToolResultOffload);
2297
+ if (staleOffloadCfg !== undefined && offloadStore === undefined) {
2298
+ deps.onError?.(new Error("compaction.staleToolResultOffload is set but the tool-result offload store is disabled (toolResultThresholdChars ≤ 0/∞) — the knob is inert this run; re-enable offloading or drop the knob"), { phase: "config", sessionId });
2299
+ }
2300
+ const staleOffload = staleOffloadCfg !== undefined && offloadStore !== undefined ? { cfg: staleOffloadCfg, store: offloadStore } : undefined;
2301
+ const guardedBrain = brainCallGuardrailMs === undefined
2302
+ ? deps.brain
2303
+ : {
2304
+ ...deps.brain,
2305
+ stream: withBrainCallGuardrail((m, c, o) => deps.brain.stream(m, c, o), brainCallGuardrailMs, brainCallGuardrailRef),
2306
+ };
2241
2307
  const harness = new AgentHarness({
2242
2308
  abortResultDetails: () => suspendRef.token !== undefined || reviewRef.token !== undefined ? { code: "gate.parked" } : undefined,
2243
2309
  ...(spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
@@ -2271,12 +2337,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2271
2337
  systemPrompt,
2272
2338
  ...(assembled.systemBlocks ? { systemBlocks: assembled.systemBlocks } : {}),
2273
2339
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
2274
- runtime: brainToRuntime(brainCallGuardrailMs === undefined
2275
- ? deps.brain
2276
- : {
2277
- ...deps.brain,
2278
- stream: withBrainCallGuardrail((m, c, o) => deps.brain.stream(m, c, o), brainCallGuardrailMs, brainCallGuardrailRef),
2279
- }),
2340
+ runtime: brainToRuntime({
2341
+ ...guardedBrain,
2342
+ stream: (m, c, o) => {
2343
+ if (staleOffload === undefined) {
2344
+ recordBrainContext(m.id, c);
2345
+ return guardedBrain.stream(m, c, o);
2346
+ }
2347
+ return (async () => {
2348
+ const projected = await projectStaleToolResults(c, staleOffload.cfg, staleOffload.store, sessionId, staleOffloadWrittenRefs);
2349
+ recordBrainContext(m.id, projected);
2350
+ return await guardedBrain.stream(m, projected, o);
2351
+ })();
2352
+ },
2353
+ }),
2280
2354
  });
2281
2355
  harnessRef.current = harness;
2282
2356
  let releaseSignal = () => undefined;
@@ -3083,7 +3157,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3083
3157
  }
3084
3158
  return false;
3085
3159
  }
3086
- if (suspendLoopCapHit(priorSuspendCount, maxSuspends, " for a plan_review (likely a resume/restart loop)."))
3160
+ if (suspendLoopCapHit(suspendChainBase(), maxSuspends, " for a plan_review (likely a resume/restart loop)."))
3087
3161
  return false;
3088
3162
  const leafId = await session.getLeafId();
3089
3163
  if (!leafId) {
@@ -3131,7 +3205,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3131
3205
  createdAt: mintedAt,
3132
3206
  suspendedAt: now(),
3133
3207
  deadline: mintedAt + (sanitizedTtlMs(spec.durableApproval?.ttlMs) ?? DEFAULT_RESOURCE_TTL_MS),
3134
- suspendCount: priorSuspendCount + 1,
3208
+ suspendCount: suspendChainBase() + 1,
3135
3209
  humanReview: humanReviewRef.count > 0
3136
3210
  ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
3137
3211
  : undefined,
@@ -3192,7 +3266,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3192
3266
  if (abortController.signal.aborted) {
3193
3267
  return undefined;
3194
3268
  }
3195
- if (suspendLoopCapHit(priorSuspendCount, maxSuspends, ` for tool "${req.toolName}" — likely a resume/restart loop.`))
3269
+ if (suspendLoopCapHit(suspendChainBase(), maxSuspends, ` for tool "${req.toolName}" — likely a resume/restart loop.`))
3196
3270
  return undefined;
3197
3271
  if (remoteEnv !== undefined) {
3198
3272
  if (hasBackgroundShell(remoteEnv))
@@ -3215,6 +3289,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3215
3289
  args: postHookArgs,
3216
3290
  safety,
3217
3291
  shellGated: (req.toolName === "Bash" && shellGatedBash) || (req.toolName === "Monitor" && shellGatedMonitor),
3292
+ ...(effectiveShellGate !== "off" ? { shellGateDoctrine: effectiveShellGate } : {}),
3218
3293
  });
3219
3294
  gate =
3220
3295
  safety !== undefined
@@ -3275,7 +3350,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3275
3350
  humanReview: humanReviewRef.count > 0
3276
3351
  ? { count: humanReviewRef.count, totalWaitMs: humanReviewRef.totalWaitMs, gates: [...humanReviewRef.gates] }
3277
3352
  : undefined,
3278
- suspendCount: priorSuspendCount + 1,
3353
+ suspendCount: suspendChainBase() + 1,
3279
3354
  resourceLedger: approvalLedger,
3280
3355
  sourceTaskId: sessionId,
3281
3356
  ...(spec.principal ? { principal: spec.principal } : {}),
@@ -3448,7 +3523,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3448
3523
  : {}),
3449
3524
  });
3450
3525
  let trimmed = trimToBudget(edited, guardAt, estimateContextTokens(edited, charsPerToken).tokens, charsPerToken);
3451
- if (trimmed.length < edited.length) {
3526
+ const trimDroppedMessages = trimmed.length < edited.length;
3527
+ if (trimDroppedMessages) {
3452
3528
  trimPressureRef.droppedMessages = true;
3453
3529
  const droppedCount = edited.length - trimmed.length;
3454
3530
  trimmed = insertTrimNotice(trimmed);
@@ -3471,6 +3547,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3471
3547
  catch {
3472
3548
  }
3473
3549
  }
3550
+ requestLossyRef.current =
3551
+ capped !== healed ||
3552
+ mediaCapped !== capped ||
3553
+ edited !== mediaCapped ||
3554
+ trimDroppedMessages ||
3555
+ swept.dropped.length > 0;
3474
3556
  return { messages: swept.messages };
3475
3557
  });
3476
3558
  const cacheBreakDetector = deps.cacheBreakDetection === false ? undefined : new CacheBreakDetector();
@@ -3584,7 +3666,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3584
3666
  : undefined;
3585
3667
  const listBackgroundTasks = () => defaultTaskRegistry
3586
3668
  .list({ owner: hostTaskId, scope: taskScope, sessionId })
3587
- .filter((t) => t.status === "pending" || t.status === "running")
3669
+ .filter((t) => t.status === "pending" || t.status === "running" || t.status === "parked")
3588
3670
  .map((t) => ({ id: t.task_id, ...(t.description !== undefined ? { description: t.description } : {}), status: t.status }));
3589
3671
  const overheadState = { promptChars: 0 };
3590
3672
  const centerCompactionCandidate = deps.promptSource !== undefined && centerAdoption !== undefined
@@ -3636,7 +3718,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3636
3718
  : undefined;
3637
3719
  overheadState.promptChars = systemPrompt.length;
3638
3720
  const preparedHolder = {};
3639
- 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, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, 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, 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 } : {}) });
3721
+ 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, cacheBreakDetector, cacheFingerprint, 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 } : {}) });
3640
3722
  const prepared = buildPrepared();
3641
3723
  preparedHolder.current = prepared;
3642
3724
  return prepared;
@@ -3,7 +3,7 @@ import { CheckpointError, BINDING_CHECKPOINT_VERSION, checkpointVersionOf, MAX_S
3
3
  import { engineVersion } from "../version.js";
4
4
  import { CONFIG_CATALOG_VERSION, declarationReasons, resolveEffectiveConfig } from "../../config/catalog.js";
5
5
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
6
- import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
6
+ import { DEFAULT_COMPACTION_INSTRUCTIONS, createRapidRefillState, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill, sanitizeCompactionSettings } from "../auto-compaction.js";
7
7
  import { ASK_USER_QUESTION_TOOL_NAME, QUESTION_AWAITS_RESUME } from "../ask-question.js";
8
8
  import { computeCostMicroUsd, modelCostToPricing } from "../pricing.js";
9
9
  import { emitTrace } from "../trace.js";
@@ -23,7 +23,7 @@ import { OUTPUT_TOOL_NAME, SKILLS_LISTING_PROBE_HEADER, resolveOutputRetries } f
23
23
  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
- import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated } from "./compaction-call-options.js";
26
+ import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
27
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";
@@ -426,6 +426,12 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
426
426
  if (declaredMaxTurns !== undefined && declaredMaxTurns > 0) {
427
427
  ratios.push({ axis: "turn budget", ratio: (stats.turns + 1) / declaredMaxTurns });
428
428
  }
429
+ if (walltimeMonotonicDeadline !== undefined) {
430
+ const walltimeWindowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
431
+ if (walltimeWindowMs > 0) {
432
+ ratios.push({ axis: "walltime budget", ratio: (performance.now() - rs.telemetry.taskStartMonotonic) / walltimeWindowMs });
433
+ }
434
+ }
429
435
  let tightest;
430
436
  for (const r of ratios)
431
437
  if (tightest === undefined || r.ratio > tightest.ratio)
@@ -490,7 +496,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
490
496
  rs.attach.attachState.surfacedMtime.delete(p);
491
497
  changed = scan.changed;
492
498
  }
493
- const backgroundTasksOn = rs.attach.attachmentsCfg?.backgroundTasks === true;
499
+ const backgroundTasksOn = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
494
500
  const bgTasks = backgroundTasksOn && rs.attach.attachState.postCompactPending ? prepared.listBackgroundTasks() : undefined;
495
501
  const toolsDeltaOn = rs.attach.attachmentsCfg?.toolsDelta === true;
496
502
  const tdRef = toolsDeltaOn ? prepared.toolsDeltaRef : undefined;
@@ -541,6 +547,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
541
547
  : {}),
542
548
  ...(bgTasks !== undefined ? { backgroundTasks: bgTasks } : {}),
543
549
  ...(pendingTools !== undefined ? { newTools: pendingTools } : {}),
550
+ ...(prepared.toolMaterializeStatic ? { newToolsStaticFace: true } : {}),
544
551
  ...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
545
552
  ...(rs.attach.agentListingOn && prepared.agentListing !== undefined
546
553
  ? {
@@ -556,7 +563,11 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
556
563
  ...(mcpDropped !== undefined ? { mcpDroppedTools: mcpDropped } : {}),
557
564
  }));
558
565
  if (pendingTools !== undefined || mcpToolsDelta !== undefined) {
559
- const exact = renderToolsDelta({ ...(pendingTools !== undefined ? { added: pendingTools } : {}), ...(mcpToolsDelta ?? {}) });
566
+ const exact = renderToolsDelta({
567
+ ...(pendingTools !== undefined ? { added: pendingTools } : {}),
568
+ ...(prepared.toolMaterializeStatic ? { staticFace: true } : {}),
569
+ ...(mcpToolsDelta ?? {}),
570
+ });
560
571
  if (exact !== undefined && due.some((a) => a.source === "tools_delta" && a.body === exact)) {
561
572
  const ref = prepared.toolsDeltaRef;
562
573
  if (pendingTools !== undefined)
@@ -755,6 +766,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
755
766
  ...centerAdoptionOption(prepared),
756
767
  model: event.model,
757
768
  compactionModel: prepared.compModel,
769
+ ...forkContextOption(prepared, false),
758
770
  brain: compactionBrain,
759
771
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
760
772
  thinking: prepared.thinking,
@@ -1338,6 +1350,7 @@ export class Runner {
1338
1350
  return Promise.race([p, timeout]).finally(() => clearTimeout(t));
1339
1351
  };
1340
1352
  let reapHandle;
1353
+ let steerChain = Promise.resolve();
1341
1354
  const notifyRef = {};
1342
1355
  const manualCompactRef = { requested: false, waiters: [] };
1343
1356
  const drainManualCompactWaiters = (outcome) => {
@@ -1501,24 +1514,41 @@ export class Runner {
1501
1514
  return suggestionsDone.catch(() => []);
1502
1515
  },
1503
1516
  steer: async (text, options) => {
1504
- if (resultValue)
1505
- throw steeringError("the task has already finished");
1506
- const h = handle ?? (await orTimeout(ready));
1507
- if (!h)
1508
- throw steeringError("the task is not running");
1509
1517
  if (options?.trusted && sanitizeUntrustedText(text) !== text) {
1510
1518
  throw steeringError("trusted steering text must not contain a </system-reminder> tag", "steering.invalid_content");
1511
1519
  }
1512
1520
  const payload = options?.trusted ? formatHookFeedback(text) : text;
1513
- try {
1514
- await h.harness.steer(payload, { provenance: "engine-note" });
1515
- }
1516
- catch (e) {
1517
- if (e instanceof Error && e.code === "invalid_state") {
1518
- throw steeringError("the task is no longer running");
1521
+ const deliver = async () => {
1522
+ if (resultValue)
1523
+ throw steeringError("the task has already finished");
1524
+ const h = handle ?? (await orTimeout(ready));
1525
+ if (!h)
1526
+ throw steeringError("the task is not running");
1527
+ try {
1528
+ await h.harness.steer(payload, { provenance: "engine-note" });
1529
+ return;
1519
1530
  }
1520
- throw e;
1521
- }
1531
+ catch (e) {
1532
+ if (!(e instanceof Error && e.code === "invalid_state"))
1533
+ throw e;
1534
+ }
1535
+ const birthDeadline = Date.now() + READY_TIMEOUT_MS;
1536
+ while (resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
1537
+ try {
1538
+ await h.harness.steer(payload, { provenance: "engine-note" });
1539
+ return;
1540
+ }
1541
+ catch (e2) {
1542
+ if (!(e2 instanceof Error && e2.code === "invalid_state"))
1543
+ throw e2;
1544
+ }
1545
+ await new Promise((r) => setTimeout(r, 10));
1546
+ }
1547
+ throw steeringError("the task is no longer running");
1548
+ };
1549
+ const p = steerChain.then(deliver);
1550
+ steerChain = p.then(() => undefined, () => undefined);
1551
+ return p;
1522
1552
  },
1523
1553
  notify: async (input, opts) => {
1524
1554
  const notifyError = (msg, code) => {
@@ -1753,7 +1783,8 @@ export class Runner {
1753
1783
  }
1754
1784
  }
1755
1785
  }
1756
- onReady({ harness: prepared.harness, abortController: prepared.abortController });
1786
+ const loopLatch = { ended: false };
1787
+ onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch });
1757
1788
  const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
1758
1789
  prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
1759
1790
  if (resume &&
@@ -1903,7 +1934,8 @@ export class Runner {
1903
1934
  rs.attach.agentListingOn = rs.attach.attachmentsCfg?.agentListing !== false && eventDefaultOn("agent_listing");
1904
1935
  rs.attach.skillsListingOn = rs.attach.attachmentsCfg?.skillsListing !== false && eventDefaultOn("skills_listing");
1905
1936
  const listingsLive = (rs.attach.agentListingOn && prepared.agentListing !== undefined) || (rs.attach.skillsListingOn && prepared.skillsListing !== undefined);
1906
- rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive ? createAttachmentState() : undefined;
1937
+ const backgroundTasksLive = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
1938
+ rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive ? createAttachmentState() : undefined;
1907
1939
  rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.legDate } : undefined;
1908
1940
  rs.attach.instrProbe = this.deps.probeInstructionSources;
1909
1941
  rs.attach.instrState =
@@ -1916,7 +1948,7 @@ export class Runner {
1916
1948
  : undefined;
1917
1949
  rs.counters.cadenceTurns = 0;
1918
1950
  rs.turn.lastTurnHadToolCalls = false;
1919
- if (rs.attach.attachState !== undefined && rs.attach.attachmentsCfg?.backgroundTasks === true) {
1951
+ if (rs.attach.attachState !== undefined && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
1920
1952
  try {
1921
1953
  const branch = await prepared.session.getBranch();
1922
1954
  for (let i = branch.length - 1; i >= 0; i--) {
@@ -2240,6 +2272,19 @@ export class Runner {
2240
2272
  };
2241
2273
  const withinTaskCompaction = (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true);
2242
2274
  const compactionBreaker = { failures: 0 };
2275
+ if (spec.compaction?.enabled ?? true) {
2276
+ const prefixWindow = prepared.model.autoCompactTokens ?? prepared.model.contextTokens ?? prepared.model.contextWindow;
2277
+ if (Number.isFinite(prefixWindow) && prefixWindow > 0) {
2278
+ const prefixSettings = sanitizeCompactionSettings({ ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction }, prefixWindow);
2279
+ const prefixCompactAt = prefixWindow - prefixSettings.reserveTokens;
2280
+ if (prepared.promptOverheadTokens >= prefixCompactAt) {
2281
+ this.deps.onError?.(new Error(`compaction cannot help: the fixed request prefix (system prompt + tool schemas, ≈${prepared.promptOverheadTokens} tokens) ` +
2282
+ `already meets or exceeds the compaction threshold (${prefixCompactAt} of a ${prefixWindow}-token window). ` +
2283
+ `Compaction only shrinks conversation history, so this run will re-trigger or overflow regardless — ` +
2284
+ `shrink the system prompt/tool surface or use a larger-window model.`), { phase: "config", sessionId: prepared.sessionId });
2285
+ }
2286
+ }
2287
+ }
2243
2288
  const windowSafetyOptions = (mainModel) => ({
2244
2289
  ...(rs.budget.maxCostMicroUsd !== undefined
2245
2290
  ? {
@@ -2409,6 +2454,7 @@ export class Runner {
2409
2454
  ...centerAdoptionOption(prepared),
2410
2455
  model: prepared.harness.getModel(),
2411
2456
  compactionModel: prepared.compModel,
2457
+ ...forkContextOption(prepared, true),
2412
2458
  brain: compactionBrain,
2413
2459
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
2414
2460
  thinking: prepared.thinking,
@@ -2705,9 +2751,11 @@ export class Runner {
2705
2751
  }));
2706
2752
  }
2707
2753
  }
2754
+ loopLatch.ended = true;
2708
2755
  abortedLive = prepared.abortController.signal.aborted;
2709
2756
  }
2710
2757
  catch (err) {
2758
+ loopLatch.ended = true;
2711
2759
  if (errorCodeOf(err) === "resume.tool_unavailable") {
2712
2760
  await settleTeardownLeg(() => prepared.mcp.dispose(), "mcp.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
2713
2761
  await settleTeardownLeg(() => prepared.a2a?.dispose(), "a2a.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
@@ -2775,7 +2823,7 @@ export class Runner {
2775
2823
  });
2776
2824
  }
2777
2825
  }
2778
- if (rs.attach.attachState?.postCompactPending === true && rs.attach.attachmentsCfg?.backgroundTasks === true) {
2826
+ if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
2779
2827
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
2780
2828
  }
2781
2829
  const comp = await this.finish(spec, prepared, {
@@ -3512,6 +3560,7 @@ export class Runner {
3512
3560
  }
3513
3561
  const args = resolvedArgs;
3514
3562
  onExecuteStart?.(pendingAction.toolCallId);
3563
+ prepared.suspendProgressRef.executedApproved = true;
3515
3564
  let res;
3516
3565
  try {
3517
3566
  res = await tool.execute(pendingAction.toolCallId, args, prepared.abortController.signal);
@@ -3622,6 +3671,7 @@ export class Runner {
3622
3671
  ...centerAdoptionOption(prepared),
3623
3672
  model: prepared.model,
3624
3673
  compactionModel: prepared.compModel,
3674
+ ...forkContextOption(prepared, false),
3625
3675
  brain: opts?.brain ?? this.deps.brain,
3626
3676
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
3627
3677
  thinking: prepared.thinking,
@@ -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 (and rides the next request). ` +
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
- ? "Most tools start as name-only placeholders to keep requests small; activating one here loads its full " +
243
- "parameter schema. Until you have that schema you cannot reliably form a call, so activate a tool rather " +
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
- "Activated tools become callable with their full parameters on your next turn.",
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
- return `- ${safeName(n)} (${tag})${info ? ` — ${info.hint}` : ""}`;
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 = newly.length > 0
335
- ? `Activated ${newly.length} tool(s); they are now available with full parameters — call them directly:`
336
- : "These tools are already active — call them directly:";
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: {
@@ -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.";