@sema-agent/core 5.11.0 → 5.13.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 (41) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/agents/subagent.js +4 -2
  3. package/dist/core/auto-compaction.d.ts +12 -1
  4. package/dist/core/auto-compaction.js +3 -1
  5. package/dist/core/checkpoint-store.d.ts +8 -1
  6. package/dist/core/checkpoint-store.js +3 -1
  7. package/dist/core/compliance.d.ts +11 -0
  8. package/dist/core/compliance.js +34 -0
  9. package/dist/core/exec-gate.js +12 -1
  10. package/dist/core/governance-codes.d.ts +12 -0
  11. package/dist/core/governance-codes.js +24 -0
  12. package/dist/core/locked-config.d.ts +27 -0
  13. package/dist/core/locked-config.js +42 -0
  14. package/dist/core/memory-admission.d.ts +47 -0
  15. package/dist/core/memory-admission.js +156 -0
  16. package/dist/core/memory.d.ts +2 -0
  17. package/dist/core/memory.js +3 -2
  18. package/dist/core/retention.d.ts +36 -0
  19. package/dist/core/retention.js +31 -0
  20. package/dist/core/runner/assemble-result.js +1 -1
  21. package/dist/core/runner/compaction-call-options.d.ts +13 -1
  22. package/dist/core/runner/compaction-call-options.js +85 -0
  23. package/dist/core/runner/prepare-memory.d.ts +9 -0
  24. package/dist/core/runner/prepare-memory.js +28 -2
  25. package/dist/core/runner/prepare-task.d.ts +4 -0
  26. package/dist/core/runner/prepare-task.js +183 -26
  27. package/dist/core/runner/runtask.js +101 -47
  28. package/dist/core/runner/turn-attachments.js +3 -1
  29. package/dist/core/session-store.d.ts +1 -0
  30. package/dist/core/session-store.js +1 -0
  31. package/dist/core/tool-result-store.d.ts +2 -0
  32. package/dist/core/tool-result-store.js +1 -0
  33. package/dist/core/types.d.ts +12 -2
  34. package/dist/engine/compaction/compaction.d.ts +11 -2
  35. package/dist/engine/compaction/compaction.js +87 -9
  36. package/dist/engine/harness/agent-harness.js +11 -1
  37. package/dist/engine/llm/validation.js +11 -1
  38. package/dist/index.d.ts +7 -2
  39. package/dist/index.js +6 -1
  40. package/dist/prompt-assembly/event-registry.js +1 -1
  41. package/package.json +1 -1
@@ -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;
@@ -760,6 +766,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
760
766
  ...centerAdoptionOption(prepared),
761
767
  model: event.model,
762
768
  compactionModel: prepared.compModel,
769
+ ...forkContextOption(prepared, false),
763
770
  brain: compactionBrain,
764
771
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
765
772
  thinking: prepared.thinking,
@@ -1443,6 +1450,12 @@ export class Runner {
1443
1450
  errorMessage: err instanceof Error ? err.message : String(err),
1444
1451
  errorCode: code,
1445
1452
  ...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
1453
+ ...(() => {
1454
+ const hinted = err.retryAfterMs;
1455
+ return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
1456
+ ? { retryAfterMs: hinted }
1457
+ : {};
1458
+ })(),
1446
1459
  stats: { turns: 0, tokens: 0, toolCalls: 0, cachedTokens: 0, costMicroUsd: 0 },
1447
1460
  };
