@sema-agent/server 7.50.0 → 7.52.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 (222) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +83 -20
  4. package/dist/adoption/plan.d.ts +4 -4
  5. package/dist/adoption/plan.js +11 -0
  6. package/dist/approval-card.d.ts +26 -2
  7. package/dist/approval-card.js +9 -0
  8. package/dist/approval-hmac.d.ts +1 -1
  9. package/dist/approval-reconciler.d.ts +2 -2
  10. package/dist/approval-reconciler.js +1 -1
  11. package/dist/bench/l8/artifact.d.ts +1 -1
  12. package/dist/bench/s1/arms.d.ts +1 -1
  13. package/dist/bench/s1/live-deps.d.ts +1 -1
  14. package/dist/bench/s1/oracle.d.ts +1 -1
  15. package/dist/bench/s1/row.d.ts +1 -1
  16. package/dist/boot/budget-tracing.d.ts +1 -1
  17. package/dist/boot/budget-tracing.js +2 -5
  18. package/dist/boot/coordinators.js +3 -1
  19. package/dist/boot/deferred-sandbox-path-env.d.ts +1 -1
  20. package/dist/boot/device-lane.d.ts +79 -0
  21. package/dist/boot/device-lane.js +63 -0
  22. package/dist/boot/engine-lease.d.ts +1 -1
  23. package/dist/boot/execution-env.d.ts +48 -2
  24. package/dist/boot/execution-env.js +57 -5
  25. package/dist/boot/governance-seams.d.ts +2 -2
  26. package/dist/boot/leader.d.ts +1 -1
  27. package/dist/boot/memory-boundary.d.ts +2 -2
  28. package/dist/boot/memory-consolidation.d.ts +6 -6
  29. package/dist/boot/org-memory.d.ts +1 -1
  30. package/dist/boot/parked-revive-gate.d.ts +3 -3
  31. package/dist/boot/reapers.d.ts +2 -2
  32. package/dist/boot/reapers.js +17 -4
  33. package/dist/boot/resolve-spec.d.ts +15 -0
  34. package/dist/boot/resolve-spec.js +40 -15
  35. package/dist/boot/retention-lane.d.ts +2 -2
  36. package/dist/boot/runner-deps.d.ts +17 -7
  37. package/dist/boot/runner-deps.js +4 -2
  38. package/dist/boot/runtime-caps.d.ts +23 -0
  39. package/dist/boot/runtime-caps.js +24 -2
  40. package/dist/boot/session-faces.d.ts +6 -1
  41. package/dist/boot/session-faces.js +17 -3
  42. package/dist/boot/shutdown.d.ts +8 -0
  43. package/dist/boot/shutdown.js +3 -1
  44. package/dist/boot/stores.js +40 -1
  45. package/dist/boot/task-list-lane.d.ts +2 -2
  46. package/dist/brain.js +51 -31
  47. package/dist/budget.d.ts +2 -2
  48. package/dist/capabilities/center-plugins.d.ts +1 -1
  49. package/dist/capabilities/collab-workflows.d.ts +1 -1
  50. package/dist/capabilities/hands-lane.d.ts +3 -3
  51. package/dist/capabilities/scenarios.d.ts +3 -3
  52. package/dist/capabilities/skills.d.ts +1 -1
  53. package/dist/config-center/hot-keys-registry.js +5 -0
  54. package/dist/config-center/http-client.d.ts +2 -2
  55. package/dist/config-center/read-face.d.ts +1 -1
  56. package/dist/config-center/restart-signal.d.ts +1 -1
  57. package/dist/config-center/types.d.ts +10 -3
  58. package/dist/config-invariants.d.ts +2 -2
  59. package/dist/config-invariants.js +18 -0
  60. package/dist/config-types.d.ts +90 -18
  61. package/dist/config.js +91 -19
  62. package/dist/deployment-governance.d.ts +1 -1
  63. package/dist/device-enrollment.d.ts +125 -0
  64. package/dist/device-enrollment.js +156 -0
  65. package/dist/device-store.d.ts +385 -0
  66. package/dist/device-store.js +407 -0
  67. package/dist/device-ws-hub.d.ts +182 -0
  68. package/dist/device-ws-hub.js +1012 -0
  69. package/dist/device-ws-protocol.d.ts +429 -0
  70. package/dist/device-ws-protocol.js +464 -0
  71. package/dist/env-facts.d.ts +5 -2
  72. package/dist/env-facts.js +1 -0
  73. package/dist/execution-lane-caps.d.ts +152 -0
  74. package/dist/execution-lane-caps.js +166 -0
  75. package/dist/fleet/fleet-bus.d.ts +1 -1
  76. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  77. package/dist/fleet/fleet-terminal-window.d.ts +1 -1
  78. package/dist/http/active-run-conflict.d.ts +2 -2
  79. package/dist/http/principal-gate.d.ts +1 -1
  80. package/dist/http/route-ctx.d.ts +24 -4
  81. package/dist/http/routes/admin-memory-optout.d.ts +48 -0
  82. package/dist/http/routes/admin-memory-optout.js +75 -0
  83. package/dist/http/routes/agents-roster.d.ts +1 -1
  84. package/dist/http/routes/approvals-assistant.js +21 -21
  85. package/dist/http/routes/capabilities.js +27 -11
  86. package/dist/http/routes/diagnostics.d.ts +1 -1
  87. package/dist/http/routes/leader.d.ts +1 -1
  88. package/dist/http/routes/memory-bundle.d.ts +1 -1
  89. package/dist/http/routes/memory-bundle.js +3 -1
  90. package/dist/http/routes/memory-compliance.d.ts +4 -4
  91. package/dist/http/routes/memory-origin.d.ts +4 -4
  92. package/dist/http/routes/memory-origin.js +3 -1
  93. package/dist/http/routes/memory-policy.js +6 -2
  94. package/dist/http/routes/retention-ops.d.ts +1 -1
  95. package/dist/http/routes/rules.d.ts +5 -2
  96. package/dist/http/routes/rules.js +84 -5
  97. package/dist/http/routes/run-memory-capture.d.ts +34 -0
  98. package/dist/http/routes/run-memory-capture.js +97 -0
  99. package/dist/http/routes/runs.d.ts +1 -0
  100. package/dist/http/routes/runs.js +212 -6
  101. package/dist/http/routes/session-sync.d.ts +4 -6
  102. package/dist/http/routes/session-sync.js +60 -47
  103. package/dist/http/routes/sessions.js +61 -188
  104. package/dist/http/routes/shared-memory.d.ts +1 -1
  105. package/dist/http/routes/tasks.js +1 -1
  106. package/dist/http/routes/workflows.js +19 -0
  107. package/dist/http/server.d.ts +49 -13
  108. package/dist/http/server.js +109 -13
  109. package/dist/http/verify-rounds.d.ts +1 -1
  110. package/dist/http/wire-types.d.ts +41 -8
  111. package/dist/leader/endpoint.d.ts +1 -1
  112. package/dist/leader/leader.d.ts +1 -1
  113. package/dist/leader/repair-oracle.d.ts +2 -2
  114. package/dist/leader/repair-oracle.js +1 -1
  115. package/dist/leader/wire.js +4 -2
  116. package/dist/main.js +23 -9
  117. package/dist/memory-bundle-engine.d.ts +1 -1
  118. package/dist/memory-operator-faces.d.ts +5 -5
  119. package/dist/memory-origin-fence.d.ts +50 -0
  120. package/dist/memory-origin-fence.js +15 -0
  121. package/dist/memory-posture.d.ts +13 -1
  122. package/dist/memory-posture.js +1 -0
  123. package/dist/memory-scope.d.ts +6 -1
  124. package/dist/memory-scope.js +3 -2
  125. package/dist/model-select.d.ts +2 -2
  126. package/dist/observability/fail-open.d.ts +17 -5
  127. package/dist/observability/fail-open.js +17 -5
  128. package/dist/orchestration/hardened-vm-runner.d.ts +9 -2
  129. package/dist/orchestration/hardened-vm-runner.js +11 -1
  130. package/dist/orchestration/hardened-vm-worker-runner.js +2 -2
  131. package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
  132. package/dist/plugins/background-shell-support.d.ts +2 -2
  133. package/dist/plugins/blob-backend.d.ts +7 -2
  134. package/dist/plugins/blob-backend.js +22 -18
  135. package/dist/plugins/caching-session-store.d.ts +7 -3
  136. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  137. package/dist/plugins/device-store-sql.d.ts +130 -0
  138. package/dist/plugins/device-store-sql.js +574 -0
  139. package/dist/plugins/file-history-store-sql.d.ts +170 -0
  140. package/dist/plugins/file-history-store-sql.js +827 -0
  141. package/dist/plugins/file-run-store.d.ts +44 -5
  142. package/dist/plugins/file-run-store.js +85 -13
  143. package/dist/plugins/fork-routing-session-store.d.ts +1 -1
  144. package/dist/plugins/fork-routing-session-store.js +2 -2
  145. package/dist/plugins/leader-run-store-sql.d.ts +1 -1
  146. package/dist/plugins/local-session-store.d.ts +9 -1
  147. package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
  148. package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
  149. package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
  150. package/dist/plugins/memory-origin-law.d.ts +2 -2
  151. package/dist/plugins/memory-run-store.d.ts +4 -3
  152. package/dist/plugins/memory-run-store.js +2 -1
  153. package/dist/plugins/permission-rule-store-file.d.ts +16 -6
  154. package/dist/plugins/permission-rule-store-file.js +77 -13
  155. package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
  156. package/dist/plugins/permission-rule-store-sql.js +123 -21
  157. package/dist/plugins/pg-pool.js +59 -0
  158. package/dist/plugins/pg-safe-json.d.ts +1 -1
  159. package/dist/plugins/remote-env-device.d.ts +271 -0
  160. package/dist/plugins/remote-env-device.js +727 -0
  161. package/dist/plugins/remote-env-k8s.d.ts +1 -1
  162. package/dist/plugins/remote-scratchpad.js +1 -1
  163. package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
  164. package/dist/plugins/retention-store-sql.d.ts +7 -7
  165. package/dist/plugins/retention-store-sql.js +6 -0
  166. package/dist/plugins/run-store-sql.d.ts +9 -3
  167. package/dist/plugins/run-store-sql.js +2 -2
  168. package/dist/plugins/send-user-file.d.ts +1 -1
  169. package/dist/plugins/session-placement.d.ts +1 -1
  170. package/dist/plugins/sql-errors.d.ts +1 -1
  171. package/dist/plugins/store-backend.d.ts +68 -29
  172. package/dist/plugins/store-backend.js +78 -15
  173. package/dist/plugins/store-contracts.d.ts +5 -3
  174. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  175. package/dist/plugins/tidb-pool.js +59 -0
  176. package/dist/plugins/web-search.d.ts +1 -1
  177. package/dist/prompts-domain-validate.js +1 -1
  178. package/dist/resource-suspend.d.ts +1 -1
  179. package/dist/resource-window.d.ts +3 -3
  180. package/dist/rules-consent.d.ts +231 -9
  181. package/dist/rules-consent.js +411 -13
  182. package/dist/run-local.js +1 -0
  183. package/dist/runs.d.ts +15 -1
  184. package/dist/runs.js +19 -0
  185. package/dist/runtime-caps-resolver.d.ts +36 -1
  186. package/dist/runtime-caps-resolver.js +25 -0
  187. package/dist/runtime-governance.d.ts +1 -1
  188. package/dist/security.d.ts +5 -5
  189. package/dist/security.js +1 -1
  190. package/dist/session-sync.d.ts +52 -37
  191. package/dist/session-sync.js +19 -16
  192. package/dist/shared-memory-scope-authorizer.d.ts +1 -1
  193. package/dist/sighup-idle.d.ts +1 -1
  194. package/dist/spec-fields.d.ts +22 -1
  195. package/dist/spec-fields.js +11 -0
  196. package/dist/store-live-probe.d.ts +2 -2
  197. package/dist/task-a2a.d.ts +1 -1
  198. package/dist/task-cwd.d.ts +25 -0
  199. package/dist/task-cwd.js +3 -0
  200. package/dist/task-mcp.d.ts +3 -3
  201. package/dist/task-settings.d.ts +3 -3
  202. package/dist/task-settings.js +3 -1
  203. package/dist/task-workflow.d.ts +1 -1
  204. package/dist/text-bidi.d.ts +2 -2
  205. package/dist/tool-approval.d.ts +191 -9
  206. package/dist/tool-approval.js +248 -29
  207. package/dist/trace/core-keyset-guard.d.ts +1 -1
  208. package/dist/trace/engine-notice-wire.d.ts +6 -4
  209. package/dist/trace/engine-notice-wire.js +2 -0
  210. package/dist/trace/ledger-events.d.ts +1 -1
  211. package/dist/trace/ledger-sink.d.ts +2 -2
  212. package/dist/trace/project.d.ts +3 -2
  213. package/dist/trace/project.js +1 -0
  214. package/dist/turn-activity.d.ts +1 -1
  215. package/dist/wall-clock-jump-guard.d.ts +3 -3
  216. package/package.json +2 -2
  217. package/dist/http/tar.d.ts +0 -17
  218. package/dist/http/tar.js +0 -53
  219. package/dist/http/workspace-content.d.ts +0 -11
  220. package/dist/http/workspace-content.js +0 -20
  221. package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
  222. package/dist/plugins/file-snapshot-store-sql.js +0 -213
