@sema-agent/core 5.65.0 → 7.0.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 (165) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/agents/subagent.d.ts +2 -2
  3. package/dist/agents/subagent.js +11 -0
  4. package/dist/agents/verify.d.ts +1 -1
  5. package/dist/brain/anthropic.js +1 -1
  6. package/dist/brain/errors.d.ts +29 -0
  7. package/dist/brain/errors.js +20 -0
  8. package/dist/brain/open-responses.js +2 -2
  9. package/dist/brain/route-adjudicator.d.ts +8 -1
  10. package/dist/brain/route-adjudicator.js +1 -0
  11. package/dist/brain/status-sink.js +12 -1
  12. package/dist/brain/stream-engine.js +17 -6
  13. package/dist/core/auto-compaction.d.ts +26 -0
  14. package/dist/core/auto-compaction.js +7 -2
  15. package/dist/core/auto-mode-arming.d.ts +138 -0
  16. package/dist/core/auto-mode-arming.js +181 -0
  17. package/dist/core/auto-mode-defaults.d.ts +13 -0
  18. package/dist/core/auto-mode-defaults.js +5 -0
  19. package/dist/core/auto-mode-prompt.d.ts +14 -3
  20. package/dist/core/auto-mode-prompt.js +10 -7
  21. package/dist/core/auto-mode-rebuild.d.ts +75 -0
  22. package/dist/core/auto-mode-rebuild.js +41 -0
  23. package/dist/core/auto-mode.d.ts +15 -0
  24. package/dist/core/auto-mode.js +4 -2
  25. package/dist/core/checkpoint-store.d.ts +18 -0
  26. package/dist/core/context-edit.d.ts +47 -5
  27. package/dist/core/context-guard.d.ts +1 -1
  28. package/dist/core/file-history-retention.d.ts +106 -0
  29. package/dist/core/file-history-retention.js +36 -0
  30. package/dist/core/file-history-store.d.ts +768 -0
  31. package/dist/core/file-history-store.js +880 -0
  32. package/dist/core/governance-codes.d.ts +13 -3
  33. package/dist/core/governance-codes.js +15 -0
  34. package/dist/core/hooks.d.ts +39 -7
  35. package/dist/core/hooks.js +38 -21
  36. package/dist/core/lsp.d.ts +2 -2
  37. package/dist/core/mcp.d.ts +29 -7
  38. package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
  39. package/dist/core/memory-engine/consolidation-driver.js +71 -4
  40. package/dist/core/memory-engine/consolidation.d.ts +25 -2
  41. package/dist/core/memory-engine/consolidation.js +4 -1
  42. package/dist/core/memory-engine/distiller.d.ts +84 -1
  43. package/dist/core/memory-engine/distiller.js +68 -0
  44. package/dist/core/memory-engine/dual-root.js +3 -0
  45. package/dist/core/memory-engine/engine.d.ts +328 -15
  46. package/dist/core/memory-engine/engine.js +355 -29
  47. package/dist/core/memory-engine/file-backend.d.ts +30 -0
  48. package/dist/core/memory-engine/file-backend.js +14 -13
  49. package/dist/core/memory-engine/frontmatter.d.ts +22 -1
  50. package/dist/core/memory-engine/frontmatter.js +3 -0
  51. package/dist/core/memory-engine/header-hints.d.ts +5 -0
  52. package/dist/core/memory-engine/index.d.ts +5 -4
  53. package/dist/core/memory-engine/index.js +5 -4
  54. package/dist/core/memory-engine/layout.d.ts +88 -2
  55. package/dist/core/memory-engine/layout.js +112 -3
  56. package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
  57. package/dist/core/memory-engine/provenance-wording.js +3 -0
  58. package/dist/core/memory-engine/tools.d.ts +89 -8
  59. package/dist/core/memory-engine/tools.js +263 -22
  60. package/dist/core/memory-engine/types.d.ts +64 -1
  61. package/dist/core/memory-recall.d.ts +6 -0
  62. package/dist/core/memory.d.ts +27 -1
  63. package/dist/core/memory.js +16 -2
  64. package/dist/core/permission-rule-consent.d.ts +62 -2
  65. package/dist/core/permission-rule-consent.js +105 -14
  66. package/dist/core/permission-rule-model.d.ts +118 -16
  67. package/dist/core/permission-rule-model.js +57 -9
  68. package/dist/core/permission-rule-session.d.ts +124 -0
  69. package/dist/core/permission-rule-session.js +121 -0
  70. package/dist/core/permission-rule-store.d.ts +65 -2
  71. package/dist/core/permission-rule-store.js +75 -16
  72. package/dist/core/permission-rule-sync.d.ts +9 -0
  73. package/dist/core/permission-rule-sync.js +52 -19
  74. package/dist/core/retention-policy.d.ts +9 -0
  75. package/dist/core/retention-policy.js +5 -2
  76. package/dist/core/retention.d.ts +13 -2
  77. package/dist/core/runner/assemble-result.d.ts +19 -1
  78. package/dist/core/runner/assemble-result.js +17 -2
  79. package/dist/core/runner/compaction-call-options.d.ts +93 -0
  80. package/dist/core/runner/compaction-call-options.js +3 -0
  81. package/dist/core/runner/memory-capture-optout.d.ts +80 -0
  82. package/dist/core/runner/memory-capture-optout.js +53 -0
  83. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  84. package/dist/core/runner/prepare-config-doors.js +16 -0
  85. package/dist/core/runner/prepare-hands-readface.d.ts +110 -5
  86. package/dist/core/runner/prepare-hands-readface.js +99 -7
  87. package/dist/core/runner/prepare-memory.d.ts +88 -0
  88. package/dist/core/runner/prepare-memory.js +305 -24
  89. package/dist/core/runner/prepare-task.d.ts +141 -1
  90. package/dist/core/runner/prepare-task.js +478 -81
  91. package/dist/core/runner/runtask.d.ts +9 -20
  92. package/dist/core/runner/runtask.js +133 -96
  93. package/dist/core/runner/session-file-state-replay.d.ts +18 -10
  94. package/dist/core/runner/session-file-state-replay.js +52 -1
  95. package/dist/core/runner/tool-disclosure.js +2 -1
  96. package/dist/core/runner/turn-attachments.d.ts +22 -12
  97. package/dist/core/session-store.d.ts +1 -1
  98. package/dist/core/session-store.js +6 -1
  99. package/dist/core/session.d.ts +34 -1
  100. package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
  101. package/dist/core/store-contracts/file-history-store-contract.js +720 -0
  102. package/dist/core/store-contracts/permission-rule-sync-contract.js +15 -1
  103. package/dist/core/task-registry-shared.js +11 -1
  104. package/dist/core/tool-errors.js +1 -0
  105. package/dist/core/tool-policy.d.ts +172 -1
  106. package/dist/core/tool-policy.js +32 -1
  107. package/dist/core/tool-result-store.js +2 -1
  108. package/dist/core/trace.d.ts +31 -2
  109. package/dist/core/types.d.ts +795 -89
  110. package/dist/core/types.js +4 -3
  111. package/dist/core/untrusted-text.d.ts +1 -1
  112. package/dist/core/untrusted-text.js +8 -0
  113. package/dist/core/workflow-run-store-contract.js +6 -2
  114. package/dist/core/workflow-run-store.d.ts +4 -1
  115. package/dist/engine/compaction/compaction.d.ts +88 -10
  116. package/dist/engine/compaction/compaction.js +109 -30
  117. package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
  118. package/dist/engine/execution-env/node-execution-env.js +28 -0
  119. package/dist/engine/harness/agent-harness.d.ts +52 -1
  120. package/dist/engine/harness/agent-harness.js +36 -1
  121. package/dist/engine/harness/types.d.ts +26 -1
  122. package/dist/engine/llm/types.d.ts +50 -4
  123. package/dist/engine/loop/agent-loop.d.ts +5 -1
  124. package/dist/engine/loop/agent-loop.js +25 -0
  125. package/dist/engine/loop/types.d.ts +19 -0
  126. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  127. package/dist/engine/session/session.js +1 -1
  128. package/dist/index.d.ts +20 -9
  129. package/dist/index.js +16 -7
  130. package/dist/orchestration/run-workflow-tool.d.ts +20 -2
  131. package/dist/orchestration/run-workflow-tool.js +22 -3
  132. package/dist/orchestration/workflow-governance.d.ts +59 -1
  133. package/dist/orchestration/workflow-governance.js +61 -8
  134. package/dist/orchestration/workflow-meta.d.ts +4 -2
  135. package/dist/orchestration/workflow-primitives.js +56 -13
  136. package/dist/orchestration/workflow-types.d.ts +78 -2
  137. package/dist/orchestration/workflow.d.ts +20 -0
  138. package/dist/orchestration/workflow.js +163 -14
  139. package/dist/prompt-assembly/event-registry.js +1 -1
  140. package/dist/prompts/default.d.ts +7 -7
  141. package/dist/stores/file/file-history-store.d.ts +368 -0
  142. package/dist/stores/file/file-history-store.js +1248 -0
  143. package/dist/stores/file/index.d.ts +22 -13
  144. package/dist/stores/file/index.js +4 -4
  145. package/dist/stores/file/permission-rule-store.d.ts +11 -0
  146. package/dist/stores/file/permission-rule-store.js +23 -9
  147. package/dist/stores/file/strategy-store.d.ts +3 -3
  148. package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
  149. package/dist/tools/fs/bash-readonly-classifier.js +106 -4
  150. package/dist/tools/fs/fs-bash.js +9 -5
  151. package/dist/tools/fs/fs-shared.d.ts +52 -1
  152. package/dist/tools/fs/fs-shared.js +14 -0
  153. package/dist/tools/fs/fs-write.d.ts +5 -5
  154. package/dist/tools/fs/fs-write.js +71 -14
  155. package/dist/tools/fs/index.d.ts +6 -1
  156. package/dist/tools/fs/index.js +1 -1
  157. package/dist/tools/web.js +2 -1
  158. package/package.json +5 -1
  159. package/test/export-surface.snapshot.json +169 -23
  160. package/dist/core/file-snapshot-store.d.ts +0 -165
  161. package/dist/core/file-snapshot-store.js +0 -259
  162. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
  163. package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
  164. package/dist/stores/file/file-snapshot-store.d.ts +0 -58
  165. package/dist/stores/file/file-snapshot-store.js +0 -353
