@sema-agent/core 5.12.0 → 5.14.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 (101) hide show
  1. package/CHANGELOG.md +320 -0
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +4 -0
  4. package/dist/agents/subagent.js +137 -43
  5. package/dist/brain/anthropic.js +33 -10
  6. package/dist/brain/context-overflow.d.ts +20 -0
  7. package/dist/brain/context-overflow.js +58 -0
  8. package/dist/brain/open-responses.js +24 -10
  9. package/dist/brain/openai.js +29 -11
  10. package/dist/brain/request-params.d.ts +2 -0
  11. package/dist/brain/request-params.js +16 -0
  12. package/dist/brain/stream-engine.d.ts +9 -1
  13. package/dist/brain/stream-engine.js +256 -27
  14. package/dist/brain/timeout.d.ts +1 -0
  15. package/dist/brain/timeout.js +1 -0
  16. package/dist/core/a2a.d.ts +2 -2
  17. package/dist/core/a2a.js +3 -3
  18. package/dist/core/ask-question.d.ts +47 -2
  19. package/dist/core/ask-question.js +209 -28
  20. package/dist/core/background-agent-store.d.ts +2 -0
  21. package/dist/core/checkpoint-store.d.ts +49 -18
  22. package/dist/core/checkpoint-store.js +117 -4
  23. package/dist/core/compliance.d.ts +11 -0
  24. package/dist/core/compliance.js +34 -0
  25. package/dist/core/governance-codes.d.ts +12 -0
  26. package/dist/core/governance-codes.js +24 -0
  27. package/dist/core/hooks.d.ts +24 -2
  28. package/dist/core/hooks.js +97 -10
  29. package/dist/core/human-input-projection.d.ts +12 -0
  30. package/dist/core/human-input-projection.js +27 -0
  31. package/dist/core/locked-config.d.ts +27 -0
  32. package/dist/core/locked-config.js +42 -0
  33. package/dist/core/mcp.d.ts +7 -2
  34. package/dist/core/mcp.js +7 -7
  35. package/dist/core/memory-admission.d.ts +51 -0
  36. package/dist/core/memory-admission.js +159 -0
  37. package/dist/core/memory.d.ts +2 -0
  38. package/dist/core/memory.js +3 -2
  39. package/dist/core/retention.d.ts +36 -0
  40. package/dist/core/retention.js +31 -0
  41. package/dist/core/runner/assemble-result.d.ts +1 -0
  42. package/dist/core/runner/assemble-result.js +2 -2
  43. package/dist/core/runner/prepare-memory.d.ts +9 -0
  44. package/dist/core/runner/prepare-memory.js +28 -2
  45. package/dist/core/runner/prepare-task.d.ts +18 -6
  46. package/dist/core/runner/prepare-task.js +428 -38
  47. package/dist/core/runner/runtask.d.ts +3 -6
  48. package/dist/core/runner/runtask.js +258 -77
  49. package/dist/core/runner/tool-output-projection.js +1 -0
  50. package/dist/core/session-store.d.ts +4 -0
  51. package/dist/core/session-store.js +5 -0
  52. package/dist/core/session.d.ts +1 -0
  53. package/dist/core/store-contracts/background-agent-store-contract.js +19 -0
  54. package/dist/core/store-contracts/checkpoint-store-contract.js +62 -3
  55. package/dist/core/task-notification.d.ts +2 -0
  56. package/dist/core/task-notification.js +5 -3
  57. package/dist/core/task-registry-agent.d.ts +1 -0
  58. package/dist/core/task-registry-agent.js +6 -0
  59. package/dist/core/task-registry.d.ts +1 -0
  60. package/dist/core/task-registry.js +4 -1
  61. package/dist/core/tool-policy.d.ts +5 -0
  62. package/dist/core/tool-policy.js +2 -1
  63. package/dist/core/tool-result-store.d.ts +2 -0
  64. package/dist/core/tool-result-store.js +1 -0
  65. package/dist/core/types.d.ts +38 -2
  66. package/dist/core/wiring-manifest.d.ts +97 -0
  67. package/dist/core/wiring-manifest.js +186 -0
  68. package/dist/engine/compaction/compaction.js +2 -2
  69. package/dist/engine/harness/agent-harness.d.ts +2 -1
  70. package/dist/engine/harness/agent-harness.js +19 -2
  71. package/dist/engine/harness/types.d.ts +3 -1
  72. package/dist/engine/llm/types.d.ts +7 -0
  73. package/dist/engine/llm/types.js +8 -1
  74. package/dist/engine/llm/validation.js +11 -1
  75. package/dist/engine/session/import-validate.d.ts +6 -1
  76. package/dist/engine/session/import-validate.js +29 -6
  77. package/dist/engine/session/memory-repo.d.ts +3 -1
  78. package/dist/engine/session/memory-repo.js +2 -2
  79. package/dist/index.d.ts +12 -4
  80. package/dist/index.js +12 -4
  81. package/dist/internal/harness-types.d.ts +1 -1
  82. package/dist/internal/llm.d.ts +2 -2
  83. package/dist/internal/llm.js +1 -1
  84. package/dist/orchestration/run-workflow-tool.d.ts +4 -0
  85. package/dist/orchestration/run-workflow-tool.js +3 -0
  86. package/dist/orchestration/workflow-types.d.ts +8 -0
  87. package/dist/orchestration/workflow-types.js +14 -0
  88. package/dist/orchestration/workflow.d.ts +4 -0
  89. package/dist/orchestration/workflow.js +134 -5
  90. package/dist/prompts/default.js +1 -1
  91. package/dist/stores/file/checkpoint-store.d.ts +3 -5
  92. package/dist/stores/file/checkpoint-store.js +31 -2
  93. package/dist/stores/file/index.js +1 -1
  94. package/dist/stores/file/session-store.d.ts +3 -1
  95. package/dist/stores/file/session-store.js +2 -2
  96. package/dist/stores/file/shared-ledger.js +8 -1
  97. package/dist/tools/fs/bash-readonly-classifier.d.ts +3 -0
  98. package/dist/tools/fs/bash-readonly-classifier.js +94 -0
  99. package/dist/tools/fs/fs-bash.js +31 -12
  100. package/dist/tools/fs/safety.js +34 -10
  101. package/package.json +1 -1