@@ -6,7 +6,7 @@ import { hasBidiControls } from "./text-bidi.js";
6
6
  import { createLogger } from "./observability/logger.js";
7
7
  import { recordFailOpen } from "./observability/fail-open.js";
8
8
  import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
9
- import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, } from "./approval-card.js";
9
+ import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, } from "./approval-card.js";
10
10
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
11
11
  const defaultLogger = createLogger();
12
12
  export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
@@ -16,6 +16,12 @@ export function isApprovalGateKind(k) {
16
16
  export const APPROVAL_GATE_KINDS_SQL_IN = `(${APPROVAL_GATE_KINDS.map((k) => `'${k}'`).join(",")})`;
17
17
  const MAX_APPROVAL_ARGS_BYTES = 16384;
18
18
  const DEFAULT_APPROVAL_TTL_MS = 5 * 60_000;
19
+ const DEFAULT_UNREACHED_ASK_TTL_MS = 60 * 60_000;
20
+ const SUSPENDED_POLL_MAX_PER_TICK = 64;
21
+ const SUSPENDED_POLL_BUDGET_MS = 5_000;
22
+ const EDITED_DECISION_MARK_CAP_MS = 10_000;
23
+ const ABANDONED_EDIT_TOMBSTONE_TTL_MS = 10 * 60_000;
24
+ const ABANDONED_EDIT_TOMBSTONE_MAX = 256;
19
25
  const DEFAULT_WINDOW_MARGIN_MS = 10_000;
20
26
  const MAX_ALLOW_SESSIONS = 4096;
21
27
  const DEFAULT_ADMIT_MAX_PER_TASK = 32;
@@ -35,6 +41,7 @@ function durableRuleMaterial(material) {
35
41
  }
36
42
  return { ruleCommand: material.command, ruleScopeRoot: material.scopeRoot ?? null };
37
43
  }