@@ -17,6 +17,7 @@ import { delimitUntrusted } from "../core/untrusted-text.js";
17
17
  import { OUTPUT_TOOL_NAME } from "../core/runner/synthetic-tools.js";
18
18
  import { compileOutputSchema } from "../core/runner/strict-output-schema.js";
19
19
  import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
20
+ import { WorkflowScriptError } from "./workflow-meta.js";
20
21
  export * from "./workflow-types.js";
21
22
  const MAX_TRANSCRIPT_CHARS = 4000;
22
23
  const WORKFLOW_RESULT_MAX = 4000;
@@ -201,9 +202,94 @@ function currentWorkflowDepth() {
201
202
  return workflowDepthStore.getStore()?.depth ?? 0;
202
203
  }
203
204
  export const MAX_WORKFLOW_ITEMS = 4096;
205
+ function describeFanOutValue(v) {
206
+ if (v === null)
207
+ return "null";
208
+ if (Array.isArray(v))
209
+ return "array";
210
+ if (v instanceof Promise)
211
+ return "a Promise (an already-CALLED agent(...) — wrap it: () => agent(...))";
212
+ return typeof v;
213
+ }
214
+ function assertFanOutCallables(lane, member, list) {
215
+ for (let i = 0; i < list.length; i++) {
216
+ if (typeof list[i] !== "function") {
217
+ throw new WorkflowScriptError(`${lane}() ${member} at index ${i} is not a function (got ${describeFanOutValue(list[i])}) — each ${member} must be a ` +
218
+ `thunk: () => agent(...). Nothing was run: a non-function member is never called, so its slot would fold to null ` +
219
+ `with no agent, no journal row and no error — the whole call is refused instead.`);
220
+ }
221
+ }
222
+ }
223
+ function assertFanOutArray(lane, what, v) {
224
+ if (!Array.isArray(v)) {
225
+ throw new WorkflowScriptError(`${lane}(): ${what} must be an ARRAY (got ${describeFanOutValue(v)}). Nothing was run.`);
226
+ }
227
+ }
228
+ function snapshotFanOutList(lane, what, v) {
229
+ assertFanOutArray(lane, what, v);
230
+ const declared = v.length;
231
+ if (typeof declared !== "number" || !Number.isSafeInteger(declared) || declared < 0) {
232
+ throw new WorkflowScriptError(`${lane}(): ${what} does not report a valid array length. Nothing was run.`);
233
+ }
234
+ if (declared > MAX_WORKFLOW_ITEMS)
235
+ throw new Error(`${lane}: too many items (${declared} > ${MAX_WORKFLOW_ITEMS})`);
236
+ const snapshot = [];
237
+ for (let i = 0; i < declared; i++)
238
+ snapshot.push(v[i]);
239
+ return snapshot;
240
+ }
241
+ function assertFanOutOptionsShape(v, position) {
242
+ const looksLikeBag = typeof v === "object" &&
243
+ v !== null &&
244
+ !Array.isArray(v) &&
245
+ Object.keys(v).every((k) => k === "errors") &&
246
+ (v.errors === undefined || Array.isArray(v.errors));
247
+ if (!looksLikeBag) {
248
+ throw new WorkflowScriptError(`pipeline() argument at stage position ${position} is not a function (got ${describeFanOutValue(v)}) and is not the ` +
249
+ `trailing fan-out options bag \`{ errors: [] }\` either — each stage must be a function (prev, item, index) => …. ` +
250
+ `Nothing was run.`);
251
+ }
252
+ }
204
253
  export const WORKFLOW_AGENT_STALL_MS = 180_000;