1448
1461
  emitTrace(spec.tracer ?? this.deps.tracer, () => ({
@@ -1696,9 +1709,7 @@ export class Runner {
1696
1709
  queue.push({ type: "task_notification", notification: item.payload, ...notificationIdent() });
1697
1710
  if (notificationHarness) {
1698
1711
  const xml = renderTaskNotificationXml(item.payload);
1699
- const deliver = item.priority === "later"
1700
- ? notificationHarness.followUp(xml, { provenance: "engine-note", enginePayload: item.payload })
1701
- : notificationHarness.steer(xml, { provenance: "engine-note", enginePayload: item.payload });
1712
+ const deliver = notificationHarness.steer(xml, { provenance: "engine-note", enginePayload: item.payload });
1702
1713
  void deliver.then(() => item.onDisposition?.("queued"), () => {
1703
1714
  parkTaskNotification(item.payload, item.priority);
1704
1715
  item.onDisposition?.("parked");
@@ -1927,7 +1938,8 @@ export class Runner {
1927
1938
  rs.attach.agentListingOn = rs.attach.attachmentsCfg?.agentListing !== false && eventDefaultOn("agent_listing");
1928
1939
  rs.attach.skillsListingOn = rs.attach.attachmentsCfg?.skillsListing !== false && eventDefaultOn("skills_listing");
1929
1940
  const listingsLive = (rs.attach.agentListingOn && prepared.agentListing !== undefined) || (rs.attach.skillsListingOn && prepared.skillsListing !== undefined);
1930
- rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive ? createAttachmentState() : undefined;
1941
+ const backgroundTasksLive = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
1942
+ rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive ? createAttachmentState() : undefined;
1931
1943
  rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.legDate } : undefined;
1932
1944
  rs.attach.instrProbe = this.deps.probeInstructionSources;
1933
1945
  rs.attach.instrState =
@@ -1940,7 +1952,7 @@ export class Runner {
1940
1952
  : undefined;
1941
1953
  rs.counters.cadenceTurns = 0;
1942
1954
  rs.turn.lastTurnHadToolCalls = false;
1943
- if (rs.attach.attachState !== undefined && rs.attach.attachmentsCfg?.backgroundTasks === true) {
1955
+ if (rs.attach.attachState !== undefined && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
1944
1956
  try {
1945
1957
  const branch = await prepared.session.getBranch();
1946
1958
  for (let i = branch.length - 1; i >= 0; i--) {
@@ -2264,6 +2276,19 @@ export class Runner {
2264
2276
  };
2265
2277
  const withinTaskCompaction = (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true);
2266
2278
  const compactionBreaker = { failures: 0 };
2279
+ if (spec.compaction?.enabled ?? true) {
2280
+ const prefixWindow = prepared.model.autoCompactTokens ?? prepared.model.contextTokens ?? prepared.model.contextWindow;
2281
+ if (Number.isFinite(prefixWindow) && prefixWindow > 0) {
2282
+ const prefixSettings = sanitizeCompactionSettings({ ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction }, prefixWindow);
2283
+ const prefixCompactAt = prefixWindow - prefixSettings.reserveTokens;
2284
+ if (prepared.promptOverheadTokens >= prefixCompactAt) {
2285
+ this.deps.onError?.(new Error(`compaction cannot help: the fixed request prefix (system prompt + tool schemas, ≈${prepared.promptOverheadTokens} tokens) ` +
2286
+ `already meets or exceeds the compaction threshold (${prefixCompactAt} of a ${prefixWindow}-token window). ` +
2287
+ `Compaction only shrinks conversation history, so this run will re-trigger or overflow regardless — ` +
2288
+ `shrink the system prompt/tool surface or use a larger-window model.`), { phase: "config", sessionId: prepared.sessionId });
2289
+ }
2290
+ }
2291
+ }
2267
2292
  const windowSafetyOptions = (mainModel) => ({
2268
2293
  ...(rs.budget.maxCostMicroUsd !== undefined
2269
2294
  ? {
@@ -2302,6 +2327,22 @@ export class Runner {
2302
2327
  });
2303
2328
  const stopHook = (spec.hooks ?? this.deps.hooks)?.stop;
2304
2329
  const finalVerificationOn = spec.finalVerification === true;
2330
+ const finalVerifyBudgetFill = () => {
2331
+ let worst = 0;
2332
+ if (rs.budget.maxTokensWindow !== undefined && rs.budget.maxTokensWindow > 0)
2333
+ worst = Math.max(worst, stats.tokens / rs.budget.maxTokensWindow);
2334
+ if (rs.budget.maxCostMicroUsd !== undefined && rs.budget.maxCostMicroUsd > 0)
2335
+ worst = Math.max(worst, stats.costMicroUsd / rs.budget.maxCostMicroUsd);
2336
+ if (walltimeMonotonicDeadline !== undefined && prepared.suspendForResource === undefined) {
2337
+ const windowMs = walltimeMonotonicDeadline - rs.telemetry.taskStartMonotonic;
2338
+ if (windowMs > 0)
2339
+ worst = Math.max(worst, (performance.now() - rs.telemetry.taskStartMonotonic) / windowMs);
2340
+ }
2341
+ return worst;
2342
+ };
2343
+ const emitFinalVerifyEcho = (body) => {
2344
+ queue.push({ type: "steering_injected", source: "final_verification", preview: body.slice(0, 220), ...ident() });
2345
+ };
2305
2346
  if (stopHook || finalVerificationOn) {
2306
2347
  let consecutiveBlocks = 0;
2307
2348
  prepared.harness.setStopGate(async () => {
@@ -2311,58 +2352,69 @@ export class Runner {
2311
2352
  (rs.counters.finalVerifyInjections === 0 || (rs.counters.finalVerifyInjections === 1 && rs.counters.groundingSignalPreR9 && !rs.counters.groundingSignalPostR9)) &&
2312
2353
  rs.counters.wroteThisRun &&
2313
2354
  prepared.outputRef.set !== true &&
2314
- !(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1)) {
2355
+ !(rs.limits.effectiveMaxTurns !== undefined && rs.limits.effectiveMaxTurns > 0 && stats.turns >= rs.limits.effectiveMaxTurns - 1) &&
2356
+ finalVerifyBudgetFill() < 0.9) {
2315
2357
  rs.counters.finalVerifyInjections += 1;
2316
2358
  if (rs.counters.finalVerifyInjections === 2) {
2359
+ const reentryBody = "<system-reminder>[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
2360
+ "or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
2361
+ "embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
2362
+ "execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
2363
+ "declared one, re-parse the structure from the raw bytes and reconcile it with your output, compare against the " +
2364
+ "reference data, or replay the deterministic path — and REPORT the check's concrete result before finishing. " +
2365
+ "A closing statement without a reported check result is not verification. If you already ran such a check, state " +
2366
+ "its concrete result now; if the check mismatches, fix the deliverable first. This is the final reminder from " +
2367
+ "this verification gate — it will not intervene again.</system-reminder>";
2368
+ emitFinalVerifyEcho(reentryBody);
2317
2369
  return [
2318
2370
  {
2319
2371
  role: "user",
2320
2372
  engineMinted: true,
2321
- content: "<system-reminder>[final verification] Your tool calls in this run worked with raw bytes, structural parsing, " +
2322
- "or checksum/digest computation — the deliverable very likely embeds verifiable structure (structural fields, an " +
2323
- "embedded checksum-family value, reference data it must match, or a replayable deterministic path). You MUST " +
2324
- "execute the grounding check that structure supports — recompute the embedded value and compare it against the " +
2325
- "declared one, re-parse the structure from the raw bytes and reconcile it with your output, compare against the " +
2326
- "reference data, or replay the deterministic path — and REPORT the check's concrete result before finishing. " +
2327
- "A closing statement without a reported check result is not verification. If you already ran such a check, state " +
2328
- "its concrete result now; if the check mismatches, fix the deliverable first. This is the final reminder from " +
2329
- "this verification gate — it will not intervene again.</system-reminder>",
2373
+ content: reentryBody,
2330
2374
  timestamp: Date.now(),
2331
2375
  },
2332
2376
  ];
2333
2377
  }
2378
+ const nudgeBody = "<system-reminder>[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
2379
+ "exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
2380
+ "output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
2381
+ "stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
2382
+ "finishing. " +
2383
+ "Treat verification writes as state-harmless: when the deliverable itself is a persisted final " +
2384
+ "state (for example a committed or pushed file, a deployed artifact, or a required output file), " +
2385
+ "do not change that state merely to test it. This constrains HOW you verify — it is never a " +
2386
+ "license to skip the real acceptance path or to check a substitute of your own making: expected " +
2387
+ "values must come from the task's requirements, never from content you generated. If the real " +
2388
+ "acceptance path requires a write, use disposable inputs or an isolated target, end in the exact " +
2389
+ "required final state, and verify that final state before finishing. " +
2390
+ "If the work relied on a third-party API, library, or model, check the usage contract the object itself declares " +
2391
+ "(docstrings, metadata, configuration — e.g. prompt conventions shipped with a model) and confirm your calls follow " +
2392
+ "it rather than a default symmetric usage. Verify not only that the deliverable EXISTS but that the METHOD that " +
2393
+ "produced it matches the task's requirements. " +
2394
+ "Choose the verification SURFACE deliberately: check against the reference data, oracle, or evaluation tooling the " +
2395
+ "task itself provides — re-running your own implementation and getting the same answer is self-consistency, not " +
2396
+ "correctness — and cross-check through an independent second path where the task or environment offers one " +
2397
+ "(checksums, runtime artifacts). Verify the PERSISTED artifact — re-read what is actually on disk or committed, " +
2398
+ "not in-memory state — against every hard constraint from the original task text (numeric bounds, allowed-value " +
2399
+ "lists, naming semantics, required files), reconciling whole-set completeness: nothing missing, nothing duplicated. " +
2400
+ "If the deliverable embeds verifiable structure — structural fields, an embedded checksum-family value, " +
2401
+ "reference data it must match, or a replayable deterministic path — you MUST execute the grounding check " +
2402
+ "that structure supports and REPORT its concrete result in your closing summary: for such a deliverable, " +
2403
+ "no reported check result means the work is not finished. " +
2404
+ "If the task produced neither an executable deliverable nor any verifiable structure or acceptance " +
2405
+ "oracle to check against, briefly confirm completion and stop. " +
2406
+ "Residue YOUR OWN testing created (scratch files, running processes, generated outputs the task does not ask for) " +
2407
+ "is not protected state — if the task's required final state is a clean target, removing your own residue is part " +
2408
+ "of delivering it. " +
2409
+ "Verification must never LAUNDER uncertainty: if part of your conclusion was uncertain before this check, keep " +
2410
+ "reporting it as uncertain unless the check you actually ran resolved it — a re-stated conclusion is not new " +
2411
+ "evidence.</system-reminder>";
2412
+ emitFinalVerifyEcho(nudgeBody);
2334
2413
  return [
2335
2414
  {
2336
2415
  role: "user",
2337
2416
  engineMinted: true,
2338
- content: "<system-reminder>[final verification] Before finishing: re-verify the FINAL deliverable through its REAL entry point, " +
2339
- "exactly as the acceptance criteria would exercise it — execute the binary/function/endpoint directly and read the ACTUAL " +
2340
- "output and exit code. Do NOT rely on earlier self-tests, shell redirections, or assumptions (a program that prints to " +
2341
- "stdout is not a program that writes the required file). If anything mismatches the task's requirements, fix it before " +
2342
- "finishing. " +
2343
- "Treat verification writes as state-harmless: when the deliverable itself is a persisted final " +
2344
- "state (for example a committed or pushed file, a deployed artifact, or a required output file), " +
2345
- "do not change that state merely to test it. This constrains HOW you verify — it is never a " +
2346
- "license to skip the real acceptance path or to check a substitute of your own making: expected " +
2347
- "values must come from the task's requirements, never from content you generated. If the real " +
2348
- "acceptance path requires a write, use disposable inputs or an isolated target, end in the exact " +
2349
- "required final state, and verify that final state before finishing. " +
2350
- "If the work relied on a third-party API, library, or model, check the usage contract the object itself declares " +
2351
- "(docstrings, metadata, configuration — e.g. prompt conventions shipped with a model) and confirm your calls follow " +
2352
- "it rather than a default symmetric usage. Verify not only that the deliverable EXISTS but that the METHOD that " +
2353
- "produced it matches the task's requirements. " +
2354
- "Choose the verification SURFACE deliberately: check against the reference data, oracle, or evaluation tooling the " +
2355
- "task itself provides — re-running your own implementation and getting the same answer is self-consistency, not " +
2356
- "correctness — and cross-check through an independent second path where the task or environment offers one " +
2357
- "(checksums, runtime artifacts). Verify the PERSISTED artifact — re-read what is actually on disk or committed, " +
2358
- "not in-memory state — against every hard constraint from the original task text (numeric bounds, allowed-value " +
2359
- "lists, naming semantics, required files), reconciling whole-set completeness: nothing missing, nothing duplicated. " +
2360
- "If the deliverable embeds verifiable structure — structural fields, an embedded checksum-family value, " +
2361
- "reference data it must match, or a replayable deterministic path — you MUST execute the grounding check " +
2362
- "that structure supports and REPORT its concrete result in your closing summary: for such a deliverable, " +
2363
- "no reported check result means the work is not finished. " +
2364
- "If the task produced neither an executable deliverable nor any verifiable structure or acceptance " +
2365
- "oracle to check against, briefly confirm completion and stop.</system-reminder>",
2417
+ content: nudgeBody,
2366
2418
  timestamp: Date.now(),
2367
2419
  },
2368
2420
  ];
@@ -2433,6 +2485,7 @@ export class Runner {
2433
2485
  ...centerAdoptionOption(prepared),
2434
2486
  model: prepared.harness.getModel(),
2435
2487
  compactionModel: prepared.compModel,
2488
+ ...forkContextOption(prepared, true),
2436
2489
  brain: compactionBrain,
2437
2490
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
2438
2491
  thinking: prepared.thinking,
@@ -2801,7 +2854,7 @@ export class Runner {
2801
2854
  });
2802
2855
  }
2803
2856
  }
2804
- if (rs.attach.attachState?.postCompactPending === true && rs.attach.attachmentsCfg?.backgroundTasks === true) {
2857
+ if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
2805
2858
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
2806
2859
  }
2807
2860
  const comp = await this.finish(spec, prepared, {
@@ -3649,6 +3702,7 @@ export class Runner {
3649
3702
  ...centerAdoptionOption(prepared),
3650
3703
  model: prepared.model,
3651
3704
  compactionModel: prepared.compModel,
3705
+ ...forkContextOption(prepared, false),
3652
3706
  brain: opts?.brain ?? this.deps.brain,
3653
3707
  getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
3654
3708
  thinking: prepared.thinking,
@@ -367,7 +367,9 @@ function renderBackgroundTasks(tasks) {
367
367
  ? "stopped"
368
368
  : t.status === "running"
369
369
  ? "still running in background"
370
- : t.status;
370
+ : t.status === "parked"
371
+ ? "parked awaiting an out-of-band approval — do NOT re-issue the gated call"
372
+ : t.status;
371
373
  return `- [${t.id}] Task "${(t.description ?? "background task").slice(0, PROJECTION_CONTENT_MAX)}" ${phrase}`;
372
374
  });
373
375
  return ("Context was compacted. Background tasks from before the compaction (check output with TaskOutput, " +
@@ -10,6 +10,7 @@ export interface TtlSessionStoreOptions {
10
10
  evict?: EvictPolicy;
11
11
  }
12
12
  export declare class TtlSessionStore implements SessionStore {
13
+ readonly retention: "none";
13
14
  private repo;
14
15
  private entries;
15
16
  private owners;
@@ -4,6 +4,7 @@ const DAY_MS = 24 * 60 * 60 * 1000;
4
4
  import { SESSION_DEFAULT_TTL_DAYS } from "../config/defaults.js";
5
5
  export { SESSION_DEFAULT_TTL_DAYS };
6
6
  export class TtlSessionStore {
7
+ retention = "none";
7
8
  repo;
8
9
  entries = new Map();
9
10
  owners = new Map();
@@ -1,5 +1,6 @@
1
1
  import type { AgentTool } from "../internal/harness-types.js";
2
2
  export interface ToolResultStore {
3
+ readonly retention?: import("./retention.js").RetentionDeclaration;
3
4
  put(ref: string, content: string): Promise<void> | void;
4
5
  get(ref: string, opts?: {
5
6
  offset?: number;
@@ -15,6 +16,7 @@ export interface ToolResultSlice {
15
16
  }
16
17
  export declare class InMemoryToolResultStore implements ToolResultStore {
17
18
  private readonly opts?;
19
+ readonly retention: "none";
18
20
  private readonly map;
19
21
  private totalChars;
20
22
  constructor(opts?: {
@@ -25,6 +25,7 @@ function refSegment(raw) {
25
25
  }
26
26
  export class InMemoryToolResultStore {
27
27
  opts;
28
+ retention = "none";
28
29
  map = new Map();
29
30
  totalChars = 0;
30
31
  constructor(opts) {
@@ -3,6 +3,10 @@ import type { AgentTool, ThinkingLevel } from "../internal/harness.js";
3
3
  import type { CompleteSimpleFn, DocumentContent, ImageContent, Model, ResilienceOptions, StreamFn, TextContent } from "../internal/llm.js";
4
4
  import type { TaskNotificationPayload } from "./task-notification.js";
5
5
  export type ModelRef = string | Model;
6
+ export interface StaleToolResultOffloadOptions {
7
+ keepRecentPerTool?: number;
8
+ minSavingsChars?: number;
9
+ }
6
10
  export type ModelRole = "default" | "summarize" | "subagent" | "team" | "synthesize" | "advisor" | "verifier" | "classifier";
7
11
  export type RoleSpec = ModelRef | {
8
12
  model?: ModelRef;
@@ -371,6 +375,7 @@ export interface TaskSpec {
371
375
  maxFiles?: number;
372
376
  maxCharsPerFile?: number;
373
377
  };
378
+ staleToolResultOffload?: StaleToolResultOffloadOptions;
374
379
  clampTolerance?: number;
375
380
  };
376
381
  attachments?: {
@@ -382,7 +387,7 @@ export interface TaskSpec {
382
387
  };
383
388
  planModeReminder?: true;
384
389
  budgetUsd?: true;
385
- backgroundTasks?: true;
390
+ backgroundTasks?: boolean;
386
391
  toolsDelta?: true;
387
392
  agentListing?: boolean;
388
393
  skillsListing?: boolean;
@@ -579,7 +584,7 @@ export type TaskEvent = ({
579
584
  reason?: string;
580
585
  } & TaskEventIdentity) | ({
581
586
  type: "steering_injected";
582
- source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
587
+ source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools" | "final_verification";
583
588
  preview: string;
584
589
  } & TaskEventIdentity) | ({
585
590
  type: "diagnostics";
@@ -732,6 +737,11 @@ export interface RunnerDeps {
732
737
  fileSnapshotStore?: import("./file-snapshot-store.js").FileSnapshotStore;
733
738
  sessionPolicyStore?: import("./session-policy-store.js").SessionPolicyStore;
734
739
  runtimeCapsResolver?: (principal: string | undefined) => RuntimeCaps | undefined | Promise<RuntimeCaps | undefined>;
740
+ lockedConfig?: import("./locked-config.js").LockedConfig;
741
+ compliancePostureResolver?: (principal: string | undefined) => import("./compliance.js").CompliancePosture | undefined | Promise<import("./compliance.js").CompliancePosture | undefined>;
742
+ memoryScopeAdmission?: import("./memory-admission.js").MemoryScopeAdmission;
743
+ deploymentMemoryScopes?: readonly string[];
744
+ retentionPolicy?: import("./retention.js").RetentionPolicy;
735
745
  autoMode?: {
736
746
  rules?: import("./auto-mode-prompt.js").AutoModeRules;
737
747
  settingsDenyRules?: readonly string[];
@@ -1,4 +1,4 @@
1
- import type { Model, StreamFn, Usage } from "../llm/index.js";
1
+ import type { Context, Message, Model, StreamFn, Tool, Usage } from "../llm/index.js";
2
2
  import { type AgentCoreCompletionRuntimeDeps } from "../loop/runtime-deps.js";
3
3
  import type { AgentMessage, ThinkingLevel } from "../loop/types.js";
4
4
  import { CompactionError, type Result, type SessionTreeEntry } from "../harness/types.js";
@@ -47,6 +47,15 @@ export interface CutPointResult {
47
47
  }
48
48
  export declare function findCutPoint(entries: SessionTreeEntry[], startIndex: number, endIndex: number, keepRecentTokens: number, charsPerToken?: number): CutPointResult;
49
49
  export declare const SUMMARIZATION_SYSTEM_PROMPT = "You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.";
50
+ export interface CompactionForkContext {
51
+ systemPrompt?: string;
52
+ systemBlocks?: Context["systemBlocks"];
53
+ messages: Message[];
54
+ tools?: Tool[];
55
+ modelId?: string;
56
+ }
57
+ export declare function extractForkSummaryEnvelope(text: string): string | undefined;
58
+ export declare function forkSummarizationInstruction(customInstructions?: string): string;
50
59
  export declare function summaryOutputBudgetTokens(model: Model, settings: CompactionSettings): number;
51
60
  export interface SummarizationInputTruncation {
52
61
  label: "history" | "turn_prefix";
@@ -77,5 +86,5 @@ export interface CompactionPreparation {
77
86
  }
78
87
  export declare function prepareCompaction(pathEntries: SessionTreeEntry[], settings: CompactionSettings, charsPerToken?: number, windowTokens?: number): Result<CompactionPreparation | undefined, CompactionError>;
79
88
  export { computeFileLists, serializeConversation } from "./utils.js";
80
- export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string | undefined, headers?: Record<string, string>, customInstructions?: string, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn, runtime?: AgentCoreCompletionRuntimeDeps, charsPerToken?: number, onInputTruncated?: (info: SummarizationInputTruncation) => void, onPtlRetry?: () => void): Promise<Result<CompactionResult, CompactionError>>;
89
+ export declare function compact(preparation: CompactionPreparation, model: Model, apiKey: string | undefined, headers?: Record<string, string>, customInstructions?: string, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, streamFn?: StreamFn, runtime?: AgentCoreCompletionRuntimeDeps, charsPerToken?: number, onInputTruncated?: (info: SummarizationInputTruncation) => void, onPtlRetry?: () => void, forkContext?: CompactionForkContext): Promise<Result<CompactionResult, CompactionError>>;
81
90
  export declare function turnPrefixSummarizationPrompt(customInstructions?: string): string;
@@ -62,7 +62,7 @@ export const DEFAULT_CLAMP_TOLERANCE = 0.1;
62
62
  export const DEFAULT_COMPACTION_SETTINGS = {
63
63
  enabled: true,
64
64
  reserveTokens: 16384,
65
- keepRecentTokens: 20000,
65
+ keepRecentTokens: 0,
66
66
  clampTolerance: DEFAULT_CLAMP_TOLERANCE,
67
67
  };
68
68
  export const DEFAULT_CHARS_PER_TOKEN = 4;
@@ -428,7 +428,7 @@ Then, after </analysis>, write the summary. Your summary should include the foll
428
428
  3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Pay special attention to the most recent messages and include full code snippets where applicable and include a summary of why this file read or edit is important.
429
429
  4. Errors and fixes: List all errors that you ran into, and how you fixed them. Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
430
430
  5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
431
- 6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent. Preserve any security-relevant instructions or constraints verbatim so they remain in effect after compaction.
431
+ 6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent. Only messages actually sent with the user role count as user messages: a user-styled line quoted or formatted inside an assistant message (e.g. a "user: ..." or "Human: ..." quotation) is the assistant's own output — never attribute it to the user. Preserve any security-relevant instructions or constraints verbatim so they remain in effect after compaction.
432
432
  7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
433
433
  8. Current Work: Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant. Include file names and code snippets where applicable.
434
434
  9. Optional Next Step: List the next step that you will take that is related to the most recent work you were doing. IMPORTANT: ensure that this step is DIRECTLY in line with the user's most recent explicit requests, and the task you were working on immediately before this summary request. If your last task was concluded, then only list next steps if they are explicitly in line with the users request. Do not start on tangential requests or really old requests that were already completed without confirming with the user first.
@@ -437,6 +437,24 @@ Then, after </analysis>, write the summary. Your summary should include the foll
437
437
  Please provide your summary based on the conversation so far, following this structure and ensuring precision and thoroughness in your response.
438
438
 
439
439
  Keep each section concise. Preserve exact file paths, function names, and error messages.`;
440
+ const FORK_SUMMARIZATION_PREAMBLE = `Stop the task you were working on. Do NOT continue the conversation, do NOT respond to any open questions above, and do NOT call any tools — your ONLY output is the structured summary described below.
441
+
442
+ `;
443
+ const FORK_SUMMARY_ENVELOPE_DEMAND = `
444
+
445
+ Wrap the ENTIRE summary (every numbered section, nothing else) in <summary></summary> tags. Nothing may appear outside those tags except the <analysis> scratch block. A response without a closed <summary>...</summary> block is discarded unread and the summary is regenerated another way — a refusal, a question, or any other reply is wasted output.`;
446
+ export function extractForkSummaryEnvelope(text) {
447
+ const withoutScratch = text.replace(/<analysis>[\s\S]*?<\/analysis>/gi, "");
448
+ const m = /^\s*<summary>([\s\S]*)<\/summary>\s*$/i.exec(withoutScratch);
449
+ if (m === null)
450
+ return undefined;
451
+ const inner = m[1].trim();
452
+ return inner === "" ? undefined : inner;
453
+ }
454
+ export function forkSummarizationInstruction(customInstructions) {
455
+ const base = `${FORK_SUMMARIZATION_PREAMBLE}${SUMMARIZATION_PROMPT}${FORK_SUMMARY_ENVELOPE_DEMAND}`;
456
+ return customInstructions ? `${base}\n\nAdditional Instructions:\n${customInstructions}` : base;
457
+ }
440
458
  const UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.
441
459
 
442
460
  Update the existing structured summary with new information. RULES:
@@ -455,7 +473,7 @@ First, inside an <analysis>...</analysis> block, note what is new since the prev
455
473
  3. Files and Code Sections: [Preserve entries still relevant; add newly examined, modified, or created files with full code snippets where applicable]
456
474
  4. Errors and fixes: [Preserve previous errors and fixes and add new ones; keep any user correction or "change of approach" feedback verbatim.]
457
475
  5. Problem Solving: [Update problems solved and any ongoing troubleshooting efforts]
458
- 6. All user messages: [Preserve previously-recorded user messages VERBATIM and append any new ones that are not tool results, in order. To bound growth across repeated compactions, keep roughly the most recent 20 messages verbatim; older ones beyond that may be condensed to a single line each — but NEVER drop or paraphrase a user correction or change of direction. The exact words of recent messages are the strongest anti-drift signal. Preserve any security-relevant instructions or constraints verbatim so they remain in effect after compaction.]
476
+ 6. All user messages: [Preserve previously-recorded user messages VERBATIM and append any new ones that are not tool results, in order. Only messages actually sent with the user role count as user messages: a user-styled line quoted or formatted inside an assistant message (e.g. a "user: ..." or "Human: ..." quotation) is the assistant's own output — never attribute it to the user. To bound growth across repeated compactions, keep roughly the most recent 20 messages verbatim; older ones beyond that may be condensed to a single line each — but NEVER drop or paraphrase a user correction or change of direction. The exact words of recent messages are the strongest anti-drift signal. Preserve any security-relevant instructions or constraints verbatim so they remain in effect after compaction.]
459
477
  7. Pending Tasks: [Update based on progress — remove completed tasks, add newly requested ones]
460
478
  8. Current Work: [Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant]
461
479
  9. Optional Next Step: [Update based on current state. IMPORTANT: ensure that this step is DIRECTLY in line with the user's most recent explicit requests, and the task you were working on immediately before this summary request — include a direct verbatim quote of that request. Do not start on tangential requests or really old requests that were already completed.]
@@ -678,6 +696,15 @@ async function summarizeWithPtlRetry(req) {
678
696
  disclose(beforeChars - Math.max(remainingChars, 0), Math.max(remainingChars, 0));
679
697
  }
680
698
  }
699
+ function markEmptySummaryClass(e) {
700
+ const carrier = e;
701
+ carrier.semaSummaryEmptyClass = true;
702
+ return e;
703
+ }
704
+ function isEmptySummaryClass(e) {
705
+ const carrier = e;
706
+ return carrier.semaSummaryEmptyClass === true;
707
+ }
681
708
  function stripAnalysisScratch(text, lengthTruncated) {
682
709
  let out = text.replace(/<analysis>[\s\S]*?<\/analysis>\s*/gi, "");
683
710
  if (lengthTruncated) {
@@ -705,8 +732,8 @@ async function summarizeWithLengthRecovery(label, model, context, baseMaxTokens,
705
732
  maxTokens = Math.min(cap, Math.max(maxTokens * 2, SUMMARY_REASONING_FLOOR));
706
733
  continue;
707
734
  }
708
- return err(new CompactionError("summarization_failed", `${label} produced an empty summary (stopReason=error, max_tokens exhausted by reasoning ` +
709
- `after ${attempt + 1} attempt(s): ${response.errorMessage || "no detail"})`));
735
+ return err(markEmptySummaryClass(new CompactionError("summarization_failed", `${label} produced an empty summary (stopReason=error, max_tokens exhausted by reasoning ` +
736
+ `after ${attempt + 1} attempt(s): ${response.errorMessage || "no detail"})`)));
710
737
  }
711
738
  return err(new CompactionError("summarization_failed", `${label} failed: ${response.errorMessage || "Unknown error"}`));
712
739
  }
@@ -723,10 +750,43 @@ async function summarizeWithLengthRecovery(label, model, context, baseMaxTokens,
723
750
  maxTokens = Math.min(cap, Math.max(maxTokens * 2, SUMMARY_REASONING_FLOOR));
724
751
  continue;
725
752
  }
726
- return err(new CompactionError("summarization_failed", `${label} produced an empty summary (stopReason=${response.stopReason}` +
727
- `${lengthTruncated ? ", the output was analysis scratch cut at max_tokens" : ""})`));
753
+ return err(markEmptySummaryClass(new CompactionError("summarization_failed", `${label} produced an empty summary (stopReason=${response.stopReason}` +
754
+ `${lengthTruncated ? ", the output was analysis scratch cut at max_tokens" : ""})`)));
728
755
  }
729
756
  }
757
+ async function forkSummarize(fork, model, baseMaxTokens, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken) {
758
+ const instruction = forkSummarizationInstruction(customInstructions);
759
+ const context = {
760
+ ...(fork.systemPrompt !== undefined ? { systemPrompt: fork.systemPrompt } : {}),
761
+ ...(fork.systemBlocks !== undefined ? { systemBlocks: fork.systemBlocks } : {}),
762
+ ...(fork.tools !== undefined && fork.tools.length > 0 ? { tools: fork.tools } : {}),
763
+ messages: [
764
+ ...fork.messages,
765
+ { role: "user", content: [{ type: "text", text: instruction }], timestamp: Date.now() },
766
+ ],
767
+ };
768
+ let result;
769
+ try {
770
+ result = await summarizeWithLengthRecovery("Summarization", model, context, baseMaxTokens, apiKey, headers, signal, thinkingLevel, streamFn, runtime, charsPerToken);
771
+ }
772
+ catch (e) {
773
+ const msg = e instanceof Error ? e.message : String(e);
774
+ if (parsePromptTooLong(msg).isPtl)
775
+ return { kind: "fallback", detail: msg };
776
+ throw e;
777
+ }
778
+ if (!result.ok) {
779
+ if (result.error.code === "summarization_failed" && (isEmptySummaryClass(result.error) || parsePromptTooLong(result.error.message).isPtl)) {
780
+ return { kind: "fallback", detail: result.error.message };
781
+ }
782
+ return { kind: "err", error: result.error };
783
+ }
784
+ const enveloped = extractForkSummaryEnvelope(result.value);
785
+ if (enveloped === undefined) {
786
+ return { kind: "fallback", detail: "fork response lacked a closed <summary> envelope (non-conforming output)" };
787
+ }
788
+ return { kind: "ok", summary: enveloped };
789
+ }
730
790
  export async function generateSummary(currentMessages, model, summaryBudgetTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry) {
731
791
  let basePrompt = previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;
732
792
  if (customInstructions) {
@@ -866,7 +926,7 @@ Summarize the prefix to provide context for the retained suffix:
866
926
 
867
927
  Be concise. Focus on what's needed to understand the kept suffix.`;
868
928
  export { computeFileLists, serializeConversation } from "./utils.js";
869
- export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry) {
929
+ export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry, forkContext) {
870
930
  const { firstKeptEntryId, messagesToSummarize, turnPrefixMessages, isSplitTurn, tokensBefore, previousSummary, fileOps, invokedSkills, persistedOutputRefs, elidedMessages, settings, } = preparation;
871
931
  if (!firstKeptEntryId) {
872
932
  return err(new CompactionError("invalid_session", "First kept entry has no UUID - session may need migration"));
@@ -876,7 +936,25 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
876
936
  }
877
937
  let summary;
878
938
  const summaryBudget = summaryOutputBudgetTokens(model, settings);
879
- if (isSplitTurn && turnPrefixMessages.length > 0) {
939
+ if (forkContext !== undefined && forkContext.messages.length > 0) {
940
+ const forked = await forkSummarize(forkContext, model, Math.floor(0.8 * summaryBudget), apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, runtime, charsPerToken);
941
+ if (forked.kind === "ok") {
942
+ summary = forked.summary;
943
+ }
944
+ else if (forked.kind === "err") {
945
+ return err(forked.error);
946
+ }
947
+ else {
948
+ try {
949
+ onPtlRetry?.();
950
+ }
951
+ catch {
952
+ }
953
+ }
954
+ }
955
+ if (summary !== undefined) {
956
+ }
957
+ else if (isSplitTurn && turnPrefixMessages.length > 0) {
880
958
  const [historyResult, turnPrefixResult] = await Promise.all([
881
959
  messagesToSummarize.length > 0
882
960
  ? generateSummary(messagesToSummarize, model, summaryBudget, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, runtime, charsPerToken, onInputTruncated, onPtlRetry)
@@ -27,6 +27,7 @@ function createUserMessage(text, images, provenance) {
27
27
  };
28
28
  }
29
29
  const engineNotePayloads = new WeakMap();
30
+ const ENGINE_NOTE_STEER_BACKLOG_CAP = 50;
30
31
  function createFailureMessage(model, error, aborted) {
31
32
  return {
32
33
  role: "assistant",
@@ -418,7 +419,13 @@ export class AgentHarness {
418
419
  };
419
420
  }
420
421
  async drainQueuedMessages(queue, mode) {
421
- const messages = mode === "all" ? queue.splice(0) : queue.splice(0, 1);
422
+ let count = mode === "all" ? queue.length : 1;
423
+ if (mode !== "all" && queue.length > 1 && engineNotePayloads.has(queue[0])) {
424
+ count = 1;
425
+ while (count < queue.length && engineNotePayloads.has(queue[count]))
426
+ count++;
427
+ }
428
+ const messages = queue.splice(0, count);
422
429
  if (messages.length === 0) {
423
430
  return messages;
424
431
  }
@@ -713,6 +720,9 @@ export class AgentHarness {
713
720
  async enqueueInjection(queue, text, options) {
714
721
  if (AgentHarness.emptyInjection(text, options))
715
722
  return;
723
+ if (options?.enginePayload !== undefined && queue.filter((q) => engineNotePayloads.has(q)).length >= ENGINE_NOTE_STEER_BACKLOG_CAP) {
724
+ throw new AgentHarnessError("invalid_state", `engine-note backlog at cap (${ENGINE_NOTE_STEER_BACKLOG_CAP}) — park the payload for the session's next run`);
725
+ }
716
726
  const m = createUserMessage(text, options?.images, options);
717
727
  if (options?.enginePayload !== undefined)
718
728
  engineNotePayloads.set(m, options.enginePayload);
@@ -288,5 +288,15 @@ export function validateToolArguments(tool, toolCall) {
288
288
  .Errors(args)
289
289
  .map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)
290
290
  .join("\n") || "Unknown validation error";
291
- throw new Error(`Validation failed for tool "${toolCall.name}":\n${errors}\n\nReceived arguments:\n${JSON.stringify(toolCall.arguments, null, 2)}`);
291
+ const schemaJson = (() => {
292
+ try {
293
+ const s = JSON.stringify(tool.parameters);
294
+ return s.length > VALIDATION_ERROR_SCHEMA_MAX_CHARS ? `${s.slice(0, VALIDATION_ERROR_SCHEMA_MAX_CHARS)}… (schema truncated)` : s;
295
+ }
296
+ catch {
297
+ return "(schema not serializable)";
298
+ }
299
+ })();
300
+ throw new Error(`Validation failed for tool "${toolCall.name}":\n${errors}\n\nReceived arguments:\n${JSON.stringify(toolCall.arguments, null, 2)}\n\nExpected parameter schema:\n${schemaJson}`);
292
301
  }
302
+ const VALIDATION_ERROR_SCHEMA_MAX_CHARS = 4_000;