44
+ export const ASK_DECISION_CONSUMER_ABSENT = "absent";
38
45
  const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
39
46
  const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
40
47
  const DURABLE_CALL_TIMEOUT_MS = 5_000;
@@ -203,12 +210,16 @@ export class ToolApprovalCoordinator {
203
210
  allowAllSessions = new Set();
204
211
  streams = new Map();
205
212
  ttlMs;
213
+ unreachedTtlMs;
206
214
  askStore;
207
215
  windowMarginMs;
208
216
  admitMaxPerTask;
209
217
  admitMaxPerOwner;
210
218
  admitByTask = new Map();
211
219
  admitByOwner = new Map();
220
+ suspendedPollOffset = 0;
221
+ editedDecisionsInFlight = new Map();
222
+ abandonedEditedDecisions = new Map();
212
223
  storeErrorWarned = false;
213
224
  storeErrorTally = 0;
214
225
  governanceAskMarks;
@@ -225,6 +236,7 @@ export class ToolApprovalCoordinator {
225
236
  this.ruleScopeRootFor = opts?.ruleScopeRootFor;
226
237
  this.governanceAskMarks = opts?.governanceAskMarks;
227
238
  this.ttlMs = opts?.ttlMs ?? DEFAULT_APPROVAL_TTL_MS;
239
+ this.unreachedTtlMs = opts?.unreachedTtlMs ?? DEFAULT_UNREACHED_ASK_TTL_MS;
228
240
  this.askStore = opts?.askStore;
229
241
  this.windowMarginMs = opts?.windowMarginMs ?? DEFAULT_WINDOW_MARGIN_MS;
230
242
  this.admitMaxPerTask = opts?.admitMaxPerTask ?? DEFAULT_ADMIT_MAX_PER_TASK;
@@ -427,6 +439,12 @@ export class ToolApprovalCoordinator {
427
439
  const row = await this.withStoreDeadline(this.askStore.getAsk(askId), "getAsk(expire-error-recovery)", DURABLE_CONVERGE_READ_TIMEOUT_MS);
428
440
  if (row === null || row.state !== "DECIDED")
429
441
  return undefined;
442
+ if (replayTerminalAskRow(row) === true && this.abandonedEditedDecisionPending(askId)) {
443
+ this.guardRowDerivedApprove(askId, true, "expire-error-recovery");
444
+ return undefined;
445
+ }
446
+ if (this.editedDecisionInFlight(askId))
447
+ return undefined;
430
448
  const replayed = replayTerminalAskRow(row);
431
449
  return typeof replayed === "boolean" ? replayed : undefined;
432
450
  }
@@ -435,6 +453,15 @@ export class ToolApprovalCoordinator {
435
453
  return undefined;
436
454
  }
437
455
  }
456
+ hasOtherSuspendedOwner(askId, self) {
457
+ if (askId === undefined)
458
+ return false;
459
+ for (const e of this.pendingByAskId.get(askId) ?? []) {
460
+ if (e !== self && e.suspended === true && e.settledOutcome === undefined)
461
+ return true;
462
+ }
463
+ return false;
464
+ }
438
465
  settleSameAskIdParkRoute(askId) {
439
466
  const entries = this.pendingByAskId.get(askId);
440
467
  if (!entries)
@@ -506,6 +533,10 @@ export class ToolApprovalCoordinator {
506
533
  const entries = this.pendingByAskId.get(askId);
507
534
  if (!entries)
508
535
  return { settled: 0 };
536
+ if (allowed && updatedInput === undefined) {
537
+ if (this.guardRowDerivedApprove(askId, true, "notify-external-decision") === "unavailable")
538
+ return { settled: 0 };
539
+ }
509
540
  let settled = 0;
510
541
  for (const entry of [...entries]) {
511
542
  entry.settle(allowed, allowed ? "allowed" : "denied", updatedInput);
@@ -572,33 +603,49 @@ export class ToolApprovalCoordinator {
572
603
  const ctx = this.als.getStore();
573
604
  if (!ctx)
574
605
  return this.unattendedOutcome();
575
- return this.askBroadcast({ taskId: ctx.taskId, legKey: ctx.legKey ?? "", ...(ctx.legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic: ctx.legDeadlineMonotonic } : {}) }, [ctx], req, signal);
606
+ return this.askBroadcast({
607
+ taskId: ctx.taskId,
608
+ owner: ctx.owner,
609
+ ...(ctx.sessionId !== undefined ? { sessionId: ctx.sessionId } : {}),
610
+ legKey: ctx.legKey ?? "",
611
+ ...(ctx.legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic: ctx.legDeadlineMonotonic } : {}),
612
+ }, [ctx], req, signal);
576
613
  };
577
614
  boundAsk = (identity) => {
578
615
  return (req, signal) => {
579
616
  const set = this.streams.get(this.streamKey(identity.owner, identity.sessionId, identity.taskId));
580
617
  const live = set && set.size > 0 ? [...set] : [];
581
- if (live.length === 0)
582
- return Promise.resolve(this.unattendedOutcome());
583
- return this.askBroadcast({ taskId: identity.taskId, legKey: identity.legKey ?? "", ...(identity.legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic: identity.legDeadlineMonotonic } : {}) }, live, req, signal);
618
+ return this.askBroadcast({
619
+ taskId: identity.taskId,
620
+ owner: identity.owner,
621
+ ...(identity.sessionId !== undefined ? { sessionId: identity.sessionId } : {}),
622
+ legKey: identity.legKey ?? "",
623
+ ...(identity.legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic: identity.legDeadlineMonotonic } : {}),
624
+ }, live, req, signal);
584
625
  };
585
626
  };