205
254
  export const WORKFLOW_AGENT_MAX_RETRIES = 5;
206
255
  export const WORKFLOW_AGENT_THROTTLE_BACKOFF_MS = 45_000;
256
+ function readLegPendingApproval(result, announce) {
257
+ if (result === undefined)
258
+ return undefined;
259
+ const raw = result.pendingApproval;
260
+ if (raw === undefined)
261
+ return undefined;
262
+ const refuse = (why) => {
263
+ announce(`[approval] a leg result carried a malformed pendingApproval report (${why}) — ignoring it: this run ` +
264
+ `will judge the leg by its progress watchdog alone, exactly as a deployment that reports nothing does. ` +
265
+ `A leg genuinely waiting on a person will be retried and may exhaust its stall budget.`);
266
+ return undefined;
267
+ };
268
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
269
+ return refuse("not an object");
270
+ const { askIds, oldestCreatedAtMs } = raw;
271
+ if (!Array.isArray(askIds))
272
+ return refuse("askIds is not an array");
273
+ const ids = [...askIds];
274
+ if (ids.length === 0)
275
+ return refuse('askIds is empty — "nothing outstanding" is spelled by OMITTING the key');
276
+ if (!ids.every((id) => typeof id === "string" && id !== ""))
277
+ return refuse("askIds holds a non-string or empty id");
278
+ if (typeof oldestCreatedAtMs !== "number" || !Number.isFinite(oldestCreatedAtMs))
279
+ return refuse("oldestCreatedAtMs is not a finite number");
280
+ return { askIds: ids, oldestCreatedAtMs };
281
+ }
282
+ function readLegApprovalWaitedMs(result, announce) {
283
+ const raw = result.approvalWaitedMs;
284
+ if (raw === undefined)
285
+ return undefined;
286
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) {
287
+ announce(`[approval] a leg result carried an unreadable approvalWaitedMs (${typeof raw === "number" ? String(raw) : `not a number: ${typeof raw}`}) — ` +
288
+ `dropping it: this run's timeout disclosure will say nothing about approval waiting rather than publish a duration it cannot vouch for.`);
289
+ return undefined;
290
+ }
291
+ return raw;
292
+ }
207
293
  export const WORKFLOW_RESUME_CLAIM_FINALIZE_TIMEOUT_MS = 10_000;