@@ -17,7 +17,9 @@ import { uuidv7 } from "../internal/harness.js";
17
17
  import { addWorktree } from "../core/git-worktree-env.js";
18
18
  import { shellQuote } from "../tools/fs/search.js";
19
19
  import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
20
+ import { readDurableOrgAdmission } from "../core/memory-admission.js";
20
21
  import { extractErrorCode } from "../brain/errors.js";
22
+ import { governanceRetryClass } from "../core/governance-codes.js";
21
23
  import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
22
24
  function rollupDelegatedCost(stats, nested) {
23
25
  if (stats.costMicroUsd === undefined)
@@ -52,6 +54,39 @@ export function notifyResultField(result) {
52
54
  ? `${result.slice(0, BG_AGENT_NOTIFY_RESULT_MAX)}\n[result truncated: ${result.length} chars total — call TaskOutput for the full text]`
53
55
  : result;
54
56
  }
57
+ const HANDBACK_ASK_MESSAGE = "Subagent has finished and is handing back control to the main agent. Review the subagent's work and flag if any action may violate security policy.";
58
+ async function reviewHandback(opts) {
59
+ const { review, evidence, signal } = opts;
60
+ if (review === undefined)
61
+ return undefined;
62
+ if (signal?.aborted === true)
63
+ return undefined;
64
+ if (!evidence.result && !evidence.steps && !evidence.edits && !evidence.partialText)
65
+ return undefined;
66
+ const verdict = await review.decider
67
+ .decide({
68
+ req: {
69
+ toolName: opts.toolName,
70
+ args: {
71
+ result: evidence.result?.slice(0, 2_000),
72
+ toolSteps: evidence.steps,
73
+ editedFiles: evidence.edits,
74
+ ...(evidence.partialText ? { partialFindings: evidence.partialText.slice(0, 2_000) } : {}),
75
+ },
76
+ toolCallId: opts.toolCallId,
77
+ },
78
+ askMessage: HANDBACK_ASK_MESSAGE,
79
+ }, signal)
80
+ .catch(() => ({ kind: "unavailable", cause: "error" }));
81
+ if (verdict.kind !== "block")
82
+ return undefined;
83
+ return `SECURITY WARNING: This subagent performed actions that may violate security policy. Reason: ${inlineUntrusted(verdict.reason ?? "(no reason given)")}. Review the subagent's actions carefully before acting on its output.`;
84
+ }
85
+ function withHandbackWarning(warning, result) {
86
+ if (warning === undefined)
87
+ return result;
88
+ return result ? `${warning}\n\n${result}` : warning;
89
+ }
55
90
  export function inheritedManifestScopeFor(snapshot) {
56
91
  if (!snapshot || snapshot.length === 0)
57
92
  return undefined;
@@ -221,6 +256,7 @@ export function classifySubagentError(child) {
221
256
  return undefined;
222
257
  const code = child.errorCode ?? extractErrorCode(child.errorMessage);
223
258
  const msg = (child.errorMessage ?? "").toLowerCase();
259
+ const governanceClass = code !== undefined ? governanceRetryClass(code) : undefined;
224
260
  const errorKind = code === "rate_limit"
225
261
  ? "rate_limit"
226
262
  : code === "server" && (msg.includes("overloaded") || msg.includes("529"))
@@ -229,10 +265,10 @@ export function classifySubagentError(child) {
229
265
  ? "timeout"
230
266
  : code === "network" || code === "server"
231
267
  ? "network"
232
- : code !== undefined && code.startsWith("usage.")
268
+ : governanceClass !== undefined
233
269
  ? "governance"
234
270
  : "logic";
235
- return { errorKind, retryable: errorKind !== "logic" && errorKind !== "governance" };
271
+ return { errorKind, retryable: errorKind === "governance" ? governanceClass === "transient" : errorKind !== "logic" };
236
272
  }
237
273
  export function completedAgentCard(child, extras) {
238
274
  return {
@@ -249,6 +285,7 @@ export function completedAgentCard(child, extras) {
249
285
  ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}),
250
286
  ...(child.retryAfterMs !== undefined ? { retryAfterMs: child.retryAfterMs } : {}),
251
287
  ...(child.degraded !== undefined ? { degraded: child.degraded } : {}),
288
+ ...(extras.handbackWarning !== undefined ? { handbackWarning: extras.handbackWarning } : {}),
252
289
  ...(extras.error ? { error_kind: extras.error.errorKind, retryable: extras.error.retryable } : {}),
253
290
  stats: child.stats,
254
291
  ...(child.model !== undefined ? { model: child.model, resolvedModel: child.model } : {}),
@@ -284,7 +321,7 @@ function durablePauseDelegationReport(reportLabel, child) {
284
321
  function unparkedDurablePauseReason(d) {
285
322
  return d.kind === "no_park_lane"
286
323
  ? `the agent hit an approval gate and paused durably, but this delegation cannot be parked ` +
287
- `(parking needs an explicitly named child plus a durable agent row, a checkpoint store and the ` +
324
+ `(parking needs a durable agent row, a checkpoint store and the ` +
288
325
  `durable-session capability). Its checkpoint ${d.checkpointToken} is still committed and the ` +
289
326
  `approval must be resolved through the deployment's approval channel; the gated action never ran.`
290
327
  : `the agent hit an approval gate and paused durably, but the park did not take (` +
@@ -295,6 +332,10 @@ function unparkedDurablePauseReason(d) {
295
332
  function errorKindClause(c) {
296
333
  return c !== undefined ? ` (error_kind: ${c.errorKind}, retryable: ${c.retryable})` : "";
297
334
  }
335
+ function stripSpawnTurnQuestionFace(spec) {
336
+ const { onQuestion: spawnTurnQuestionFace, ...rest } = spec;
337
+ return { spec: rest, internalsFlag: spawnTurnQuestionFace !== undefined ? { questionFaceStripped: true } : {} };
338
+ }
298
339
  const FAILED_SESSION_RETAIN_TTL_MS = 15 * 60 * 1000;
299
340
  const PARTIAL_FINDINGS_MAX_CHARS = 1200;
300
341
  const BG_NOTIFY_DRAIN_WINDOW_MS = 2_000;
@@ -501,6 +542,13 @@ export function createSubagentResume(deps) {
501
542
  }
502
543
  catch {
503
544
  }
545
+ try {
546
+ const resumedOrgVerdict = entry.internalsSnapshot.ownOrgAdmissionRef?.current;
547
+ if (resumedOrgVerdict !== undefined)
548
+ deps.registry.recordBackgroundAgentOrgAdmission(deps.taskId, resumedOrgVerdict);
549
+ }
550
+ catch {
551
+ }
504
552
  },
505
553
  }
506
554
  : {}),
@@ -528,6 +576,14 @@ export function createSubagentResume(deps) {
528
576
  void _;
529
577
  const child = await stream.result();
530
578
  let status = child.status === "completed" ? "completed" : abort.signal.aborted ? "killed" : "failed";
579
+ const resumeHandbackWarning = await reviewHandback({
580
+ review: deps.currentAutoModeReview,
581
+ toolCallId: deps.parentToolCallId,
582
+ toolName: "Agent(handback)",
583
+ evidence: { result: child.result, steps: resumeStepRecorder.recentSteps(), edits: resumeStepRecorder.editedFiles() },
584
+ signal: abort.signal,
585
+ });
586
+ const resumeHandbackResult = withHandbackWarning(resumeHandbackWarning, child.result);
531
587
  const errCodeRevive = status === "failed" ? (child.errorCode ?? extractErrorCode(child.errorMessage)) : undefined;
532
588
  const errClassRevive = status === "failed"
533
589
  ? classifySubagentError({ status: "failed", ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}), ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}) })
@@ -536,7 +592,7 @@ export function createSubagentResume(deps) {
536
592
  try {
537
593
  const winner = deps.registry.settleRevivedAgent(deps.taskId, reviveCycle, {
538
594
  status,
539
- ...resultSettleFields(child.result),
595
+ ...resultSettleFields(resumeHandbackResult),
540
596
  ...(status !== "completed" ? { error: (child.errorMessage ?? `resumed run ${status}`).slice(0, 500) } : {}),
541
597
  ...(errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
542
598
  ...(errClassRevive !== undefined ? { retryable: errClassRevive.retryable, errorKind: errClassRevive.errorKind } : {}),
@@ -583,7 +639,7 @@ export function createSubagentResume(deps) {
583
639
  summary: reviveTerminalSummary,
584
640
  ...(failReasonRevive !== undefined ? { error: failReasonRevive.slice(0, REPORT_FIELD_MAX) } : {}),
585
641
  ...(status === "failed" && errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
586
- ...(child.result ? { result: notifyResultField(child.result) } : {}),
642
+ ...(resumeHandbackResult ? { result: notifyResultField(resumeHandbackResult) } : {}),
587
643
  ...(status === "killed" && child.result ? { partial: true } : {}),
588
644
  ...resumeResidual(),
589
645
  resumable: status !== "killed",
@@ -1302,7 +1358,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1302
1358
  const runObserverLeg = async (spec) => {
1303
1359
  if (!isObserverTaskId(sid))
1304
1360
  throw new ObserverStoppedByUserError("observer identity revoked — delivery refused");
1305
- const r = await opts.runner.runTask(spec);
1361
+ const r = await opts.runner.runTask(spec, ctx.interactionPosture !== undefined ? { parentInteractionPosture: ctx.interactionPosture } : undefined);
1306
1362
  if (r.status !== "completed") {
1307
1363
  if (r.errorCode === "resume.session_not_found") {
1308
1364
  throw new ObserverResumeStateError(`observer session no longer exists${r.errorMessage ? `: ${r.errorMessage}` : ""}`);
@@ -1441,8 +1497,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1441
1497
  ...(inheritedManifestScope ? { inheritedManifestScope } : {}),
1442
1498
  ...(treeScope !== undefined ? { registryScope: treeScope } : {}),
1443
1499
  ...(ctx.inheritedGateForChildren ? { inheritedGate: ctx.inheritedGateForChildren() } : {}),
1500
+ ownOrgAdmissionRef: { current: reviveClaim !== undefined ? readDurableOrgAdmission(reviveClaim.row) : undefined },
1444
1501
  ...(childDefaultPersona !== undefined ? { defaultSystemPrompt: childDefaultPersona } : {}),
1445
1502
  isDelegatedChild: true,
1503
+ ...(ctx.interactionPosture !== undefined ? { parentInteractionPosture: ctx.interactionPosture } : {}),
1446
1504
  parentToolCallId: ctx.toolCallId,
1447
1505
  ...(childAgentName ? { agentName: childAgentName } : {}),
1448
1506
  ...(agentName !== undefined ? { explicitAgentName: agentName } : {}),
@@ -1511,11 +1569,12 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1511
1569
  limits: childLimits,
1512
1570
  signal,
1513
1571
  });
1514
- const tryRetainChild = async (ledger, entryTtlMs, preMintedSessionId, durableProbe, releaseAnchor) => {
1572
+ const tryRetainChild = async (ledger, entryTtlMs, preMintedSessionId, durableProbe, releaseAnchor, internalsExtra) => {
1515
1573
  if (!ledger)
1516
1574
  return undefined;
1517
1575
  const childSessionId = preMintedSessionId ?? uuidv7();
1518
- const { signal: _spawnSignal, onQuestion: _spawnTurnQuestionFace, ...plainSpec } = buildChildSpec(undefined);
1576
+ const retainStrip = stripSpawnTurnQuestionFace(buildChildSpec(undefined));
1577
+ const { signal: _spawnSignal, ...plainSpec } = retainStrip.spec;
1519
1578
  let entry = ledger.register(ctx.toolCallId, {
1520
1579
  childSessionId,
1521
1580
  runner: opts.runner,
@@ -1532,7 +1591,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1532
1591
  ...(plainSpec.deferTools !== undefined ? { deferTools: [...plainSpec.deferTools] } : {}),
1533
1592
  ...(plainSpec.alwaysLoadTools !== undefined ? { alwaysLoadTools: [...plainSpec.alwaysLoadTools] } : {}),
1534
1593
  }),
1535
- internalsSnapshot: { ...childInternals },
1594
+ internalsSnapshot: { ...childInternals, ...retainStrip.internalsFlag, ...(internalsExtra ?? {}) },
1536
1595
  release: async () => {
1537
1596
  try {
1538
1597
  await opts.runner.sessions.unpin?.(childSessionId);
@@ -1595,14 +1654,14 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1595
1654
  if (ledger !== undefined && !ledger.disposed) {
1596
1655
  if (opts.background)
1597
1656
  ensureSessionReapHook(opts.background.registry);
1598
- const { signal: _spawnSignal, onQuestion: _spawnTurnQuestionFace, ...plainSpec } = buildChildSpec(undefined);
1657
+ const failedRetainStrip = stripSpawnTurnQuestionFace(buildChildSpec(undefined));
1658
+ const { signal: _spawnSignal, ...plainSpec } = failedRetainStrip.spec;
1599
1659
  const entry = ledger.register(ctx.toolCallId, {
1600
1660
  childSessionId,
1601
1661
  runner: opts.runner,
1602
1662
  ttlMs: FAILED_SESSION_RETAIN_TTL_MS,
1603
1663
  ...(childAgentName !== undefined ? { agentName: childAgentName } : {}),
1604
1664
  ...(agentName !== undefined ? { explicitAgentName: agentName } : {}),
1605
- ...(agentName !== undefined ? { explicitAgentName: agentName } : {}),
1606
1665
  specSnapshot: Object.freeze({
1607
1666
  ...plainSpec,
1608
1667
  tools: [...(plainSpec.tools ?? [])],
@@ -1613,7 +1672,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1613
1672
  ...(plainSpec.deferTools !== undefined ? { deferTools: [...plainSpec.deferTools] } : {}),
1614
1673
  ...(plainSpec.alwaysLoadTools !== undefined ? { alwaysLoadTools: [...plainSpec.alwaysLoadTools] } : {}),
1615
1674
  }),
1616
- internalsSnapshot: { ...childInternals },
1675
+ internalsSnapshot: { ...childInternals, ...failedRetainStrip.internalsFlag },
1617
1676
  release: releaseChild,
1618
1677
  });
1619
1678
  if (entry) {
@@ -1713,7 +1772,6 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1713
1772
  else if (!sessionScopedBg)
1714
1773
  ctx.signal.addEventListener("abort", onHostAbort, { once: true });
1715
1774
  }
1716
- const dropTurnBoundQuestionFace = sessionScopedBg ? { onQuestion: undefined } : {};
1717
1775
  const shortDesc = `fork: ${(typeof a.description === "string" && a.description.trim() ? a.description.trim() : prompt).slice(0, 180)}`;
1718
1776
  const bgOwner = sessionScopedBg ? ctx.sessionId : ctx.taskId ?? bg.owner;
1719
1777
  const bgScope = treeScope;
@@ -1806,7 +1864,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1806
1864
  startedAt: Date.now(),
1807
1865
  });
1808
1866
  const forkBgStartedAt = Date.now();
1809
- const bgForkSpec = { ...buildChildSpec(abort.signal), sessionId: forkedId, requireExistingSession: true, objective: forkObjective, ...dropTurnBoundQuestionFace };
1867
+ const bgForkSpecBase = { ...buildChildSpec(abort.signal), sessionId: forkedId, requireExistingSession: true, objective: forkObjective };
1868
+ const forkQuestionStrip = sessionScopedBg ? stripSpawnTurnQuestionFace(bgForkSpecBase) : undefined;
1869
+ const bgForkSpec = forkQuestionStrip !== undefined ? forkQuestionStrip.spec : bgForkSpecBase;
1810
1870
  const s2ForkNotifyReady = (inject) => {
1811
1871
  bg.registry.attachAgentNotify(taskId, inject);
1812
1872
  };
@@ -1830,7 +1890,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1830
1890
  const bgForkInternals = bgSink
1831
1891
  ? {
1832
1892
  ...forkInternals,
1893
+ ...(forkQuestionStrip !== undefined ? forkQuestionStrip.internalsFlag : {}),
1833
1894
  onNotifyInjectorReady: s2ForkNotifyReady,
1895
+ delegationTaskType: "background_agent",
1834
1896
  onForwardEvent: (e) => {
1835
1897
  try {
1836
1898
  recordingForward(e, { bgAgentId: taskId });
@@ -1861,7 +1923,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1861
1923
  }
1862
1924
  : {
1863
1925
  ...forkInternals,
1926
+ ...(forkQuestionStrip !== undefined ? forkQuestionStrip.internalsFlag : {}),
1864
1927
  onNotifyInjectorReady: s2ForkNotifyReady,
1928
+ delegationTaskType: "background_agent",
1865
1929
  onForwardEvent: (e) => {
1866
1930
  try {
1867
1931
  recordingForward(e, { bgAgentId: taskId });
@@ -1886,6 +1950,14 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1886
1950
  }
1887
1951
  catch {
1888
1952
  }
1953
+ const forkBgWarning = await reviewHandback({
1954
+ review: ctx.autoModeReview,
1955
+ toolCallId: ctx.toolCallId,
1956
+ toolName: "Agent(fork handback)",
1957
+ evidence: { result: child.result, steps: stepRecorder.recentSteps(), edits: stepRecorder.editedFiles() },
1958
+ signal: abort.signal,
1959
+ });
1960
+ const forkBgResult = withHandbackWarning(forkBgWarning, child.result);
1889
1961
  const okBg = child.status === "completed";
1890
1962
  const reapedBg = !okBg && abort.signal.aborted;
1891
1963
  const collateralBg = reapedBg && bg.registry.getAccessibleTask(taskId, { ...(bgOwner !== undefined ? { owner: bgOwner } : {}), ...(bgScope !== undefined ? { scope: bgScope } : {}) })?.status === "running";
@@ -1897,7 +1969,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1897
1969
  const settledBg = bg.registry.settleBackgroundAgent(taskId, {
1898
1970
  cycle: 0,
1899
1971
  status: okBg ? "completed" : reapedBg ? "killed" : "failed",
1900
- ...resultSettleFields(child.result),
1972
+ ...resultSettleFields(forkBgResult),
1901
1973
  ...(!okBg ? { error: reapedBg ? (collateralBg ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : child.errorMessage ?? String(child.status) } : {}),
1902
1974
  ...(errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
1903
1975
  ...(errClassFork !== undefined ? { retryable: errClassFork.retryable, errorKind: errClassFork.errorKind } : {}),
@@ -1948,7 +2020,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1948
2020
  ...(failReasonFork !== undefined ? { error: failReasonFork.slice(0, REPORT_FIELD_MAX) } : {}),
1949
2021
  ...(settledBg === "failed" && errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
1950
2022
  ...(child.sessionId ? { sessionId: child.sessionId } : {}),
1951
- ...(child.result ? { result: notifyResultField(child.result) } : {}),
2023
+ ...(forkBgResult ? { result: notifyResultField(forkBgResult) } : {}),
1952
2024
  ...(settledBg === "killed" && child.result ? { partial: true } : {}),
1953
2025
  ...residualFork,
1954
2026
  resumable: resumableFork,
@@ -2051,6 +2123,13 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2051
2123
  const wt = await finishWorktree();
2052
2124
  return { isError: true, content: `Fork failed: the forked run threw (${e instanceof Error ? e.message : String(e)}).${wt ? `\n${wt}` : ""}`, details: { error: "fork run failed" } };
2053
2125
  }
2126
+ const forkHandbackWarning = await reviewHandback({
2127
+ review: ctx.autoModeReview,
2128
+ toolCallId: ctx.toolCallId,
2129
+ toolName: "Agent(fork handback)",
2130
+ evidence: { result: forkChild.result, steps: stepRecorder.recentSteps(), edits: stepRecorder.editedFiles() },
2131
+ signal: ctx.signal,
2132
+ });
2054
2133
  const nsf = forkChild.stats.nested;
2055
2134
  ctx.reportUsage?.({
2056
2135
  tokens: forkChild.stats.tokens + (nsf?.tokens ?? 0),
@@ -2064,6 +2143,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2064
2143
  const wtLine = await finishWorktree();
2065
2144
  const forkErr = classifySubagentError(forkChild);
2066
2145
  const forkLines = [
2146
+ ...(forkHandbackWarning ? [forkHandbackWarning, ""] : []),
2067
2147
  `[Sub-agent report · ${FORK_SUBAGENT_TYPE}${a.description ? ` · ${String(a.description)}` : ""}] (forked session …${forkedId.slice(-8)})`,
2068
2148
  `status: ${forkChild.status}`,
2069
2149
  modelNote ?? "",
@@ -2085,6 +2165,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2085
2165
  details: completedAgentCard(forkChild, {
2086
2166
  subagentType: FORK_SUBAGENT_TYPE,
2087
2167
  error: forkErr,
2168
+ ...(forkHandbackWarning !== undefined ? { handbackWarning: forkHandbackWarning } : {}),
2088
2169
  ...(worktreeKeptPath !== undefined ? { worktreePath: worktreeKeptPath } : {}),
2089
2170
  ...(() => {
2090
2171
  const ts = toolStatsCounter.snapshot();
@@ -2253,7 +2334,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2253
2334
  : ctx.subagentRetain;
2254
2335
  if (sessionScopedBg && bgRetainLedger)
2255
2336
  ensureSessionReapHook(bg.registry);
2256
- const bgRetain = await tryRetainChild(bgRetainLedger, sessionScopedBg && ctx.subagentRetain ? ctx.subagentRetain.ttlMs : undefined, bgChildSessionId, bgDurableProbe, () => bg.registry.releaseDurableTranscriptAnchor(taskId));
2337
+ const bgRetain = await tryRetainChild(bgRetainLedger, sessionScopedBg && ctx.subagentRetain ? ctx.subagentRetain.ttlMs : undefined, bgChildSessionId, bgDurableProbe, () => bg.registry.releaseDurableTranscriptAnchor(taskId), {
2338
+ delegationTaskType: "background_agent",
2339
+ });
2257
2340
  if (bgRetain) {
2258
2341
  bg.registry.markRetainedContinuation(taskId);
2259
2342
  const origRelease = bgRetain.release;
@@ -2263,23 +2346,26 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2263
2346
  };
2264
2347
  }
2265
2348
  const forwardDurableApproval = ctx.durableApprovalForChildren !== undefined &&
2266
- agentName !== undefined &&
2267
2349
  bg.agentStore !== undefined &&
2268
2350
  bg.checkpointStore !== undefined &&
2269
2351
  bg.ensureChildSessionDurable !== undefined;
2270
- const bgSpec = {
2352
+ const bgSpecBase = {
2271
2353
  ...buildChildSpec(abort.signal),
2272
2354
  sessionId: bgChildSessionId,
2273
2355
  ...(forwardDurableApproval ? { durableApproval: { ...ctx.durableApprovalForChildren } } : {}),
2274
2356
  ...(reviveRow !== undefined ? { requireExistingSession: true } : {}),
2275
- ...(outlivesThisRequest ? { onQuestion: undefined } : {}),
2276
2357
  };
2358
+ const bgQuestionStrip = outlivesThisRequest ? stripSpawnTurnQuestionFace(bgSpecBase) : undefined;
2359
+ const bgSpec = bgQuestionStrip !== undefined ? bgQuestionStrip.spec : bgSpecBase;
2277
2360
  let reviveAttachedResolve;
2278
2361
  const reviveAttached = reviveRow !== undefined ? new Promise((r) => (reviveAttachedResolve = r)) : undefined;
2279
2362
  const s2NotifyReady = (inject) => {
2280
2363
  bg.registry.attachAgentNotify(taskId, inject);
2281
2364
  if (parkedResume !== undefined)
2282
2365
  bg.registry.finalizeParkedResume(taskId);
2366
+ const legOrgVerdict = childInternals.ownOrgAdmissionRef?.current;
2367
+ if (legOrgVerdict !== undefined)
2368
+ bg.registry.recordBackgroundAgentOrgAdmission(taskId, legOrgVerdict);
2283
2369
  reviveAttachedResolve?.();
2284
2370
  };
2285
2371
  const bgActivityBeat = createBgActivityBeat(ctx.toolCallId, (toolStarts) => {
@@ -2302,7 +2388,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2302
2388
  const bgInternals = bgSink
2303
2389
  ? {
2304
2390
  ...childInternals,
2391
+ ...(bgQuestionStrip !== undefined ? bgQuestionStrip.internalsFlag : {}),
2305
2392
  onNotifyInjectorReady: s2NotifyReady,
2393
+ delegationTaskType: "background_agent",
2306
2394
  onForwardEvent: (e) => {
2307
2395
  try {
2308
2396
  recordingForward(e, { bgAgentId: taskId });
@@ -2333,7 +2421,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2333
2421
  }
2334
2422
  : {
2335
2423
  ...childInternals,
2424
+ ...(bgQuestionStrip !== undefined ? bgQuestionStrip.internalsFlag : {}),
2336
2425
  onNotifyInjectorReady: s2NotifyReady,
2426
+ delegationTaskType: "background_agent",
2337
2427
  onForwardEvent: (e) => {
2338
2428
  try {
2339
2429
  recordingForward(e, { bgAgentId: taskId });
@@ -2485,6 +2575,16 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2485
2575
  catch {
2486
2576
  }
2487
2577
  const seqAtSettle = bgRetain?.cycleSeq ?? (parkedResume !== undefined ? (reviveRow?.seq ?? 1) + 1 : reviveRow?.seq);
2578
+ const bgHandbackWarning = isDurablePause(child.status)
2579
+ ? undefined
2580
+ : await reviewHandback({
2581
+ review: ctx.autoModeReview,
2582
+ toolCallId: ctx.toolCallId,
2583
+ toolName: "Agent(handback)",
2584
+ evidence: { result: child.result, steps: stepRecorder.recentSteps(), edits: stepRecorder.editedFiles() },
2585
+ signal: abort.signal,
2586
+ });
2587
+ const bgHandbackResult = withHandbackWarning(bgHandbackWarning, child.result);
2488
2588
  if (bgRetain) {
2489
2589
  if (isDurablePause(child.status))
2490
2590
  bgRetainLedger?.abandon(ctx.toolCallId);
@@ -2507,7 +2607,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2507
2607
  return false;
2508
2608
  }
2509
2609
  };
2510
- const wired = agentName !== undefined && bg.agentStore !== undefined && cpStore !== undefined && bg.ensureChildSessionDurable !== undefined;
2610
+ const wired = bg.agentStore !== undefined && cpStore !== undefined && bg.ensureChildSessionDurable !== undefined;
2511
2611
  if (wired) {
2512
2612
  const token = child.checkpointToken;
2513
2613
  if (abort.signal.aborted) {
@@ -2566,7 +2666,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2566
2666
  cycle: 0,
2567
2667
  status: ok ? "completed" : reaped ? "killed" : "failed",
2568
2668
  seq: seqAtSettle ?? 1,
2569
- ...resultSettleFields(child.result),
2669
+ ...resultSettleFields(bgHandbackResult),
2570
2670
  ...(!ok ? { error: reaped ? (collateral ? BG_AGENT_COLLATERAL_REAP_REASON : BG_AGENT_REAP_STOP_ERROR) : unparkedPauseReason ?? child.errorMessage ?? String(child.status) } : {}),
2571
2671
  ...(errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
2572
2672
  ...(errClassBg !== undefined ? { retryable: errClassBg.retryable, errorKind: errClassBg.errorKind } : {}),
@@ -2618,7 +2718,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2618
2718
  ...(failReasonBg !== undefined ? { error: failReasonBg.slice(0, REPORT_FIELD_MAX) } : {}),
2619
2719
  ...(settled === "failed" && errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
2620
2720
  ...(child.sessionId ? { sessionId: child.sessionId } : {}),
2621
- ...(child.result ? { result: notifyResultField(child.result) } : {}),
2721
+ ...(bgHandbackResult ? { result: notifyResultField(bgHandbackResult) } : {}),
2622
2722
  ...(settled === "killed" && child.result ? { partial: true } : {}),
2623
2723
  ...residual,
2624
2724
  resumable: resumableBg,
@@ -2775,6 +2875,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2775
2875
  ...(opts.background ? { registry: opts.background.registry } : {}),
2776
2876
  ...(opts.onObserverError !== undefined ? { onNotifyError: (f) => opts.onObserverError?.(f.error, { site: f.site }) } : {}),
2777
2877
  ...(ctx.onQuestion !== undefined ? { currentOnQuestion: ctx.onQuestion } : {}),
2878
+ ...(ctx.autoModeReview !== undefined ? { currentAutoModeReview: ctx.autoModeReview } : {}),
2778
2879
  });
2779
2880
  notifier.notify(() => ctx.onSubagentSpawn?.(createSteerHandle(stream, ctx.toolCallId, childAgentName, settled, {
2780
2881
  resume,
@@ -2799,26 +2900,18 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2799
2900
  child = await opts.runner.runTask(buildChildSpec(ctx.signal), childInternals);
2800
2901
  }
2801
2902
  await settleObserver(child.status);
2802
- let handbackWarning;
2803
- if (ctx.autoModeReview) {
2804
- const handbackSteps = stepRecorder.recentSteps();
2805
- const handbackEdits = stepRecorder.editedFiles();
2806
- if (child.result || handbackSteps || handbackEdits) {
2807
- const handbackVerdict = await ctx.autoModeReview.decider
2808
- .decide({
2809
- req: {
2810
- toolName: "Agent(handback)",
2811
- args: { result: child.result?.slice(0, 2_000), toolSteps: handbackSteps, editedFiles: handbackEdits },
2812
- toolCallId: ctx.toolCallId,
2813
- },
2814
- askMessage: "Subagent has finished and is handing back control to the main agent. Review the subagent's work and flag if any action may violate security policy.",
2815
- }, ctx.signal)
2816
- .catch(() => ({ kind: "unavailable", cause: "error" }));
2817
- if (handbackVerdict.kind === "block") {
2818
- handbackWarning = `SECURITY WARNING: This subagent performed actions that may violate security policy. Reason: ${inlineUntrusted(handbackVerdict.reason ?? "(no reason given)")}. Review the subagent's actions carefully before acting on its output.`;
2819
- }
2820
- }
2821
- }
2903
+ const handbackWarning = await reviewHandback({
2904
+ review: ctx.autoModeReview,
2905
+ toolCallId: ctx.toolCallId,
2906
+ toolName: "Agent(handback)",
2907
+ evidence: {
2908
+ result: child.result,
2909
+ steps: stepRecorder.recentSteps(),
2910
+ edits: stepRecorder.editedFiles(),
2911
+ ...(child.result || child.status === "completed" ? {} : { partialText: assistantTextTail.trim() }),
2912
+ },
2913
+ signal: ctx.signal,
2914
+ });
2822
2915
  const ns = child.stats.nested;
2823
2916
  ctx.reportUsage?.({
2824
2917
  tokens: child.stats.tokens + (ns?.tokens ?? 0),
@@ -2903,6 +2996,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2903
2996
  details: completedAgentCard(child, {
2904
2997
  ...(def !== undefined ? { subagentType: def.name } : {}),
2905
2998
  error: errClass,
2999
+ ...(handbackWarning !== undefined ? { handbackWarning } : {}),
2906
3000
  ...(modelFallback !== undefined ? { modelFallback } : {}),
2907
3001
  ...(worktreeKeptPath !== undefined ? { worktreePath: worktreeKeptPath } : {}),
2908
3002
  ...(() => {
@@ -5,7 +5,7 @@ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js"
5
5
  import { mintFallbackToolCallId } from "./tool-call-id.js";
6
6
  import { emitBrainTelemetry } from "./status-sink.js";
7
7
  import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, modelSupportsVision, sendableImages } from "./media-degrade.js";
8
- import { ANTHROPIC_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
8
+ import { ANTHROPIC_RESERVED, OUTPUT_CAP_KEYS, applyExtraBody, effectiveOutputCap, stripAuthHeaders } from "./request-params.js";
9
9
  import { isThinkingLevel, reasoningBudgetShare, resolveEffort } from "./reasoning.js";
10
10
  import { runStreamingBrain } from "./stream-engine.js";
11
11
  const MIN_THINKING_TOKENS = 1024;
@@ -233,7 +233,7 @@ export function createAnthropicBrain(config = {}) {
233
233
  config,
234
234
  httpLabel: "anthropic",
235
235
  stallTimeouts: options?.stallTimeouts,
236
- buildRequest: () => {
236
+ buildRequest: (overrides) => {
237
237
  const apiKey = options?.apiKey ?? config.apiKey;
238
238
  const root = (model.baseUrl || config.baseUrl || "https://api.anthropic.com").replace(/\/+$/, "");
239
239
  const nonEmptyBlocks = context.systemBlocks?.filter((b) => b.text.length > 0);
@@ -248,7 +248,7 @@ export function createAnthropicBrain(config = {}) {
248
248
  : undefined;
249
249
  const body = {
250
250
  model: model.id,
251
- max_tokens: options?.maxTokens ?? model.maxTokens ?? config.defaultMaxTokens ?? 4096,
251
+ max_tokens: overrides?.maxOutputTokens ?? options?.maxTokens ?? model.maxTokens ?? config.defaultMaxTokens ?? 4096,
252
252
  messages: toAnthropicMessages(context, model),
253
253
  stream: true,
254
254
  };
@@ -266,7 +266,7 @@ export function createAnthropicBrain(config = {}) {
266
266
  body.thinking = { type: "adaptive" };
267
267
  }
268
268
  else {
269
- const hardCap = options?.maxTokens !== undefined;
269
+ const hardCap = overrides?.maxOutputTokens !== undefined || options?.maxTokens !== undefined;
270
270
  if (hardCap && body.max_tokens < MIN_THINKING_TOKENS * 2) {
271
271
  body.thinking = undefined;
272
272
  delete body.thinking;
@@ -322,11 +322,28 @@ export function createAnthropicBrain(config = {}) {
322
322
  headers["anthropic-version"] = config.version ?? "2023-06-01";
323
323
  if (apiKey)
324
324
  headers["x-api-key"] = apiKey;
325
- thinkingRequested = body.thinking !== undefined;
326
- sentMaxTokens = body.max_tokens;
327
- sentMaxTokensLane =
328
- options?.maxTokens !== undefined ? "options" : model.maxTokens !== undefined ? "model" : "config-default";
329
- return { url: `${root}/v1/messages`, headers, body: JSON.stringify(applyExtraBody(body, model.extraBody, ANTHROPIC_RESERVED)) };
325
+ const builtThinkingRequested = body.thinking !== undefined;
326
+ const builtMaxTokens = body.max_tokens;
327
+ const builtMaxTokensLane = overrides?.maxOutputTokens !== undefined
328
+ ? "engine"
329
+ : options?.maxTokens !== undefined
330
+ ? "options"
331
+ : model.maxTokens !== undefined
332
+ ? "model"
333
+ : "config-default";
334
+ const wire = applyExtraBody(body, model.extraBody, ANTHROPIC_RESERVED);
335
+ return {
336
+ url: `${root}/v1/messages`,
337
+ headers,
338
+ body: JSON.stringify(wire),
339
+ outputCapTokens: effectiveOutputCap(wire, OUTPUT_CAP_KEYS.anthropic),
340
+ capDerivedPaths: [...OUTPUT_CAP_KEYS.anthropic, "thinking.budget_tokens"],
341
+ onCommitted: () => {
342
+ thinkingRequested = builtThinkingRequested;
343
+ sentMaxTokens = builtMaxTokens;
344
+ sentMaxTokensLane = builtMaxTokensLane;
345
+ },
346
+ };
330
347
  },
331
348
  makeParser: (ctrl) => {
332
349
  const { out, partial } = ctrl;
@@ -588,7 +605,13 @@ export function createAnthropicBrain(config = {}) {
588
605
  const safetyCut = stopReason === "refusal";
589
606
  const contextWindowExceeded = stopReason === "model_context_window_exceeded";
590
607
  const emptyNoFinish = noUsableContent && stopReason == null && !degenerate;
591
- const maxTokensNote = `effective max_tokens=${sentMaxTokens ?? "?"} (from ${sentMaxTokensLane === "options" ? "options.maxTokens" : sentMaxTokensLane === "model" ? "model.maxTokens" : "config default"})`;
608
+ const maxTokensNote = `effective max_tokens=${sentMaxTokens ?? "?"} (from ${sentMaxTokensLane === "options"
609
+ ? "options.maxTokens"
610
+ : sentMaxTokensLane === "engine"
611
+ ? "the engine's context-overflow recovery"
612
+ : sentMaxTokensLane === "model"
613
+ ? "model.maxTokens"
614
+ : "config default"})`;
592
615
  const errorMessage = degenerate
593
616
  ? DEGENERATE_MESSAGE
594
617
  : safetyCut
@@ -0,0 +1,20 @@
1
+ export declare const FLOOR_OUTPUT_TOKENS = 3000;
2
+ export interface ContextOverflowReport {
3
+ inputTokens: number;
4
+ maxTokens: number;
5
+ contextLimit: number;
6
+ }
7
+ export declare function parseContextOverflow(status: number, body: string): ContextOverflowReport | undefined;
8
+ export type OutputCapAdjustment = {
9
+ outcome: "adjust";
10
+ maxOutputTokens: number;
11
+ current: number;
12
+ } | {
13
+ outcome: "below_floor";
14
+ available: number;
15
+ } | {
16
+ outcome: "no_progress";
17
+ available: number;
18
+ current: number;
19
+ };
20
+ export declare function planOutputCapAdjustment(report: ContextOverflowReport, otherCapsInEffect?: readonly (number | undefined)[]): OutputCapAdjustment;
@@ -0,0 +1,58 @@
1
+ export const FLOOR_OUTPUT_TOKENS = 3000;
2
+ const CONTEXT_HEADROOM_TOKENS = 1000;
3
+ const CONTEXT_OVERFLOW_RE = /^input length and `max_tokens` exceed context limit: (\d+) \+ (\d+) > (\d+)(?!\w|[.,]\d|\s\d|[+\-*/])/;
4
+ const CONTEXT_OVERFLOW_PREFIX = "input length and `max_tokens` exceed context limit:";
5
+ function providerDiagnostic(body) {
6
+ const trimmed = body.trim();
7
+ if (!trimmed.startsWith("{"))
8
+ return undefined;
9
+ let parsed;
10
+ try {
11
+ parsed = JSON.parse(trimmed);
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
17
+ return undefined;
18
+ const err = parsed.error;
19
+ if (typeof err !== "object" || err === null || Array.isArray(err))
20
+ return undefined;
21
+ const message = err.message;
22
+ return typeof message === "string" ? message : undefined;
23
+ }
24
+ function reportFrom(diagnostic) {
25
+ const message = diagnostic.trimStart();
26
+ const m = CONTEXT_OVERFLOW_RE.exec(message);
27
+ if (m === null)
28
+ return undefined;
29
+ if (message.slice(m[0].length).includes(CONTEXT_OVERFLOW_PREFIX))
30
+ return undefined;
31
+ const [, rawInput, rawMax, rawLimit] = m;
32
+ if (rawInput === undefined || rawMax === undefined || rawLimit === undefined)
33
+ return undefined;
34
+ const inputTokens = Number.parseInt(rawInput, 10);
35
+ const maxTokens = Number.parseInt(rawMax, 10);
36
+ const contextLimit = Number.parseInt(rawLimit, 10);
37
+ if (!Number.isSafeInteger(inputTokens) || !Number.isSafeInteger(maxTokens) || !Number.isSafeInteger(contextLimit)) {
38
+ return undefined;
39
+ }
40
+ if (inputTokens <= contextLimit - maxTokens)
41
+ return undefined;
42
+ return { inputTokens, maxTokens, contextLimit };
43
+ }
44
+ export function parseContextOverflow(status, body) {
45
+ if (status !== 400)
46
+ return undefined;
47
+ const diagnostic = providerDiagnostic(body);
48
+ return diagnostic === undefined ? undefined : reportFrom(diagnostic);
49
+ }
50
+ export function planOutputCapAdjustment(report, otherCapsInEffect = []) {
51
+ const available = Math.max(0, report.contextLimit - report.inputTokens - CONTEXT_HEADROOM_TOKENS);
52
+ if (available < FLOOR_OUTPUT_TOKENS)
53
+ return { outcome: "below_floor", available };
54
+ const current = Math.min(report.maxTokens, ...otherCapsInEffect.filter((c) => c !== undefined));
55
+ if (available >= current)
56
+ return { outcome: "no_progress", available, current };
57
+ return { outcome: "adjust", maxOutputTokens: available, current };
58
+ }