586
627
  async askBroadcast(origin, ctxs, req, signal) {
587
628
  if (signal?.aborted)
588
629
  return false;
589
630
  const aliveCtxs = ctxs.filter((c) => !c.abortSignal?.aborted);
590
- if (aliveCtxs.length === 0)
591
- return this.unattendedOutcome();
631
+ const reachability = aliveCtxs.length === 0 ? "unreached" : "reachable";
632
+ if (reachability === "unreached") {
633
+ const unattended = this.unattendedOutcome();
634
+ if (unattended === false)
635
+ return unattended;
636
+ if (!this.askStore)
637
+ return unattended;
638
+ }
592
639
  const primary = aliveCtxs[0];
593
- const governanceForced = (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(primary.owner, primary.sessionId, origin.taskId))).isMarked(req.toolCallId);
640
+ const governanceForced = (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
594
641
  if (!governanceForced &&
595
642
  req.requiresRealApproval !== true &&
596
- primary.sessionId &&
597
- this.allowAllSessions.has(sessionAllowKey(primary.owner, primary.sessionId, sessionAllowCategory(req.toolName))) &&
643
+ origin.sessionId &&
644
+ this.allowAllSessions.has(sessionAllowKey(origin.owner, origin.sessionId, sessionAllowCategory(req.toolName))) &&
598
645
  (await this.sessionGrantUncontestedByRow(origin, req))) {
599
646
  return true;
600
647
  }
601
- const releaseAdmission = this.admit(origin.taskId, primary.owner);
648
+ const releaseAdmission = this.admit(origin.taskId, origin.owner);
602
649
  if (!releaseAdmission) {
603
650
  defaultLogger.warn("approval admission gate: too many pending asks — routing to park", {
604
651
  taskId: origin.taskId,
@@ -612,7 +659,7 @@ export class ToolApprovalCoordinator {
612
659
  const child = isChildAsk(req);
613
660
  const bounded = boundArgs(req.args);
614
661
  let effectiveGovernanceForced = governanceForced;
615
- let ruleLaneMaterial = this.buildRuleLaneMaterial(req, primary.owner, governanceForced, primary.sessionId);
662
+ let ruleLaneMaterial = this.buildRuleLaneMaterial(req, origin.owner, governanceForced, origin.sessionId);
616
663
  const frame = {
617
664
  type: "tool_approval",
618
665
  approvalId: id,
@@ -641,12 +688,14 @@ export class ToolApprovalCoordinator {
641
688
  ? { persistedRuleShadowed: redactSecrets(req.persistedRuleShadowed).slice(0, MAX_RULE_TEXT_CHARS) }
642
689
  : {}),
643
690
  ...(ruleLaneMaterial !== undefined ? { ruleOffers: ruleLaneMaterial.offers } : {}),
691
+ ...((a) => (a !== undefined ? { ruleOffersAbsence: a } : {}))(readRuleOffersAbsence(req)),
644
692
  ...((c) => (c !== undefined ? { probeCause: c } : {}))(readProbeCause(req)),
645
693
  ...((c) => (c !== undefined ? { ruleEvidence: c } : {}))(readRuleEvidence(req)),
646
694
  ...(bounded.omitted ? { argsOmitted: true } : { args: bounded.args }),
647
695
  ...(bounded.hasBidi || req.hasBidiControls === true ? { inputHasBidi: true } : {}),
648
696
  };
649
- const effectiveWindowMs = effectiveAskWindowMs(this.ttlMs, this.windowMarginMs, origin.legDeadlineMonotonic, performance.now());
697
+ const windowBudgetMs = reachability === "unreached" ? this.unreachedTtlMs : this.ttlMs;
698
+ const effectiveWindowMs = effectiveAskWindowMs(windowBudgetMs, this.windowMarginMs, origin.legDeadlineMonotonic, performance.now());
650
699
  const expiresAtMs = Date.now() + effectiveWindowMs;
651
700
  let askId;
652
701
  let batchId;
@@ -676,8 +725,8 @@ export class ToolApprovalCoordinator {
676
725
  askId,
677
726
  taskId: origin.taskId,
678
727
  sourceTaskId,
679
- sessionId: primary.sessionId ?? origin.taskId,
680
- owner: primary.owner,
728
+ sessionId: origin.sessionId ?? origin.taskId,
729
+ owner: origin.owner,
681
730
  batchId,
682
731
  toolCallId: req.toolCallId,
683
732
  legKey,
@@ -695,13 +744,14 @@ export class ToolApprovalCoordinator {
695
744
  });
696
745
  if (existingOrCreated.row.state !== "STREAM_PENDING") {
697
746
  releaseAdmission();
698
- const replayed = replayTerminalAskRow(existingOrCreated.row);
747
+ const replayed = this.guardRowDerivedApprove(askId, replayTerminalAskRow(existingOrCreated.row), "ensure-ask-idempotent-replay");
699
748
  if (replayed === true)
700
749
  recordRowDerivedApproveReplay("ensure-ask-idempotent-replay");
701
750
  return replayed === "unavailable" ? this.unattendedOutcome() : replayed;
702
751
  }
703
752
  const persistedEnvelope = ApprovalCardEnvelopeSchema.safeParse(existingOrCreated.row.cardJson);
704
- const sanePersistedDeadline = Number.isFinite(existingOrCreated.row.expiresAtMs) && existingOrCreated.row.expiresAtMs <= Date.now() + effectiveWindowMs + DURABLE_CALL_TIMEOUT_MS;
753
+ const maxMintableWindowMs = Math.max(effectiveWindowMs, this.unreachedTtlMs);
754
+ const sanePersistedDeadline = Number.isFinite(existingOrCreated.row.expiresAtMs) && existingOrCreated.row.expiresAtMs <= Date.now() + maxMintableWindowMs + DURABLE_CALL_TIMEOUT_MS;
705
755
  if (!persistedEnvelope.success || !sanePersistedDeadline) {
706
756
  this.noteStoreError(new Error(`persisted approval row is unusable (envelopeOk=${persistedEnvelope.success}, deadlineOk=${sanePersistedDeadline}, ` +
707
757
  `origin=${existingOrCreated.inserted ? "row-this-call-just-inserted" : "row-hit-idempotently"})`), "ensureAsk(persisted-row-unusable)");
@@ -772,6 +822,10 @@ export class ToolApprovalCoordinator {
772
822
  abandonedEnsure = true;
773
823
  if (lateEnsuredAskId !== undefined)
774
824
  voidAbandonedRow(lateEnsuredAskId);
825
+ if (reachability === "unreached") {
826
+ releaseAdmission();
827
+ return this.unattendedOutcome();
828
+ }
775
829
  }
776
830
  }
777
831
  }
@@ -818,7 +872,7 @@ export class ToolApprovalCoordinator {
818
872
  sig.removeEventListener("abort", fn);
819
873
  this.pending.delete(id);
820
874
  if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.unattendedOutcome() !== false) {
821
- this.recordParkTombstone(wireApprovalId, { askId, batchId, owner: primary.owner, parkedAtMs: Date.now() });
875
+ this.recordParkTombstone(wireApprovalId, { askId, batchId, owner: origin.owner, parkedAtMs: Date.now() });
822
876
  parkRouted = true;
823
877
  }
824
878
  if (askId !== undefined) {
@@ -851,11 +905,11 @@ export class ToolApprovalCoordinator {
851
905
  settle(false, "expired");
852
906
  return;
853
907
  }
854
- if (row.state === "STREAM_PENDING") {
908
+ if (row.state === "STREAM_PENDING" || this.editedDecisionInFlight(pinnedAskId)) {
855
909
  await new Promise((r) => setTimeout(r, DURABLE_CONVERGE_BACKOFF_MS[attempt]).unref?.());
856
910
  continue;
857
911
  }
858
- const outcome = replayTerminalAskRow(row);
912
+ const outcome = this.guardRowDerivedApprove(pinnedAskId, replayTerminalAskRow(row), `converge:${where}`);
859
913
  if (outcome === "unavailable") {
860
914
  windowRouteUnavailable = true;
861
915
  settle(false, "expired");
@@ -874,8 +928,15 @@ export class ToolApprovalCoordinator {
874
928
  await new Promise((r) => setTimeout(r, DURABLE_CONVERGE_BACKOFF_MS[attempt]).unref?.());
875
929
  }
876
930
  }
877
- if (!done)
878
- settle(false, "expired");
931
+ if (!done) {
932
+ if (this.editedDecisionInFlight(pinnedAskId)) {
933
+ windowRouteUnavailable = true;
934
+ settle(false, "expired");
935
+ }
936
+ else {
937
+ settle(false, "expired");
938
+ }
939
+ }
879
940
  };
880
941
  const settleParkRouteIfUnsettled = (hostSettledBy) => {
881
942
  if (done)
@@ -937,6 +998,10 @@ export class ToolApprovalCoordinator {
937
998
  settle(false, "expired");
938
999
  return;
939
1000
  }
1001
+ if (this.hasOtherSuspendedOwner(askId, selfEntry)) {
1002
+ settle(false, "expired");
1003
+ return;
1004
+ }
940
1005
  try {
941
1006
  const won = await this.withStoreDeadline(this.askStore.transitionAsk(askId, "STREAM_PENDING", "VOID", { updatedAtMs: Date.now() }), "transitionAsk(cancel)", DURABLE_CALL_TIMEOUT_MS, (lateWon) => {
942
1007
  if (!lateWon)
@@ -991,11 +1056,11 @@ export class ToolApprovalCoordinator {
991
1056
  settle,
992
1057
  forceParkNow,
993
1058
  settleParkRoute: () => settleParkRouteIfUnsettled(),
994
- owner: primary.owner,
995
- ctxTaskId: primary.taskId,
996
- originCtx: primary,
997
- ...(child ? { originTaskId: req.sourceTaskId } : { targetCtxs: new Set(aliveCtxs), onTargetGone }),
998
- ...(primary.sessionId ? { sessionId: primary.sessionId } : {}),
1059
+ owner: origin.owner,
1060
+ ...(primary ? { ctxTaskId: primary.taskId, originCtx: primary } : {}),
1061
+ ...(reachability === "unreached" ? { suspended: true } : {}),
1062
+ ...(child ? { originTaskId: req.sourceTaskId } : primary ? { targetCtxs: new Set(aliveCtxs), onTargetGone } : {}),
1063
+ ...(origin.sessionId ? { sessionId: origin.sessionId } : {}),
999
1064
  toolName: req.toolName,
1000
1065
  registeredAtMs: Date.now(),
1001
1066
  expiresAtMs: persistedExpiresAtMs,
@@ -1045,7 +1110,7 @@ export class ToolApprovalCoordinator {
1045
1110
  const openByCtx = new Map(aliveCtxs.map((c) => [c, emitOne(c)]));
1046
1111
  const emitAllP = (async () => {
1047
1112
  const results = await Promise.all(openByCtx.values());
1048
- const anySucceeded = results.some((r) => r.legacy || r.card);
1113
+ const anySucceeded = reachability === "unreached" || this.hasOtherSuspendedOwner(askId, selfEntry) || results.some((r) => r.legacy || r.card);
1049
1114
  if (!anySucceeded && !done) {
1050
1115
  if (this.askStore && askId !== undefined && batchId !== undefined) {
1051
1116
  try {
@@ -1580,14 +1645,168 @@ export class ToolApprovalCoordinator {
1580
1645
  ...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
1581
1646
  ...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
1582
1647
  ...(e.governanceForced ? { governanceForced: true } : {}),
1583
- ...(e.originTaskId !== undefined ? { fromSubagent: true } : {}),
1648
+ ...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
1584
1649
  });
1585
1650
  }
1586
1651
  return rows;
1587
1652
  }
1653
+ async pollSuspendedDecisions() {
1654
+ const store = this.askStore;
1655
+ if (!store)
1656
+ return { polled: 0, settled: 0, backlog: 0, truncated: false };
1657
+ const candidates = [];
1658
+ const seen = new Set();
1659
+ for (const entry of this.pending.values()) {
1660
+ if (entry.suspended === true && entry.askId !== undefined && entry.settledOutcome === undefined && !seen.has(entry.askId)) {
1661
+ seen.add(entry.askId);
1662
+ candidates.push(entry.askId);
1663
+ }
1664
+ }
1665
+ const start = candidates.length > 0 ? this.suspendedPollOffset % candidates.length : 0;
1666
+ const ordered = [...candidates.slice(start), ...candidates.slice(0, start)];
1667
+ const deadlineMs = Date.now() + SUSPENDED_POLL_BUDGET_MS;
1668
+ let settled = 0;
1669
+ let polled = 0;
1670
+ let truncated = false;
1671
+ for (const askId of ordered) {
1672
+ if (polled >= SUSPENDED_POLL_MAX_PER_TICK || Date.now() >= deadlineMs) {
1673
+ truncated = true;
1674
+ break;
1675
+ }
1676
+ polled++;
1677
+ this.suspendedPollOffset = start + polled;
1678
+ let row;
1679
+ try {
1680
+ row = await this.withStoreDeadline(store.getAsk(askId), "getAsk(poll-suspended)", DURABLE_CONVERGE_READ_TIMEOUT_MS);
1681
+ }
1682
+ catch (err) {
1683
+ this.noteStoreError(err, "getAsk(poll-suspended)");
1684
+ continue;
1685
+ }
1686
+ if (row === null || row.state === "STREAM_PENDING")
1687
+ continue;
1688
+ if (this.editedDecisionInFlight(askId))
1689
+ continue;
1690
+ const outcome = replayTerminalAskRow(row);
1691
+ if (outcome === "unavailable") {
1692
+ const before = this.pendingByAskId.get(askId)?.size ?? 0;
1693
+ this.settleSameAskIdParkRoute(askId);
1694
+ settled += before;
1695
+ continue;
1696
+ }
1697
+ if (typeof outcome !== "boolean")
1698
+ continue;
1699
+ const delivered = this.notifyExternalDecision(askId, outcome).settled;
1700
+ if (outcome && delivered > 0)
1701
+ recordRowDerivedApproveReplay("poll-suspended");
1702
+ settled += delivered;
1703
+ }
1704
+ const backlog = Math.max(0, candidates.length - polled);
1705
+ if (truncated) {
1706
+ defaultLogger.warn("approval_suspended_poll_truncated", {
1707
+ polled,
1708
+ settled,
1709
+ backlog,
1710
+ maxPerTick: SUSPENDED_POLL_MAX_PER_TICK,
1711
+ budgetMs: SUSPENDED_POLL_BUDGET_MS,
1712
+ resumeFromOffset: this.suspendedPollOffset,
1713
+ });
1714
+ }
1715
+ return { polled, settled, backlog, truncated };
1716
+ }
1717
+ liveConsumerCount(askId) {
1718
+ let n = 0;
1719
+ for (const e of this.pendingByAskId.get(askId) ?? [])
1720
+ if (e.settledOutcome === undefined)
1721
+ n++;
1722
+ return n;
1723
+ }
1724
+ markEditedDecisionInFlight(askId) {
1725
+ this.editedDecisionsInFlight.set(askId, (this.editedDecisionsInFlight.get(askId) ?? 0) + 1);
1726
+ let released = false;
1727
+ const drop = () => {
1728
+ if (released)
1729
+ return;
1730
+ released = true;
1731
+ clearTimeout(watchdog);
1732
+ const n = (this.editedDecisionsInFlight.get(askId) ?? 1) - 1;
1733
+ if (n <= 0)
1734
+ this.editedDecisionsInFlight.delete(askId);
1735
+ else
1736
+ this.editedDecisionsInFlight.set(askId, n);
1737
+ };
1738
+ const watchdog = setTimeout(() => {
1739
+ if (released)
1740
+ return;
1741
+ defaultLogger.warn("approval_edited_decision_mark_expired", { askId, capMs: EDITED_DECISION_MARK_CAP_MS });
1742
+ this.abandonedEditedDecisions.set(askId, Date.now() + ABANDONED_EDIT_TOMBSTONE_TTL_MS);
1743
+ if (this.abandonedEditedDecisions.size > ABANDONED_EDIT_TOMBSTONE_MAX) {
1744
+ const oldest = this.abandonedEditedDecisions.keys().next();
1745
+ if (!oldest.done)
1746
+ this.dropAbandonedEditTombstone(oldest.value, "capacity");
1747
+ }
1748
+ drop();
1749
+ }, EDITED_DECISION_MARK_CAP_MS);
1750
+ watchdog.unref?.();
1751
+ return drop;
1752
+ }
1753
+ editedDecisionInFlightCount(askId) {
1754
+ return this.editedDecisionsInFlight.get(askId) ?? 0;
1755
+ }
1756
+ editedDecisionInFlight(askId) {
1757
+ return (this.editedDecisionsInFlight.get(askId) ?? 0) > 0;
1758
+ }
1759
+ abandonedEditedDecisionPending(askId) {
1760
+ const until = this.abandonedEditedDecisions.get(askId);
1761
+ if (until === undefined)
1762
+ return false;
1763
+ if (Date.now() > until) {
1764
+ this.dropAbandonedEditTombstone(askId, "ttl");
1765
+ return true;
1766
+ }
1767
+ return true;
1768
+ }
1769
+ guardRowDerivedApprove(askId, outcome, where) {
1770
+ if (outcome !== true || askId === undefined)
1771
+ return outcome;
1772
+ if (!this.abandonedEditedDecisionPending(askId))
1773
+ return outcome;
1774
+ defaultLogger.warn("approval_row_derived_approve_parked_after_abandoned_edit", { askId, where });
1775
+ this.settleSameAskIdParkRoute(askId);
1776
+ return "unavailable";
1777
+ }
1778
+ dropAbandonedEditTombstone(askId, why) {
1779
+ const stillWaiting = this.liveConsumerCount(askId);
1780
+ if (stillWaiting > 0) {
1781
+ defaultLogger.warn("approval_abandoned_edit_tombstone_dropped_parking_consumers", { askId, why, parked: stillWaiting });
1782
+ this.settleSameAskIdParkRoute(askId);
1783
+ }
1784
+ this.abandonedEditedDecisions.delete(askId);
1785
+ }
1786
+ expireEditedDecisionMarkForTest(askId) {
1787
+ this.abandonedEditedDecisions.set(askId, Date.now() + ABANDONED_EDIT_TOMBSTONE_TTL_MS);
1788
+ this.editedDecisionsInFlight.delete(askId);
1789
+ }
1790
+ forceAbandonedEditTombstoneExpiryForTest(askId) {
1791
+ if (!this.abandonedEditedDecisions.has(askId))
1792
+ return;
1793
+ this.abandonedEditedDecisions.set(askId, Date.now() - 1);
1794
+ this.abandonedEditedDecisionPending(askId);
1795
+ }
1796
+ setAbandonedEditTombstoneDeadlineForTest(askId, atMs) {
1797
+ if (this.abandonedEditedDecisions.has(askId))
1798
+ this.abandonedEditedDecisions.set(askId, atMs);
1799
+ }
1588
1800
  pendingCount() {
1589
1801
  return this.pending.size;
1590
1802
  }
1803
+ suspendedCount() {
1804
+ let n = 0;
1805
+ for (const e of this.pending.values())
1806
+ if (e.suspended === true)
1807
+ n++;
1808
+ return n;
1809
+ }
1591
1810
  sessionAllowedCount() {
1592
1811
  return this.allowAllSessions.size;
1593
1812
  }
@@ -26,7 +26,7 @@ type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "descripti
26
26
  type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, BgNotifProjected | BgNotifExcluded>>;
27
27
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
28
28
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
29
- type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval";
29
+ type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval";
30
30
  type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld";
31
31
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
32
32
  type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end";
@@ -1,5 +1,7 @@
1
1
  /**
2
- * 上 wire 的通告码 —— **显式闭集常量表**(设计稿 ①,起步三码;今天九码)。
2
+ * 上 wire 的通告码 —— **显式闭集常量表**(设计稿 ①,起步三码;今天的码数以本常量自身为准——
3
+ * 在散文里抄数字必陈腐,codex 5.65 增量批 R1-[low] 实逮:上一句曾写「今天九码」而表里是十条。
4
+ * 码数的**受门**真源=契约文档附录 D.3 的小节标题,⑥b 双向门盯着)。
3
5
  *
4
6
  * 起步的那六码同族:都是「这条 session 的记忆姿态」的披露,resume 之后仍然相关 ⇒ 也进 durable 账本
5
7
  * (cli 断连补看走既有 events 重放腿,[4634](ii))。
@@ -14,7 +16,7 @@
14
16
  * 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
15
17
  * 刚立,立完就在下一次加码时自己咬住了)。
16
18
  */
17
- export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained"];
19
+ export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted"];
18
20
  export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
19
21
  /** 白名单谓词(单点):路由与门都读这一个,不许第二处手抄码串。 */
20
22
  export declare function isEngineNoticeWireCode(code: string): code is EngineNoticeWireCode;
@@ -80,13 +82,13 @@ export type EngineNoticeWireSink = (row: EngineNoticeWireRow) => void;
80
82
  *
81
83
  * 🔴 跨 session 隔离靠的是 **Map 键的精确串相等**,不是任何「最近活跃」回落 —— 缺席就是缺席(G5)。
82
84
  *
83
- * 🔴 **同 session 的跨代隔离靠 legId「最新代独占」**(#310 codex 对抗复审 R1-[high],验真后修)。
85
+ * 🔴 **同 session 的跨代隔离靠 legId「最新代独占」**(#310 codex 交叉复审 R1-[high],验真后修)。
84
86
  * 病(真窗,本仓每一处 in-flight 登记都为它写过 identity-guard):run 行在**流体内**就已 setTerminal /
85
87
  * setSuspended,而本腿的注销在 `finally` —— 中间那一窗里,同一条 session 上的**下一条腿**(快速续跑 /
86
88
  * 紧接着的新提交)已经注册好了自己的口。若按「会话内全量扇出」投,新腿的一条通告会**同时**落进:
87
89
  * · 旧腿那条已 end 的 SSE(顶多记一次 fail-open,无害),以及
88
90
  * · **旧 taskId 的账本** —— 一条不属于那条 run 的、终态之后的行;resume 复用同 taskId 时还会与新腿
89
- * 抢同一个 `(task_id, seq)` 域。这不是跨租户泄露,是**同租户跨 run 的账本污染**,同样必须关死。
91
+ * 抢同一个 `(task_id, seq)` 域。这不是跨租户外泄,是**同租户跨 run 的账本污染**,同样必须关死。
90
92
  * 药:每次 `registerLeg` 铸一个单调 legId,`route` **只投最新代**;旧代的口留在表里(它自己的注销仍
91
93
  * 按身份精确删,不会误删新代),但一帧都收不到。方向 = fail-closed(宁可旧腿少一帧,不可新腿的事实
92
94
  * 写进旧腿的账)。
@@ -11,6 +11,8 @@ export const ENGINE_NOTICE_WIRE_CODES = [
11
11
  "steering.parked_input_blocked",
12
12
  "task.user_steer_undrained",
13
13
  "task.user_followup_undrained",
14
+ "memory.capture_opted_out",
15
+ "memory.capture_optout_unpersisted",
14
16
  ];
15
17
  const WIRE_CODES = new Set(ENGINE_NOTICE_WIRE_CODES);
16
18
  export function isEngineNoticeWireCode(code) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * runs 账本(`task_run_event`)的**事件型闭集**——A-032 P1-③ / codex 对抗复审 F2。
2
+ * runs 账本(`task_run_event`)的**事件型闭集**——A-032 P1-③ / codex 交叉复审 F2。
3
3
  *
4
4
  * ## 为什么需要这个联合
5
5
  * 账本行的 `type` 是消费端分流的键:`GET /v1/runs/:id/events` 裸回放照发,而 `trace/project.ts` 的
@@ -40,7 +40,7 @@ export interface LedgerSink {
40
40
  * 与 {@link onEvent} 的 `case "status"` 共用**同一个**白名单 builder(`brainStatusEventData`),差别只有
41
41
  * 一处:本门**同步入链** —— 在回调进入的那一刻就占住序号槽,而不是先 `await flush()` 再入链。
42
42
  *
43
- * 🔴 **为什么必须同步入链**(codex 对抗复审 R2 [medium],红先复现后修):`onForwardEvent` 是同步入链的,
43
+ * 🔴 **为什么必须同步入链**(codex 交叉复审 R2 [medium],红先复现后修):`onForwardEvent` 是同步入链的,
44
44
  * 而 core 的第五席与 forward sink 都是**同步回调**、可能同一拍相邻到达。走 `onEvent` 的话那一次
45
45
  * `await flush()` 会让**后到**的 forward 帧抢到**更小**的序号 ⇒ durable 时间线倒挂(消费端读到「子代
46
46
  * 进度」排在触发它的那次重连之前)。钉在 `test/issue190-status-wire-disclosure.test.ts` §4⑤b。
@@ -71,7 +71,7 @@ export interface LedgerSink {
71
71
  }): Promise<void>;
72
72
  }
73
73
  /**
74
- * **单写串行链**的可复用形(#310 codex 对抗复审 R1-[high],验真后修)—— `seq` 的**分配发生在链步内**,
74
+ * **单写串行链**的可复用形(#310 codex 交叉复审 R1-[high],验真后修)—— `seq` 的**分配发生在链步内**,
75
75
  * 于是「分配序 == 发起序 == 提交序」。
76
76
  *
77
77
  * 病(实测形,`LedgerSink` 内部早有这条纪律的内联版、其顶注 [1.211 codex H2] 逐字记着它):裸写口
@@ -4,7 +4,7 @@
4
4
  * The event log (src/runs.ts) is a flat per-task seq stream: reasoning / text / tool_start / tool_end /
5
5
  * turn_end / compacted / done / suspended / failed. sema-registry renders an ORDERED `blocks[]` transcript
6
6
  * (CC-style thinking↔tool interleave), grouped into turns. This is pure presentation/projection = the
7
- * SERVICE layer (core [R57] adversarial: do NOT push this shape into core — different consumer, different shape).
7
+ * SERVICE layer (core [R57] cross-review: do NOT push this shape into core — different consumer, different shape).
8
8
  *
9
9
  * Per core [R57] corrections: there is no `suspended` event (suspend = the run row's status); per-turn tokens ride
10
10
  * on `turn_end.usage` (core additive). As of core 1.121.0 (design/99 §E1) `tool_end` ALSO carries the model-facing
@@ -18,7 +18,7 @@ import type { RunEvent, RunRecord } from "../plugins/store-contracts.js";
18
18
  import type { ModelUsageDelta, ModelUsageTracker, PromptManifestRecord } from "../budget.js";
19
19
  import type { LedgerEventType } from "./ledger-events.js";
20
20
  /** 账本写口的统一签名 —— `type` 是**闭集**(`ledger-events.ts`):新型必须先登记,否则调用点编译红
21
- * (codex 对抗复审 F2:别名门 `ap("x")` 与动态首参让纯词法门结构性抓不全)。 */
21
+ * (codex 交叉复审 F2:别名门 `ap("x")` 与动态首参让纯词法门结构性抓不全)。 */
22
22
  type AppendFn = (type: LedgerEventType, data: unknown) => Promise<void>;
23
23
  type GetEventsFn = (taskId: string, afterSeq: number) => Promise<RunEvent[]>;
24
24
  /** E8 (shell-host): drain the per-task model-usage accumulator into an append-only `model_usage` DELTA event.
@@ -402,6 +402,7 @@ export declare function brainStatusEventData(ev: {
402
402
  maxRetries?: number;
403
403
  retryInMs?: number;
404
404
  errClass?: string;
405
+ retryAtMs?: number;
405
406
  eventId?: string;
406
407
  parentToolCallId?: string;
407
408
  }): Record<string, unknown>;
@@ -295,6 +295,7 @@ export function brainStatusEventData(ev) {
295
295
  ...(num(ev.attempt) ? { attempt: ev.attempt } : {}),
296
296
  ...(num(ev.maxRetries) ? { maxRetries: ev.maxRetries } : {}),
297
297
  ...(num(ev.retryInMs) ? { retryInMs: ev.retryInMs } : {}),
298
+ ...(num(ev.retryAtMs) ? { retryAtMs: ev.retryAtMs } : {}),
298
299
  ...(typeof ev.errClass === "string" && ev.errClass.length > 0 ? { errClass: ev.errClass } : {}),
299
300
  ...identityFields(ev),
300
301
  };
@@ -3,7 +3,7 @@
3
3
  * GET /v1/runs/:id 读面的判别材料 `msSinceLastActivity` 的唯一数据源。
4
4
  *
5
5
  * 【为什么需要它】run 行的 `updatedAt` 由 liveness heartbeat(独立 `setInterval`,runs.ts)推进——
6
- * 它只证明 **server 进程**活着;turn 卡死在某个 await 上时心跳照走([3771] 推理漏洞的公开更正)。
6
+ * 它只证明 **server 进程**活着;turn 卡死在某个 await 上时心跳照走([3771] 推理缺陷的公开更正)。
7
7
  * 「turn 真在推进」的证据 = ledger sink 的 durable append(text/tool/status/park/done 全族都过那一条
8
8
  * 单写串行链)。cli 的 running 形保守臂([3779] C3)缺的就是这一维:没有它,「真忙的后台 run」与
9
9
  * 「断连僵局」在 wire 上不可分辨,只能猜。
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * 本模块改判**宽限**:检出一次前跳后,按**单调钟**静默 `grace = slaMs` 毫秒不做判定。
19
19
  *
20
- * 🔴 **宽限量是一个完整 SLA,不是 `min(J, S)`**(codex 对抗复审 R1 [high] 三,验真后修 —— 第一版正是
20
+ * 🔴 **宽限量是一个完整 SLA,不是 `min(J, S)`**(codex 交叉复审 R1 [high] 三,验真后修 —— 第一版正是
21
21
  * `min(J, S)`,而那条「J < S 时宽限恰好补平损失」的证明**是错的**)。错在哪:宽限只是**推迟**判定,
22
22
  * 它消不掉墙钟从此**永久领先** J 毫秒这件事。反例(逐字复算):S=45s,gate 在真实 t=0 铸出(deadline =
23
23
  * W0+45s),真实 t=1s 时前跳 J=10s。`min(J,S)`=10s ⇒ 宽限到真实 t=11s;此后墙钟恒为 `W0+t+10`,
@@ -29,7 +29,7 @@
29
29
  * t_x + S(墙钟与单调钟从跳变后起同速),宽限只可能把它再往后推,绝不会提前 ✔
30
30
  * 即**任一审批席都拿得到不少于一个完整 SLA 的真实时间**;单次阶跃下判定最多被推迟一个 S。
31
31
  *
32
- * 🔴 **「有界且一次性自清」只对单次阶跃成立**(codex 对抗复审 R2 [high] 五,验真后修 —— 第一版把这句话
32
+ * 🔴 **「有界且一次性自清」只对单次阶跃成立**(codex 交叉复审 R2 [high] 五,验真后修 —— 第一版把这句话
33
33
  * 写成了无条件的,那是错的):每次前跳都把窗延到「此刻 + 一个 S」,所以**只要每个 S 内再跳一次**,窗就
34
34
  * 永远续下去,deny 腿被静默关停 —— 而它是一条 **fail-closed** 腿,永久不跑比跑早了更坏。
35
35
  * 收口 = 一串连续跳变的**累计宽限总上限**(缺省 `2 × S`:一个 S 覆盖有证明的单次阶跃,第二个 S 容一串
@@ -40,7 +40,7 @@
40
40
  * ## 方向与响亮度(#157 安全轴)
41
41
  * 宽限只会**推迟**一个 fail-closed 动作(deny),绝不会放行任何东西 —— 方向安全。但它必须**响亮**:
42
42
  * 检出即 warn(带跳变量与宽限量),宽限期内每拍 info。绝对兜底仍在:`checkpoint.terminal_at_ms`
43
- * (put 时钉死的绝对上限)由另一条 reap 腿执行,不经本守卫 ⇒ 悬挂不可能无限。
43
+ * (put 时写死的绝对上限)由另一条 reap 腿执行,不经本守卫 ⇒ 悬挂不可能无限。
44
44
  *
45
45
  * 后拨(墙钟相对单调钟**倒退**)不触发宽限:它只会让 deadline 显得更远 ⇒ 判定被推迟,已经是安全方向。
46
46
  * 仍然 warn —— 一次没人知道的钟倒退是运维事实,不是可以吞掉的噪声。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.50.0",
3
+ "version": "7.52.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -54,7 +54,7 @@
54
54
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
55
55
  },
56
56
  "dependencies": {
57
- "@sema-agent/core": "5.63.0",
57
+ "@sema-agent/core": "7.0.0",
58
58
  "@sema-agent/settings-schema": "1.0.0",
59
59
  "e2b": "^2.28.0",
60
60
  "libsodium-wrappers": "^0.8.4",