208
294
  const REAL_WORKFLOW_TIMERS = {
209
295
  setTimeout(fn, ms) {
@@ -527,6 +613,18 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
527
613
  ...(opts.inheritedGate !== undefined ? { inheritedGate: opts.inheritedGate } : {}),
528
614
  ...((opts.rootSessionId ?? opts.originatingSessionId) !== undefined ? { rootSessionId: opts.rootSessionId ?? opts.originatingSessionId } : {}),
529
615
  ...(opts.placementRoot !== undefined ? { placementRoot: opts.placementRoot } : {}),
616
+ ...(opts.originatingSessionId !== undefined ? { parentSessionId: opts.originatingSessionId } : {}),
617
+ };
618
+ const captureFloorSeatsNow = () => {
619
+ const s = opts.parentMemoryCaptureState?.();
620
+ if (s === undefined)
621
+ return {};
622
+ return {
623
+ ...(s.optedOut === true ? { memoryCaptureFloor: true } : {}),
624
+ ...(s.optedOut !== true && s.indeterminate === true ? { memoryCaptureFloorIndeterminate: true } : {}),
625
+ ...(s.controlDir !== undefined ? { memoryCaptureQueryDir: s.controlDir } : {}),
626
+ ...(s.ancestors !== undefined ? { memoryCaptureAncestors: s.ancestors } : {}),
627
+ };
530
628
  };
531
629
  const bceSink = opts.onBackgroundChildEvent;
532
630
  const bceEmit = (e) => {
@@ -788,6 +886,37 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
788
886
  return;
789
887
  emitLogLine(message);
790
888
  };
889
+ const setLegRunId = (rec, from) => {
890
+ if (from.runId !== undefined && from.runId !== "")
891
+ rec.taskRunId = from.runId;
892
+ else
893
+ delete rec.taskRunId;
894
+ };
895
+ const foldLegApprovalReport = (rec, result) => {
896
+ const report = readLegPendingApproval(result, emitRunLog);
897
+ if (report !== undefined)
898
+ rec.pendingApproval = report;
899
+ else
900
+ delete rec.pendingApproval;
901
+ const waited = readLegApprovalWaitedMs(result, emitRunLog);
902
+ if (waited !== undefined)
903
+ rec.approvalWaitedMs = waited;
904
+ return report;
905
+ };
906
+ let unbackstoppedWaiverAnnounced = false;
907
+ const approvalWaiverBackstopArmed = () => {
908
+ if (totalTimeoutMs !== undefined) {
909
+ return !finalized && timeoutController?.signal.aborted !== true;
910
+ }
911
+ if (!unbackstoppedWaiverAnnounced) {
912
+ unbackstoppedWaiverAnnounced = true;
913
+ emitRunLog(`[approval] an agent reported outstanding human approvals, but this run arms no totalTimeoutMs — so the ` +
914
+ `stall-budget waiver is WITHHELD and approval-blocked attempts are charged exactly as before. Waiving them ` +
915
+ `would leave the retry loop with no ceiling at all (the token budget and maxAgents gate new ctx.agent ` +
916
+ `calls, not one call's attempts). Set runWorkflow's totalTimeoutMs to let this run wait for people.`);
917
+ }
918
+ return false;
919
+ };
791
920
  const stampBudgetOvershoot = (unsettledTokens) => {
792
921
  if (budgetTotal === null || run.budgetOvershoot !== undefined)
793
922
  return;
@@ -809,6 +938,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
809
938
  let agentsCompleted = 0;
810
939
  let agentsFailed = 0;
811
940
  let agentsInFlight = 0;
941
+ let approvalWaitedMs;
812
942
  for (const a of run.agents) {
813
943
  if (a.status === "completed")
814
944
  agentsCompleted++;
@@ -816,8 +946,10 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
816
946
  agentsFailed++;
817
947
  else
818
948
  agentsInFlight++;
949
+ if (a.approvalWaitedMs !== undefined && (approvalWaitedMs === undefined || a.approvalWaitedMs > approvalWaitedMs))
950
+ approvalWaitedMs = a.approvalWaitedMs;
819
951
  }
820
- run.timeoutInterruption = { timeoutMs: totalTimeoutMs, agentsCompleted, agentsFailed, agentsInFlight };
952
+ run.timeoutInterruption = { timeoutMs: totalTimeoutMs, agentsCompleted, agentsFailed, agentsInFlight, ...(approvalWaitedMs !== undefined ? { approvalWaitedMs } : {}) };
821
953
  };
822
954
  let divergenceNoted = false;
823
955
  const noteDivergence = (ordinal, reason) => {
@@ -989,6 +1121,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
989
1121
  rec.taskStatus = result.status;
990
1122
  if (result.sessionId)
991
1123
  rec.sessionId = result.sessionId;
1124
+ setLegRunId(rec, result);
992
1125
  rec.endedAt = now();
993
1126
  rec.stats = { tokens: s.tokens ?? 0, turns: s.turns ?? 0, costMicroUsd: s.costMicroUsd };
994
1127
  const output = settleAgentError(rec, result, rec.label, boundedRedactedSummary(result.structuredOutput ?? result.result, MAX_TRANSCRIPT_CHARS));
@@ -996,6 +1129,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
996
1129
  rec.toolCalls = result.stats.toolCalls;
997
1130
  if (activityTail.length > 0)
998
1131
  rec.activity = activityTail.slice();
1132
+ foldLegApprovalReport(rec, result);
999
1133
  emit({ type: "agent_end", runId, label: rec.label, phase: rec.phase, ...(rec.groupId !== undefined ? { groupId: rec.groupId } : {}), status: rec.status, output, ...(rec.errorCode !== undefined ? { errorCode: rec.errorCode } : {}), ...(result.stats.toolCalls !== undefined ? { toolCalls: result.stats.toolCalls } : {}), ...(result.model !== undefined ? { modelResolved: result.model } : {}), ts: rec.endedAt });
1000
1134
  void persist("update");
1001
1135
  bceTerminal(rec.callKey, rec.status === "completed" ? "completed" : "failed", output, result.sessionId || undefined, rec.stats);
@@ -1038,6 +1172,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1038
1172
  status: r.status === "completed" ? "completed" : "failed",
1039
1173
  taskStatus: r.status,
1040
1174
  ...(r.sessionId ? { sessionId: r.sessionId } : {}),
1175
+ ...(r.runId ? { taskRunId: r.runId } : {}),
1041
1176
  queuedAt: at,
1042
1177
  startedAt: at,
1043
1178
  endedAt: at,
@@ -1112,10 +1247,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1112
1247
  opts.onForwardEvent(e.type === "task_progress" ? { ...e, workflowRunId: runId, workflowAgentLabel: label } : e);
1113
1248
  }
1114
1249
  : undefined;
1115
- const baseInternals = { ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}), ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd } : {}), ...spawnAttribution, ...(enrichedForward !== undefined ? { onForwardEvent: enrichedForward } : {}), delegationTaskType: "workflow", agentName: label, onWorkspaceResolved: createWorkspaceObserver(rec) };
1250
+ const baseInternals = { ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}), ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd } : {}), ...spawnAttribution, ...captureFloorSeatsNow(), ...(enrichedForward !== undefined ? { onForwardEvent: enrichedForward } : {}), delegationTaskType: "workflow", agentName: label, onWorkspaceResolved: createWorkspaceObserver(rec) };
1116
1251
  let attempts = 0;
1117
1252
  let throttleRetried = false;
1118
1253
  let lastAttemptReason;
1254
+ let approvalWaivedAttempts = 0;
1119
1255
  for (;;) {
1120
1256
  attempts += 1;
1121
1257
  const attemptStart = now();
@@ -1234,11 +1370,19 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1234
1370
  if (stallFired && attemptResult?.status !== "completed") {
1235
1371
  if (attemptResult !== undefined && !finalized)
1236
1372
  accumulateStats(attemptResult, true);
1237
- if (attempts <= agentMaxRetries && !throttleRetried) {
1238
- lastAttemptReason = "stalled";
1373
+ if (attemptResult !== undefined && !finalized)
1374
+ setLegRunId(rec, attemptResult);
1375
+ const reported = attemptResult !== undefined && !finalized ? foldLegApprovalReport(rec, attemptResult) : undefined;
1376
+ const pendingApproval = reported !== undefined && approvalWaiverBackstopArmed() ? reported : undefined;
1377
+ if (pendingApproval !== undefined)
1378
+ approvalWaivedAttempts += 1;
1379
+ if (attempts - approvalWaivedAttempts <= agentMaxRetries && !throttleRetried) {
1380
+ lastAttemptReason = pendingApproval !== undefined ? "awaiting_approval" : "stalled";
1239
1381
  if (!finalized)
1240
1382
  void persist("update");
1241
- emitRunLog(`[stall] agent "${label}" stalled (no progress for ${Math.round(stallMs / 1000)}s) — retrying (${attempts}/${agentMaxRetries})`);
1383
+ emitRunLog(pendingApproval !== undefined
1384
+ ? `[approval] agent "${label}" made no progress for ${Math.round(stallMs / 1000)}s while ${pendingApproval.askIds.length} approval(s) raised by it were still outstanding — retrying WITHOUT charging the stall budget (${attempts - approvalWaivedAttempts}/${agentMaxRetries} charged)`
1385
+ : `[stall] agent "${label}" stalled (no progress for ${Math.round(stallMs / 1000)}s) — retrying (${attempts - approvalWaivedAttempts}/${agentMaxRetries})`);
1242
1386
  continue;
1243
1387
  }
1244
1388
  throw new WorkflowAgentStalledError(attempts, stallMs, attemptResult);
@@ -1257,6 +1401,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1257
1401
  lastAttemptReason = "throttled";
1258
1402
  emitRunLog(`[${label}] throttled response (${(result.result ?? "").trim().length} chars in ${Math.round(durationMs / 1000)}s) — sleeping ${Math.round(throttleBackoffMs / 1000)}s before retry`);
1259
1403
  if (!finalized) {
1404
+ setLegRunId(rec, result);
1260
1405
  accumulateStats(result, true);
1261
1406
  void persist("update");
1262
1407
  }
@@ -1291,10 +1436,13 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1291
1436
  rec.status = "failed";
1292
1437
  rec.endedAt = now();
1293
1438
  const salvaged = err instanceof WorkflowAgentStalledError ? err.lastResult : undefined;
1439
+ if (salvaged?.runId === undefined)
1440
+ delete rec.taskRunId;
1294
1441
  if (salvaged !== undefined) {
1295
1442
  rec.taskStatus = salvaged.status;
1296
1443
  if (salvaged.sessionId)
1297
1444
  rec.sessionId = salvaged.sessionId;
1445
+ setLegRunId(rec, salvaged);
1298
1446
  if (salvaged.errorCode !== undefined)
1299
1447
  rec.errorCode = salvaged.errorCode;
1300
1448
  if (salvaged.errorMessage !== undefined && salvaged.errorMessage !== "")
@@ -1418,6 +1566,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1418
1566
  ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}),
1419
1567
  ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd } : {}),
1420
1568
  ...spawnAttribution,
1569
+ ...captureFloorSeatsNow(),
1421
1570
  ...(enrichedForwardS !== undefined ? { onForwardEvent: enrichedForwardS } : {}),
1422
1571
  delegationTaskType: "workflow",
1423
1572
  agentName: label,
@@ -1519,31 +1668,31 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1519
1668
  return { runId, label, callKey, steer, result: () => completion };
1520
1669
  },
1521
1670
  async parallel(thunks, fanOutOpts) {
1522
- if (thunks.length > MAX_WORKFLOW_ITEMS) {
1523
- throw new Error(`parallel: too many items (${thunks.length} > ${MAX_WORKFLOW_ITEMS})`);
1524
- }
1671
+ const list = snapshotFanOutList("parallel", "the thunk list", thunks);
1672
+ assertFanOutCallables("parallel", "item", list);
1525
1673
  const errorsOut = fanOutOpts?.errors;
1526
- const settled = await Promise.all(thunks.map((t, i) => Promise.resolve()
1674
+ const settled = await Promise.all(list.map((t, i) => Promise.resolve()
1527
1675
  .then(t)
1528
1676
  .then((v) => ({ ok: true, v }), (e) => ({ ok: false, e, i }))));
1529
1677
  return foldFanOutSettled(settled, "parallel", errorsOut, emitRunLog);
1530
1678
  },
1531
1679
  async pipeline(items, ...stagesAndOpts) {
1532
- if (items.length > MAX_WORKFLOW_ITEMS) {
1533
- throw new Error(`pipeline: too many items (${items.length} > ${MAX_WORKFLOW_ITEMS})`);
1534
- }
1680
+ const itemList = snapshotFanOutList("pipeline", "the item list", items);
1535
1681
  for (let i = 0; i < stagesAndOpts.length - 1; i++) {
1536
1682
  if (typeof stagesAndOpts[i] !== "function") {
1537
- throw new Error(`pipeline: fan-out options must be the last argument (non-function argument at stage position ${i})`);
1683
+ throw new WorkflowScriptError(`pipeline: fan-out options must be the last argument (non-function argument at stage position ${i})`);
1538
1684
  }
1539
1685
  }
1540
1686
  const trailing = stagesAndOpts.length > 0 ? stagesAndOpts[stagesAndOpts.length - 1] : undefined;
1687
+ if (trailing !== undefined && typeof trailing !== "function")
1688
+ assertFanOutOptionsShape(trailing, stagesAndOpts.length - 1);
1541
1689
  const fanOutOpts = trailing !== undefined && typeof trailing !== "function" ? trailing : undefined;
1542
1690
  const stages = (fanOutOpts !== undefined ? stagesAndOpts.slice(0, -1) : stagesAndOpts);
1691
+ assertFanOutCallables("pipeline", "stage", stages);
1543
1692
  const errorsOut = fanOutOpts?.errors;
1544
1693
  noteDivergence(run.agents.length, "ctx.pipeline ordinals are latency-dependent, so its calls always run live on a resume");
1545
1694
  diverged = true;
1546
- const settled = await Promise.all(items.map((item, index) => stages
1695
+ const settled = await Promise.all(itemList.map((item, index) => stages
1547
1696
  .reduce((acc, stage) => acc.then((prev) => stage(prev, item, index)), Promise.resolve(item))
1548
1697
  .then((v) => ({ ok: true, v }), (e) => ({ ok: false, e, i: index }))));
1549
1698
  return foldFanOutSettled(settled, "pipeline", errorsOut, emitRunLog);
@@ -8,7 +8,7 @@ export const EVENT_PROMPT_REGISTRY = new Map([
8
8
  { kind: "instructions_change", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", maxBytes: 512, defaultPolicy: "always", rendererRef: "turn-attachments.ts#collectInstructionsChange" },
9
9
  { kind: "workflow_size_guideline_change", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "always", rendererRef: "runtask.ts#workflowSizeGuidelineChangeNotice" },
10
10
  { kind: "budget_usd", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderBudgetUsd" },
11
- { kind: "total_tokens_reminder", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderTotalTokensReminder" },
11
+ { kind: "total_tokens_reminder", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "on", rendererRef: "turn-attachments.ts#renderTotalTokensReminder" },
12
12
  { kind: "background_tasks", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "on", rendererRef: "turn-attachments.ts#renderBackgroundTasks" },
13
13
  { kind: "tools_delta", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderToolsDelta" },
14
14
  { kind: "agent_listing", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "on", rendererRef: "turn-attachments.ts#renderAgentListingDelta" },
@@ -12,7 +12,7 @@ import type { AgentTool } from "../internal/harness-types.js";
12
12
  *
13
13
  * Disposition against the CURRENT base (220 corpus, `../cc-decoded/pretty220.js`): the whole
14
14
  * `# Output efficiency` section is GONE upstream. Its surviving descendant is one bullet of the
15
- * `# Tone and style` group — `vMy` @596730-596738, item @596733: "Your responses should be short and
15
+ * `# Tone and style` group — `vMy` @596730-596738, item @596733 (cli250.js:44501): "Your responses should be short and
16
16
  * concise." sema keeps the sharper 88 formulation deliberately (the bare "short and concise" is the
17
17
  * instruction this block exists to beat), so this is an ACTIVE divergence, not stale porting.
18
18
  */
@@ -57,7 +57,7 @@ export declare const DEFAULT_SYSTEM_PROMPT = "You are a capable AI agent that ac
57
57
  export declare const SUBAGENT_PROMPT = "You are a sub-agent launched by another agent to work on a delegated task. Given the caller's message, you should use the tools available to complete the task. Complete the task fully\u2014don't gold-plate, but don't leave it half-done. When you complete the task, respond with a concise report covering what was done and any key findings \u2014 the caller will relay this to the user, so it only needs the essentials.\n\nYour strengths:\n- Searching for code, configurations, and patterns across large codebases\n- Analyzing multiple files to understand system architecture\n- Investigating complex questions that require exploring many files\n- Performing multi-step research tasks\n\nGuidelines:\n- For file searches: search broadly when you don't know where something lives. Read the file directly when you know the specific file path.\n- For analysis: Start broad and narrow down. Use multiple search strategies if the first doesn't yield results.\n- Be thorough: Check multiple locations, consider different naming conventions, look for related files.\n- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one.\n- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested.\n- You are already the dedicated agent for this task. Do the work directly \u2014 do not re-delegate your entire assignment to another single subagent.";
58
58
  /**
59
59
  * RB-326 (CC 2.1.220 `Von` @597010, the notes-assembler's `Notes:` element (@597011-597016)) — the delegated child's
60
- * DELIVERY contract: absolute paths in the final response (@597013) and no report-file deliverable
60
+ * DELIVERY contract: absolute paths in the final response (@597013 (cli250.js:44539)) and no report-file deliverable
61
61
  * (@597016). Both are byte-adapted from `Von`'s array; the CC-harness-specific notes in the same block
62
62
  * (cwd reset between bash calls / no emoji / no colon before tool calls) stay intentionally UNCLAIMED
63
63
  * (§6.3 — don't claim harness behavior sema doesn't have).
@@ -130,7 +130,7 @@ export declare const NO_PERSISTENT_MEMORY_NOTICE = "# Memory\n\nYou have no pers
130
130
  /**
131
131
  * Security boundary (design/64 §6.2 B — CC's `cyberRiskInstruction.ts`, Safeguards-owned, verbatim).
132
132
  * Always injected: our base prompts only say "don't introduce vulns" (write secure code); this is the
133
- * orthogonal **refuse-malicious** boundary that distinguishes authorized security work from abuse.
133
+ * orthogonal **refuse-harmful-use** boundary that distinguishes authorized security work from abuse.
134
134
  * STABLE / cacheable; safety blocks are never feature-gated.
135
135
  */
136
136
  export declare const CYBER_RISK = "IMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.";
@@ -178,7 +178,7 @@ export declare const EXECUTION_ENVIRONMENT_OPEN_READS: string;
178
178
  */
179
179
  export declare const WORKTREE_NOTICE = "# Isolated worktree\nThis task runs in its own isolated git worktree \u2014 a separate working copy whose root is the working directory shown in # Environment, NOT the repository's main checkout. Any absolute path you were given that points at the main checkout (or another worktree) refers to a DIFFERENT copy; translate it to the same relative path under this worktree's root before reading or writing, and operate only within this worktree. A file's content here may differ from the main checkout, so re-read a file in this worktree before editing it rather than assuming an earlier or external view is current.";
180
180
  /**
181
- * RB-314 — CC 2.1.220 `atp` (@597127) VERBATIM. Rides the variable `# Environment` tail (see
181
+ * RB-314 — CC 2.1.220 `atp` (@597127 (cli250.js:44534)) VERBATIM. Rides the variable `# Environment` tail (see
182
182
  * {@link buildEnvironmentContext}), which is exactly where 220 emits it: both env-block assemblers
183
183
  * (@596927 `IMy`, @596976 `OMy`) push `worktree ? atp : null` directly after the isolated-copy
184
184
  * directive. Deliberately NOT folded into {@link WORKTREE_NOTICE}: that block is composed only for
@@ -191,7 +191,7 @@ export declare const WORKTREE_NOTICE = "# Isolated worktree\nThis task runs in i
191
191
  */
192
192
  export declare const WORKTREE_STASH_WARNING: string;
193
193
  /**
194
- * RB-204 (form-one audit, CC 2.1.220 `Von` @597021 (the string element inside `Von` @597010), the assembler's first unconditional array
194
+ * RB-204 (form-one audit, CC 2.1.220 `Von` @597021 (cli250.js:44542) (the string element inside `Von` @597010), the assembler's first unconditional array
195
195
  * element — verified present at both non-fork subagent call sites, @407444-407451 and @466602-466618,
196
196
  * and ALSO applied over a custom/marketplace agent's own `getSystemPrompt()` output, not just the
197
197
  * built-in persona): composed (via {@link PromptRuntimeFacts.isSubagent}) only for a delegated,
@@ -251,7 +251,7 @@ export declare const PROJECT_CONTEXT_FRAMING = "# Project context\nThe `<user_me
251
251
  * relation to the specific tool results or user messages in which they appear."
252
252
  * - LEAN (`wMy` @596743, heading **`# Harness`**) → "`<system-reminder>` tags in messages and tool
253
253
  * results are injected by the harness, not the user."
254
- * - THIRD ARM (`itp`'s `otp(model)` short-circuit → `gMy` @597111): "The system may send updates,
254
+ * - THIRD ARM (`itp`'s `otp(model)` short-circuit → `gMy` @597111 (cli250.js:44459)): "The system may send updates,
255
255
  * reminders, or modifications to rules via mid-conversation system turns. These are
256
256
  * system-controlled, unlike function results." sema has NO counterpart and does not need one — the
257
257
  * arm is selected by a per-model latch (`otp` @597208) over CC's own model registry, which a BYOM
@@ -407,7 +407,7 @@ export declare const GIT_SNAPSHOT_CC_PREAMBLE = "This is the git status at the s
407
407
  * `# Environment` system-prompt section — the template body itself stays CC-verbatim.
408
408
  *
409
409
  * Sanitization (deliberate CC deviation — CC injects raw): status/log/branch/user are REPO-controlled
410
- * text (filenames, commit subjects, a hostile clone's config) landing in the TRUSTED prompt region →
410
+ * text (filenames, commit subjects, an untrusted clone's config) landing in the TRUSTED prompt region →
411
411
  * multi-line fields are tag-neutralized (`sanitizeUntrustedText`), single-line fields additionally
412
412
  * fold newlines (`inlineUntrusted`), same as the rest of the `# Environment` git facts.
413
413
  */