@sema-agent/server 7.142.0 → 7.143.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 (258) hide show
  1. package/MIGRATION.md +442 -0
  2. package/USAGE.md +41 -24
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +7 -5
  5. package/dist/approval-ask-audit-store.js +12 -6
  6. package/dist/approval-ask-machine.d.ts +15 -7
  7. package/dist/approval-ask-machine.js +10 -4
  8. package/dist/approval-card.d.ts +124 -25
  9. package/dist/approval-card.js +103 -23
  10. package/dist/approval-queue-kind.d.ts +27 -0
  11. package/dist/approval-queue-kind.js +6 -0
  12. package/dist/approval-reconciler.d.ts +8 -8
  13. package/dist/approval-reconciler.js +3 -3
  14. package/dist/audit.js +4 -2
  15. package/dist/boot/budget-tracing.d.ts +1 -1
  16. package/dist/boot/governance-seams.d.ts +26 -41
  17. package/dist/boot/governance-seams.js +61 -28
  18. package/dist/boot/late-binds.d.ts +7 -4
  19. package/dist/boot/late-binds.js +3 -1
  20. package/dist/boot/leader.js +2 -4
  21. package/dist/boot/memory-consolidation.d.ts +0 -8
  22. package/dist/boot/memory-consolidation.js +3 -3
  23. package/dist/boot/reapers.js +5 -4
  24. package/dist/boot/resolve-spec.d.ts +1 -1
  25. package/dist/boot/resolve-spec.js +8 -8
  26. package/dist/boot/runner-deps.d.ts +10 -7
  27. package/dist/boot/runner-deps.js +5 -17
  28. package/dist/boot/shutdown.d.ts +9 -0
  29. package/dist/boot/shutdown.js +16 -2
  30. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  31. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  32. package/dist/boot/stage-03-store-backend.js +1 -2
  33. package/dist/boot/stage-04-stores.d.ts +5 -2
  34. package/dist/boot/stage-04-stores.js +3 -5
  35. package/dist/boot/stage-05-execution-env.d.ts +5 -2
  36. package/dist/boot/stage-06-runners.d.ts +5 -2
  37. package/dist/boot/stage-06-runners.js +1 -3
  38. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  39. package/dist/boot/stage-07-capability-layer.js +5 -1
  40. package/dist/boot/stage-08-reapers.d.ts +5 -2
  41. package/dist/boot/stage-09-leader.d.ts +5 -2
  42. package/dist/boot/stage-10-http-server.d.ts +5 -2
  43. package/dist/boot/stage-10-http-server.js +9 -1
  44. package/dist/boot/stores.d.ts +5 -2
  45. package/dist/boot/stores.js +7 -7
  46. package/dist/boot/web-admin.d.ts +5 -1
  47. package/dist/boot/web-admin.js +31 -19
  48. package/dist/capabilities/hands-lane.d.ts +9 -39
  49. package/dist/capabilities/hands-lane.js +8 -13
  50. package/dist/capabilities/mcp-probe.d.ts +3 -2
  51. package/dist/capabilities/scenarios.d.ts +11 -1
  52. package/dist/capabilities/scenarios.js +1 -0
  53. package/dist/capabilities/skills.d.ts +1 -17
  54. package/dist/capabilities/skills.js +0 -8
  55. package/dist/capability-facts.d.ts +8 -8
  56. package/dist/capability-facts.js +4 -4
  57. package/dist/card-void.d.ts +6 -9
  58. package/dist/config-catalog.js +1 -1
  59. package/dist/config-dir-trust.js +58 -29
  60. package/dist/config-invariants.d.ts +7 -1
  61. package/dist/config-invariants.js +7 -0
  62. package/dist/config-provider.d.ts +10 -0
  63. package/dist/config-provider.js +15 -4
  64. package/dist/config-root-migration.d.ts +13 -0
  65. package/dist/config-root-migration.js +99 -0
  66. package/dist/config-types.d.ts +5 -5
  67. package/dist/config.d.ts +4 -3
  68. package/dist/config.js +1 -1
  69. package/dist/delegated-session-owner.d.ts +5 -3
  70. package/dist/delegated-session-owner.js +1 -1
  71. package/dist/device-ws-protocol.d.ts +1 -0
  72. package/dist/device-ws-protocol.js +2 -1
  73. package/dist/env-model-table-gate.d.ts +53 -0
  74. package/dist/env-model-table-gate.js +53 -0
  75. package/dist/fleet/fleet-bus.d.ts +28 -52
  76. package/dist/fleet/fleet-bus.js +35 -100
  77. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  78. package/dist/fleet/fleet-reconciler.js +1 -1
  79. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  80. package/dist/fleet/subagent-tail-bus.js +15 -28
  81. package/dist/hooks/hook-runner.js +28 -7
  82. package/dist/host-decision.d.ts +54 -20
  83. package/dist/host-decision.js +42 -14
  84. package/dist/http/admission.d.ts +1 -1
  85. package/dist/http/admission.js +7 -3
  86. package/dist/http/approval-sweeps.js +11 -8
  87. package/dist/http/caller-trust.d.ts +1 -1
  88. package/dist/http/durable-run-leg.js +2 -2
  89. package/dist/http/engine-refusal.d.ts +16 -0
  90. package/dist/http/engine-refusal.js +17 -0
  91. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  92. package/dist/http/memory-compliance-failure-reply.js +4 -6
  93. package/dist/http/owner-access.d.ts +11 -0
  94. package/dist/http/owner-access.js +13 -0
  95. package/dist/http/resume-legs.d.ts +21 -7
  96. package/dist/http/resume-legs.js +71 -49
  97. package/dist/http/resume-restatement.d.ts +39 -0
  98. package/dist/http/resume-restatement.js +25 -0
  99. package/dist/http/route-ctx.d.ts +35 -10
  100. package/dist/http/route-ctx.js +1 -1
  101. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  102. package/dist/http/routes/approvals-assistant.js +142 -31
  103. package/dist/http/routes/capabilities.js +11 -2
  104. package/dist/http/routes/memory-policy.d.ts +2 -2
  105. package/dist/http/routes/memory-policy.js +1 -1
  106. package/dist/http/routes/notify-wake.js +23 -25
  107. package/dist/http/routes/rules.js +48 -13
  108. package/dist/http/routes/runs.js +9 -8
  109. package/dist/http/routes/session-memory-sync.d.ts +34 -6
  110. package/dist/http/routes/session-memory-sync.js +27 -10
  111. package/dist/http/routes/session-sync.js +13 -20
  112. package/dist/http/routes/sessions.d.ts +2 -2
  113. package/dist/http/routes/sessions.js +22 -23
  114. package/dist/http/routes/tasks.js +27 -49
  115. package/dist/http/server.d.ts +13 -7
  116. package/dist/http/server.js +2 -2
  117. package/dist/http/stored-body-replay.js +15 -4
  118. package/dist/http/wire-types.d.ts +17 -7
  119. package/dist/leader/leader.js +5 -2
  120. package/dist/leader/wire.d.ts +7 -1
  121. package/dist/leader/wire.js +6 -5
  122. package/dist/leg-facts.d.ts +37 -0
  123. package/dist/leg-facts.js +15 -0
  124. package/dist/memory-bundle-engine.d.ts +1 -4
  125. package/dist/memory-bundle-engine.js +2 -2
  126. package/dist/memory-operator-faces.d.ts +30 -66
  127. package/dist/memory-operator-faces.js +17 -88
  128. package/dist/memory-scope-stores.d.ts +38 -40
  129. package/dist/memory-scope-stores.js +51 -36
  130. package/dist/memory-scope.d.ts +0 -9
  131. package/dist/memory-scope.js +0 -9
  132. package/dist/memory-sync-client.js +14 -3
  133. package/dist/memory-sync.d.ts +20 -4
  134. package/dist/memory-sync.js +23 -4
  135. package/dist/model-plane-face.d.ts +13 -7
  136. package/dist/model-plane-face.js +2 -1
  137. package/dist/observability/fail-open.d.ts +20 -4
  138. package/dist/observability/fail-open.js +17 -4
  139. package/dist/observability/run-terminal.js +4 -0
  140. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  141. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  142. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  143. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  144. package/dist/orchestration/workflow-park-decision.js +5 -3
  145. package/dist/org-delivery-gate.d.ts +11 -0
  146. package/dist/org-delivery-gate.js +19 -1
  147. package/dist/parked-decide.d.ts +13 -9
  148. package/dist/parked-decide.js +7 -7
  149. package/dist/parked-revive-surface.js +1 -1
  150. package/dist/peer-inbound-face.d.ts +127 -0
  151. package/dist/peer-inbound-face.js +158 -0
  152. package/dist/permission-rule-list.d.ts +13 -9
  153. package/dist/permission-rule-list.js +7 -3
  154. package/dist/plugins/approval-ask-store-file.js +18 -6
  155. package/dist/plugins/approval-ask-store-memory.js +8 -6
  156. package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
  157. package/dist/plugins/approval-ask-store-sql.js +30 -11
  158. package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
  159. package/dist/plugins/checkpoint-store-sql.js +9 -7
  160. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  161. package/dist/plugins/file-history-store-sql.js +24 -19
  162. package/dist/plugins/file-run-store.d.ts +29 -0
  163. package/dist/plugins/file-run-store.js +97 -8
  164. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  165. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  166. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  167. package/dist/plugins/local-checkpoint-store.js +2 -2
  168. package/dist/plugins/local-session-store.d.ts +6 -2
  169. package/dist/plugins/local-session-store.js +8 -2
  170. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  171. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  172. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  173. package/dist/plugins/mailbox-store-sql.js +76 -7
  174. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  175. package/dist/plugins/memory-sync-store-pg.js +21 -0
  176. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  177. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  178. package/dist/plugins/one-time-migrations.d.ts +32 -2
  179. package/dist/plugins/one-time-migrations.js +106 -3
  180. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  181. package/dist/plugins/org-delivery-quota-store.js +2 -12
  182. package/dist/plugins/park-commit-watch.d.ts +1 -1
  183. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  184. package/dist/plugins/permission-rule-store-file.js +15 -2
  185. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  186. package/dist/plugins/permission-rule-store-sql.js +35 -2
  187. package/dist/plugins/remote-env-device.d.ts +8 -11
  188. package/dist/plugins/remote-env-device.js +6 -3
  189. package/dist/plugins/remote-env-k8s.d.ts +37 -24
  190. package/dist/plugins/remote-env-k8s.js +50 -17
  191. package/dist/plugins/retention-store-sql.d.ts +2 -2
  192. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  193. package/dist/plugins/sandbox-read-floor.js +2 -7
  194. package/dist/plugins/sql-errors.d.ts +10 -1
  195. package/dist/plugins/sql-errors.js +13 -0
  196. package/dist/plugins/store-backend.d.ts +23 -28
  197. package/dist/plugins/store-backend.js +9 -1
  198. package/dist/plugins/store-contracts.d.ts +29 -5
  199. package/dist/plugins/store-contracts.js +4 -1
  200. package/dist/plugins/task-attachment-store.d.ts +2 -2
  201. package/dist/plugins/task-attachment-store.js +37 -52
  202. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  203. package/dist/plugins/tool-result-store-sql.js +23 -2
  204. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  205. package/dist/plugins/wiring-section-codec.js +55 -0
  206. package/dist/plugins/workspace-fill.d.ts +1 -1
  207. package/dist/rule-entry-list.d.ts +3 -2
  208. package/dist/rule-entry-list.js +5 -1
  209. package/dist/rules-consent.d.ts +28 -5
  210. package/dist/rules-consent.js +40 -28
  211. package/dist/run-local.js +9 -5
  212. package/dist/runs.d.ts +12 -8
  213. package/dist/runs.js +6 -7
  214. package/dist/runtime-governance.d.ts +1 -1
  215. package/dist/server-secret-env.d.ts +4 -0
  216. package/dist/session-error-wire.d.ts +12 -0
  217. package/dist/session-error-wire.js +6 -0
  218. package/dist/session-incarnation.d.ts +1 -1
  219. package/dist/session-notify-door.d.ts +106 -0
  220. package/dist/session-notify-door.js +93 -0
  221. package/dist/session-sync.d.ts +9 -14
  222. package/dist/session-sync.js +5 -5
  223. package/dist/session-workspace.d.ts +3 -13
  224. package/dist/session-workspace.js +0 -9
  225. package/dist/standing-answer-grant.d.ts +5 -0
  226. package/dist/standing-answer-grant.js +10 -0
  227. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  228. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  229. package/dist/task-mcp.d.ts +1 -1
  230. package/dist/task-settings.d.ts +11 -8
  231. package/dist/task-settings.js +31 -11
  232. package/dist/tool-approval.d.ts +52 -34
  233. package/dist/tool-approval.js +66 -57
  234. package/dist/trace/core-keyset-guard.d.ts +11 -8
  235. package/dist/trace/engine-notice-wire.d.ts +1 -1
  236. package/dist/trace/engine-notice-wire.js +3 -1
  237. package/dist/trace/exit-seat.d.ts +7 -1
  238. package/dist/trace/exit-seat.js +1 -0
  239. package/dist/trace/ledger-sink.d.ts +2 -2
  240. package/dist/trace/model-segment.d.ts +55 -25
  241. package/dist/trace/model-segment.js +101 -33
  242. package/dist/trace/project.d.ts +7 -4
  243. package/dist/trace/project.js +6 -0
  244. package/dist/trace/redact.d.ts +61 -9
  245. package/dist/trace/redact.js +1490 -468
  246. package/dist/trace/self-minted-wire.d.ts +510 -38
  247. package/dist/trace/self-minted-wire.js +165 -17
  248. package/dist/trace/wire-projection-faces.d.ts +1 -1
  249. package/dist/trace/wire-projection-faces.js +1 -1
  250. package/dist/verifier-pause-card.d.ts +33 -5
  251. package/dist/verifier-pause-card.js +15 -10
  252. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  253. package/dist/web-admin-client/apply-effective.js +90 -10
  254. package/dist/workspace-space.d.ts +19 -3
  255. package/dist/workspace-space.js +8 -4
  256. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  257. package/package.json +3 -2
  258. package/wire-changes/server@7.143.0.json +1167 -0
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import type { IncomingMessage } from "node:http";
15
15
  import { CheckpointError, type QuestionAnswer, type CheckpointToken } from "@sema-agent/core";
16
- import type { DecideBinding } from "./wire-types.js";
16
+ import type { DecideBinding, DecidedAction } from "./wire-types.js";
17
17
  import type { CheckpointStoreFull } from "../plugins/store-backend.js";
18
18
  import type { HostDecision } from "../host-decision.js";
19
19
  import { type GateSighting } from "../approval-gate-currency.js";
@@ -23,6 +23,7 @@ import type { RunRegistry, RouteHelpers, RouteLegs } from "./route-ctx.js";
23
23
  import type { createRequestGuards } from "./request-guards.js";
24
24
  import type { FlatServiceDeps } from "./server.js";
25
25
  import { type ResumeRefusalAnswer } from "./checkpoint-ctx.js";
26
+ import { type ResumeRestatement } from "./resume-restatement.js";
26
27
  /**
27
28
  * 🔴 **一次 `CheckpointError` 拒的处置 —— 唯一读口**:{@link CHECKPOINT_ERROR_DISPOSITION} 的读法(doctrine 在那张表头)。resume 腿的 catch 臂只调它一次,
28
29
  * 行的落点与 wire 的 `retriable` 都读这一个结果。纯函数,钉直接调。运行期遇到表外的码(装树 core 比本二进制多一个码的版本偏斜 —— 型面上拼不出来)
@@ -35,18 +36,30 @@ export declare function checkpointRefusalAnswer(e: CheckpointError, at: {
35
36
  readonly taskId?: string;
36
37
  readonly versionKeys: Readonly<Record<string, string>>;
37
38
  }): ResumeRefusalAnswer;
39
+ /**
40
+ * 一个答案只属于它被展示的那一个动作 —— 决断绑定的**唯一判官**:决断方点名它看到的那一对(`boundCallId` + `boundInputHash`,
41
+ * 队列行上展示的原值),两位都是非空串才成一只 {@link DecidedAction};缺哪位就点名哪位(序 = 体键序)。
42
+ * 不从当前停驻行补:补上的是此刻停在门上的动作,不是决断方看到的那一个(7.142.x 及以前的旧形 —— 同一调用 id 换了入参的新停驻会接住旧决定)。
43
+ */
44
+ export declare function decidedActionOf(binding: Pick<DecideBinding, "boundCallId" | "boundInputHash">): {
45
+ readonly action: DecidedAction;
46
+ readonly missingKeys?: undefined;
47
+ } | {
48
+ readonly action?: undefined;
49
+ readonly missingKeys: readonly ("boundCallId" | "boundInputHash")[];
50
+ };
38
51
  export declare function createResumeLegs(ctx: {
39
52
  deps: FlatServiceDeps;
40
53
  runnerFor: FlatServiceDeps["runnerFor"];
41
54
  } & RunRegistry & RouteHelpers & Pick<RouteLegs, "prepareSpec" | "finalizeTaskResult"> & Pick<ReturnType<typeof createRequestGuards>, "ownerCostQuotaDenied" | "ownerFleetLeaseDenied" | "usageWindowDenialFor">): {
42
55
  routeCtxBase: RouteCtxBase;
43
- resumeCheckpoint: (sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding, onResumeCommitted?: (grant: {
56
+ resumeCheckpoint: (sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, answer: QuestionAnswer | undefined, binding: DecideBinding, onResumeCommitted?: (grant: {
44
57
  overrideSessionId?: string;
45
58
  rememberableToolName: string | null;
46
59
  }) => Promise<void>, acceptEarly?: boolean, decider?: {
47
60
  principal?: string;
48
61
  explicitOperator: boolean;
49
- }) => Promise<ResumeLegOutcome>;
62
+ }, restatement?: ResumeRestatement) => Promise<ResumeLegOutcome>;
50
63
  driveResumeIntoRunLog: (args: DriveResumeArgs) => Promise<ResumeLegOutcome>;
51
64
  driveResumeLeg: (args: DriveResumeArgs, onAccepted?: (receipt: {
52
65
  status: number;
@@ -59,9 +72,9 @@ export declare function createResumeLegs(ctx: {
59
72
  resumePreempted: (sessionId: string, req: IncomingMessage | undefined, acceptEarly?: boolean, decider?: {
60
73
  principal?: string;
61
74
  explicitOperator: boolean;
62
- }) => Promise<ResumeLegOutcome>;
63
- startFreshHostTurn: (sessionId: string, objective: string, principal: string | undefined, req: IncomingMessage, workflowDecision: WorkflowDecisionDelivery, runId: string) => Promise<ResumeLegOutcome>;
64
- resumeWake: (sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined, acceptEarly?: boolean, workflowDecision?: WorkflowDecisionDelivery, ownerLeaseAdmittedFor?: string) => Promise<ResumeLegOutcome>;
75
+ }, restatement?: ResumeRestatement) => Promise<ResumeLegOutcome>;
76
+ startFreshHostTurn: (sessionId: string, objective: string, principal: string | undefined, req: IncomingMessage, workflowDecision: WorkflowDecisionDelivery, runId: string, restatement?: ResumeRestatement) => Promise<ResumeLegOutcome>;
77
+ resumeWake: (sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined, acceptEarly?: boolean, workflowDecision?: WorkflowDecisionDelivery, ownerLeaseAdmittedFor?: string, restatement?: ResumeRestatement) => Promise<ResumeLegOutcome>;
65
78
  preparePlanReview: (sessionId: string, log?: {
66
79
  taskId?: string;
67
80
  principalPresent?: boolean;
@@ -78,6 +91,7 @@ export declare function createResumeLegs(ctx: {
78
91
  readonly ok: true;
79
92
  readonly prepared: PreparedPlanReview;
80
93
  }>;
81
- drivePlanReview: (prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, acceptEarly?: boolean, permissionModeAfter?: PlanApprovePermissionModeAfter) => Promise<ResumeLegOutcome>;
94
+ drivePlanReview: (prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, acceptEarly?: boolean, permissionModeAfter?: PlanApprovePermissionModeAfter, restatement?: ResumeRestatement) => Promise<ResumeLegOutcome>;
95
+ sessionNotifyDoor: import("../session-notify-door.js").SessionNotifyDoor;
82
96
  };
83
97
  //# sourceMappingURL=resume-legs.d.ts.map
@@ -4,7 +4,7 @@ import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, t
4
4
  import { decideParkedAgent, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, heldWorkflowParkDecisions, planWorkflowParkedDecide } from "../parked-decide.js";
5
5
  import { parkDecisionFingerprint } from "../orchestration/workflow-park-decision.js";
6
6
  import { headReadOf } from "../session-incarnation.js";
7
- import { createSessionWorkspaceRecorder, legSessionWitnessOf, sessionWorkspaceStoreOf } from "../session-workspace.js";
7
+ import { createSessionWorkspaceRecorder, sessionWorkspaceStoreOf } from "../session-workspace.js";
8
8
  const DELIVERY_NOT_RECORDED = "workflow park decide: this delivery no longer holds the host session (its claim was released) — the decision was not recorded and this delivery was not started";
9
9
  import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
10
10
  import { graftParkToolCallId, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort, voidHeldParkCard } from "../runs.js";
@@ -15,7 +15,7 @@ import { isTerminalRunStatus } from "../plugins/store-contracts.js";
15
15
  import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
16
16
  import { recordResumeResourceWindow } from "../resource-window.js";
17
17
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
18
- import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../fleet/fleet-bus.js";
18
+ import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../fleet/fleet-bus.js";
19
19
  import { defaultSubagentTailBus } from "../fleet/subagent-tail-bus.js";
20
20
  import { currentApprovalGateToken, currentGateFacesOf, sightsCurrentGate } from "../approval-gate-currency.js";
21
21
  import { approverFacingArgs, createApprovalCardEmitter, declaredApprovalFrameFamilies, parkRowStandingAnswerFacts, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerDurablyRecordable } from "../tool-approval.js";
@@ -25,7 +25,7 @@ import { serviceVersion } from "../service-version.js";
25
25
  import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
26
26
  import { withPrincipal } from "../observability/principal-context.js";
27
27
  import { withModelReachScope } from "../model-reach-scope.js";
28
- import { runSkillsOf } from "../capabilities/skills.js";
28
+ import { legFactsOf } from "../leg-facts.js";
29
29
  import { createInstructionSelectionLeg } from "../instruction-selection.js";
30
30
  import { modelTextEventData, appendParkRow, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, compactionStartedEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
31
31
  import { redactSecrets } from "../trace/redact.js";
@@ -57,9 +57,11 @@ import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnsealableAnswer } from
57
57
  import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, resumeRefusalWithCard, sealCheckpointCtx, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
58
58
  import { refusalBody, resumeRefusalFacts } from "./refusal-body.js";
59
59
  import { storedBodyReplayRefusal } from "./stored-body-replay.js";
60
+ import { restateResumeCtx } from "./resume-restatement.js";
60
61
  import { httpErrorRefusal } from "./send.js";
61
62
  import { createRunAcceptance } from "../run-acceptance.js";
62
63
  import { fieldConflictFacts } from "../field-conflict.js";
64
+ import { createSessionNotifyDoor } from "../session-notify-door.js";
63
65
  function legMayHaveReopenedCheckpoint(r) {
64
66
  return r.terminal.kind === "failed" && nestedPauseOf(r.terminal) === undefined;
65
67
  }
@@ -107,6 +109,8 @@ const CHECKPOINT_ERROR_DISPOSITION = {
107
109
  "checkpoint.reopened_concurrently": "retriable",
108
110
  "resume.parent_constraint_missing": "retriable",
109
111
  "resume.parent_constraint_mismatch": "retriable",
112
+ "resume.composition_mismatch": "retriable",
113
+ "checkpoint.void_unpin_failed": "terminal",
110
114
  "resume.constraint_rejected": "retriable",
111
115
  "resume.constraint_unprojectable": "retriable",
112
116
  "resume.usage_window_exhausted": "retriable",
@@ -230,14 +234,19 @@ export function checkpointRefusalAnswer(e, at) {
230
234
  }),
231
235
  }, at.retriable);
232
236
  }
237
+ export function decidedActionOf(binding) {
238
+ const { boundCallId, boundInputHash } = binding;
239
+ const missingKeys = [...(typeof boundCallId === "string" && boundCallId.length > 0 ? [] : ["boundCallId"]), ...(typeof boundInputHash === "string" && boundInputHash.length > 0 ? [] : ["boundInputHash"])];
240
+ return missingKeys.length === 0 ? { action: { boundCallId: boundCallId, boundInputHash: boundInputHash } } : { missingKeys };
241
+ }
233
242
  export function createResumeLegs(ctx) {
234
243
  const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, runOwnerOk, sessionOwnerOk, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
235
244
  const ctxOpening = (sessionId) => ({ sessionId, opener: deps.checkpointCtxSealing });
236
245
  const resumeFleetScope = (req, auth) => req ? (gatedPrincipal(req, deps.config) ?? "default") : (auth.principal ?? "default");
237
- async function resumeCheckpoint(sessionId, decision, reason, hostDecision, req, answer, binding, onResumeCommitted, acceptEarly, decider) {
246
+ async function resumeCheckpoint(sessionId, decision, reason, hostDecision, req, answer, binding, onResumeCommitted, acceptEarly, decider, restatement) {
238
247
  const cs = deps.checkpointStore;
239
248
  const token = await currentApprovalGateToken(cs, sessionId);
240
- if (binding?.checkpointToken && !sightsCurrentGate({ face: "checkpointToken", value: binding.checkpointToken }, token ? { checkpointToken: token } : null)) {
249
+ if (binding.checkpointToken && !sightsCurrentGate({ face: "checkpointToken", value: binding.checkpointToken }, token ? { checkpointToken: token } : null)) {
241
250
  let seenUnreadable = false;
242
251
  const seen = await cs.get(binding.checkpointToken).catch((e) => {
243
252
  seenUnreadable = true;
@@ -326,20 +335,23 @@ export function createResumeLegs(ctx) {
326
335
  }),
327
336
  };
328
337
  }
329
- if (req !== undefined && !binding?.checkpointToken && !binding?.boundCallId) {
338
+ const decided = decidedActionOf(binding);
339
+ if (decided.action === undefined) {
330
340
  return {
331
341
  status: 400,
332
342
  body: refusalBody({
333
343
  errorCode: "decide_requires_binding",
334
- message: "decide requires the decision-action binding — echo checkpointToken and/or boundCallId from the pending approval (GET /v1/approvals)",
344
+ message: "decide requires the decision-action binding — echo boundCallId and boundInputHash exactly as the pending approval you are deciding shows them (GET /v1/approvals), so a decision on a view that has since been replaced is refused instead of landing on the gate that replaced it",
335
345
  facts: {
336
346
  bindingEnforced: true,
347
+ missingKeys: decided.missingKeys,
337
348
  },
338
349
  }),
339
350
  };
340
351
  }
352
+ const action = decided.action;
341
353
  const s57RowCallId = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.toolCallId : undefined;
342
- if (binding?.boundCallId !== undefined && s57RowCallId && binding.boundCallId !== s57RowCallId) {
354
+ if (s57RowCallId && action.boundCallId !== s57RowCallId) {
343
355
  let s57CurrentPending;
344
356
  if (decider !== undefined && cp.status === "pending" && cp.sessionId === sessionId && cp.pendingAction.kind === "tool_approval") {
345
357
  s57CurrentPending = {
@@ -400,8 +412,9 @@ export function createResumeLegs(ctx) {
400
412
  const parkedReq = {
401
413
  token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
402
414
  decision, hostDecision, ...(reason !== undefined ? { reason } : {}),
403
- ...(binding !== undefined ? { binding: (({ checkpointToken: _staleGuardOnly, ...rest }) => rest)(binding) } : {}),
415
+ binding: { ...(({ checkpointToken: _staleGuardOnly, ...rest }) => rest)(binding), ...action },
404
416
  ...(answer !== undefined ? { answer } : {}),
417
+ ...(restatement !== undefined ? { restatement } : {}),
405
418
  ...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
406
419
  };
407
420
  if (origin?.kind === "background" && parkedAgentStore !== undefined && parkedSurface !== undefined) {
@@ -458,7 +471,7 @@ export function createResumeLegs(ctx) {
458
471
  if (decisions === undefined || runStore === undefined) {
459
472
  return { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) };
460
473
  }
461
- const fingerprint = parkDecisionFingerprint(plan.outcome);
474
+ const fingerprint = parkDecisionFingerprint(plan.outcome, restatement);
462
475
  const liveReceiptOf = async (held) => {
463
476
  if (held === null || held.fingerprint !== fingerprint)
464
477
  return undefined;
@@ -488,8 +501,8 @@ export function createResumeLegs(ctx) {
488
501
  .catch(() => undefined);
489
502
  const admittedPayer = decodeCheckpointScope(cp.scope);
490
503
  const out = hostToken
491
- ? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, delivery, admittedPayer)
492
- : await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, delivery, plan.runId);
504
+ ? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, delivery, admittedPayer, restatement)
505
+ : await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, delivery, plan.runId, restatement);
493
506
  if (recordFailure !== undefined)
494
507
  throw recordFailure.err;
495
508
  if (acceptedTaskIdOf(out) === undefined)
@@ -502,9 +515,10 @@ export function createResumeLegs(ctx) {
502
515
  return resumeCtxCorruptAnswer(ctxRead0.where);
503
516
  if (ctxRead0.kind === "unsealable")
504
517
  return resumeCtxUnsealableAnswer(ctxRead0);
505
- const ctx = ctxRead0.kind === "ok" ? ctxRead0.ctx : null;
506
- if (!ctx)
518
+ const storedCtx = ctxRead0.kind === "ok" ? ctxRead0.ctx : null;
519
+ if (!storedCtx)
507
520
  return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
521
+ const ctx = restateResumeCtx(storedCtx, restatement);
508
522
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
509
523
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
510
524
  if (!resolved.ok)
@@ -520,21 +534,19 @@ export function createResumeLegs(ctx) {
520
534
  }
521
535
  const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
522
536
  const pa = cp.pendingAction;
523
- const persistedCallId = pa.kind === "tool_approval" ? pa.toolCallId : "";
524
- const persistedHash = pa.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
525
537
  const outcome = {
526
538
  gate: "policy_ask",
527
- boundCallId: binding?.boundCallId ?? persistedCallId,
528
- boundInputHash: binding?.boundInputHash ?? persistedHash,
539
+ boundCallId: action.boundCallId,
540
+ boundInputHash: action.boundInputHash,
529
541
  decision: decision === "approve" ? "allow" : "deny",
530
- ...(decision === "approve" && !isEngineQuestionRow(pa) && binding?.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
542
+ ...(decision === "approve" && !isEngineQuestionRow(pa) && binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
531
543
  ...(reason ? { reason } : {}),
532
- ...(binding?.feedback !== undefined ? { feedback: binding.feedback } : {}),
544
+ ...(binding.feedback !== undefined ? { feedback: binding.feedback } : {}),
533
545
  ...(answer !== undefined ? { answer } : {}),
534
546
  hostDecision,
535
547
  };
536
548
  const verifyRounds = verifyRoundsFromBody(ctx.body);
537
- const driven = await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, commitment: { kind: "gate_resolved", ...captureSelectedGate() }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(boundGrant ? { onResumeCommitted: boundGrant } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}), ...(ownerLeaseAdmittedHere ? { ownerLeaseAdmitted: true } : {}) });
549
+ const driven = await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: resolved.runner, legFacts: resolved.legFacts, token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, commitment: { kind: "gate_resolved", ...captureSelectedGate() }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(boundGrant ? { onResumeCommitted: boundGrant } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}), ...(ownerLeaseAdmittedHere ? { ownerLeaseAdmitted: true } : {}) });
538
550
  noteLateSettleIfAccepted(driven);
539
551
  return driven;
540
552
  }
@@ -547,14 +559,17 @@ export function createResumeLegs(ctx) {
547
559
  const resolveResumeSpec = async (ctx, req, auth, lease) => {
548
560
  const engineNotices = [];
549
561
  const execution = await resolveExecutionContext(deps, { kind: "continuation", root: auth.sessionId, principal: auth.principal });
550
- const spec = stampSpecRoot(await deps.resolveSpec({ ...ctx.body, ...ONE_SHOT_SUBMIT_STRIP }, req, auth, {
562
+ const resolved = await deps.resolveSpec({ ...ctx.body, ...ONE_SHOT_SUBMIT_STRIP }, req, auth, {
551
563
  leg: "resume",
552
564
  onNotice: (n) => engineNotices.push(n),
553
565
  ...(ctx.unattendedLeg === true ? { unattendedLeg: true } : {}),
554
566
  lease,
555
567
  execution,
556
- }), execution);
557
- return { spec, engineNotices, execution };
568
+ });
569
+ const legFacts = legFactsOf(resolved);
570
+ const runner = runnerFor(resolved);
571
+ const spec = stampSpecRoot(resolved, execution);
572
+ return { spec, runner, legFacts, engineNotices, execution };
558
573
  };
559
574
  const refuseStoredBodyReplay = (sessionId, refusal) => {
560
575
  deps.logger?.warn?.("resume_refused_stored_body", { sessionId, ...storedBodyReplayLogFields(refusal) });
@@ -566,8 +581,8 @@ export function createResumeLegs(ctx) {
566
581
  return { ok: false, answer: refuseStoredBodyReplay(auth.sessionId, replay) };
567
582
  const lease = deps.peerDesk.lease("resume");
568
583
  try {
569
- const { spec, engineNotices, execution } = await resolveResumeSpec(ctx, req, auth, lease);
570
- return { ok: true, spec, engineNotices, execution, peerLease: lease };
584
+ const { spec, runner, legFacts, engineNotices, execution } = await resolveResumeSpec(ctx, req, auth, lease);
585
+ return { ok: true, spec, runner, legFacts, engineNotices, execution, peerLease: lease };
571
586
  }
572
587
  catch (e) {
573
588
  lease.release();
@@ -603,7 +618,7 @@ export function createResumeLegs(ctx) {
603
618
  const reparkStatus = outcome.gate === "plan_review" ? "needs_review" : "suspended";
604
619
  const commitResumeCtx = () => Promise.resolve().then(() => deps.checkpointStore.putCtx(resumeCtx.sessionId, sealCheckpointCtx({ ...resumeCtx.ctx, writerVersion: serviceVersion() }, deps.checkpointCtxSealing, resumeCtx.sessionId))).catch((e) => {
605
620
  deps.metrics?.inc("resume_ctx_not_persisted_total");
606
- deps.logger?.warn?.("resume_ctx_not_persisted", { sessionId: resumeCtx.sessionId, gate: outcome.gate, error: errorText(e), note: "this continuation runs with the body it was given; the durable rebuild input still holds the PREVIOUS body, so the NEXT continuation of this session rebuilds from that one (never wider than this leg's)" });
621
+ deps.logger?.warn?.("resume_ctx_not_persisted", { sessionId: resumeCtx.sessionId, gate: outcome.gate, error: errorText(e), note: "this continuation runs with the body it was given; the durable rebuild input still holds the PREVIOUS body, so the NEXT continuation of this session rebuilds from that one — its permission mode is never wider than this leg's, but if this leg restated its MCP / A2A peer tables (mcpServers / a2aPeers on the resume request), a later continuation that does not restate them runs with the previous tables again" });
607
622
  });
608
623
  const legKey = createHash("sha256").update(String(token)).digest("hex");
609
624
  const streamApprovalOn = resolveStreamApprovalGate({
@@ -777,7 +792,7 @@ export function createResumeLegs(ctx) {
777
792
  let unregisterEngineNotice;
778
793
  try {
779
794
  const instructionLeg = createInstructionSelectionLeg();
780
- const legWiringSections = () => ({ instructions: instructionLeg.segment(), skills: runSkillsOf(taskConfig) });
795
+ const legWiringSections = () => ({ instructions: instructionLeg.segment(), skills: args.legFacts?.skills });
781
796
  result = await instructionLeg.run(sessionId, () => withModelReachScope({ sessionId, taskId }, () => withPrincipal(principal, async () => {
782
797
  const resumeNotifiedKeys = new NotifiedKeys();
783
798
  if (deps.runStore && taskId) {
@@ -832,9 +847,8 @@ export function createResumeLegs(ctx) {
832
847
  if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
833
848
  defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
834
849
  }
835
- const hadRow = isFleetAgentTerminalNotification(n) ? (fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false) : false;
836
850
  const parked = !resumeLegLive && Boolean(deps.workflowCompletionInbox && sessionId);
837
- deps.logger?.info?.("task_notification_observed", { route: "resume-verify", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive: resumeLegLive, parkedDurable: parked });
851
+ deps.logger?.info?.("task_notification_observed", { route: "resume-verify", taskId: n.task_id, taskType: n.task_type, status: n.status, legLive: resumeLegLive, parkedDurable: parked });
838
852
  if (parked) {
839
853
  void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(sessionId, principal ?? null, n, Date.now(), taskId, opts?.priority)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "resume-verify", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
840
854
  }
@@ -856,7 +870,7 @@ export function createResumeLegs(ctx) {
856
870
  resumeLegLive = true;
857
871
  const vrEngine = await resumeWithVerification(legRunner, token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, resumeObjective, verifyRounds, verifyInternals);
858
872
  const vrCause = cancelCtrl.cause();
859
- await voidVerifierPauseCard(vrEngine, deps.checkpointStore);
873
+ await voidVerifierPauseCard(vrEngine, legRunner);
860
874
  const vrJudged = atHostBoundary(vrEngine, sessionId);
861
875
  if (vrJudged.terminal.kind === "paused")
862
876
  heldPark = vrJudged;
@@ -939,7 +953,7 @@ export function createResumeLegs(ctx) {
939
953
  const driveResume = async () => {
940
954
  resumeLegLive = true;
941
955
  const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
942
- const resumeWorkspace = createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), legSessionWitnessOf(resumeTaskConfig));
956
+ const resumeWorkspace = createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), args.legFacts?.sessionWitness);
943
957
  const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
944
958
  ...launchInternals,
945
959
  onForwardEvent: (e) => {
@@ -976,9 +990,8 @@ export function createResumeLegs(ctx) {
976
990
  if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
977
991
  defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
978
992
  }
979
- const hadRow = isFleetAgentTerminalNotification(n) ? (fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false) : false;
980
993
  const parked = !resumeLegLive && Boolean(deps.workflowCompletionInbox && sessionId);
981
- deps.logger?.info?.("task_notification_observed", { route: "resume", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive: resumeLegLive, parkedDurable: parked });
994
+ deps.logger?.info?.("task_notification_observed", { route: "resume", taskId: n.task_id, taskType: n.task_type, status: n.status, legLive: resumeLegLive, parkedDurable: parked });
982
995
  if (parked) {
983
996
  void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(sessionId, principal ?? null, n, Date.now(), taskId, opts?.priority)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "resume", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
984
997
  }
@@ -1307,7 +1320,7 @@ export function createResumeLegs(ctx) {
1307
1320
  const owner = decodeCheckpointScope(scope ?? CHECKPOINT_PUBLIC_SCOPE);
1308
1321
  return owner === undefined || owner === decider.principal;
1309
1322
  }
1310
- async function resumePreempted(sessionId, req, acceptEarly, decider) {
1323
+ async function resumePreempted(sessionId, req, acceptEarly, decider, restatement) {
1311
1324
  const cs = deps.checkpointStore;
1312
1325
  const RESUMABLE_GATE_KIND = "resource_limit";
1313
1326
  const preemptBinding = { gateKinds: [RESUMABLE_GATE_KIND] };
@@ -1357,7 +1370,7 @@ export function createResumeLegs(ctx) {
1357
1370
  return resumeCtxUnsealableAnswer(ctxRead);
1358
1371
  if (ctxRead.kind === "absent")
1359
1372
  return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
1360
- const ctx = ctxRead.ctx;
1373
+ const ctx = restateResumeCtx(ctxRead.ctx, restatement);
1361
1374
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1362
1375
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1363
1376
  if (!resolved.ok)
@@ -1367,13 +1380,13 @@ export function createResumeLegs(ctx) {
1367
1380
  const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1368
1381
  const verifyRounds = verifyRoundsFromBody(ctx.body);
1369
1382
  const selectedGate = durableRowIdentity("decision-receipt", cp.checkpointId, token);
1370
- return await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, commitment: { kind: "gate_resolved", ...selectedGate }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1383
+ return await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: resolved.runner, legFacts: resolved.legFacts, token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, commitment: { kind: "gate_resolved", ...selectedGate }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1371
1384
  }
1372
1385
  finally {
1373
1386
  resolved.peerLease.release();
1374
1387
  }
1375
1388
  }
1376
- async function startFreshHostTurn(sessionId, objective, principal, req, workflowDecision, runId) {
1389
+ async function startFreshHostTurn(sessionId, objective, principal, req, workflowDecision, runId, restatement) {
1377
1390
  const cs = deps.checkpointStore;
1378
1391
  if (!deps.runStore) {
1379
1392
  return { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) };
@@ -1383,8 +1396,8 @@ export function createResumeLegs(ctx) {
1383
1396
  return resumeCtxCorruptAnswer(ctxRead.where);
1384
1397
  if (ctxRead.kind === "unsealable")
1385
1398
  return resumeCtxUnsealableAnswer(ctxRead);
1386
- const ctx = ctxRead.kind === "ok" ? ctxRead.ctx : null;
1387
- if (!ctx) {
1399
+ const storedCtx = ctxRead.kind === "ok" ? ctxRead.ctx : null;
1400
+ if (!storedCtx) {
1388
1401
  return {
1389
1402
  status: 409,
1390
1403
  body: refusalBody({
@@ -1397,6 +1410,7 @@ export function createResumeLegs(ctx) {
1397
1410
  }),
1398
1411
  };
1399
1412
  }
1413
+ const ctx = restateResumeCtx(storedCtx, restatement);
1400
1414
  const windowDenial = await usageWindowDenialFor(principal || GLOBAL_USAGE_KEY);
1401
1415
  if (windowDenial)
1402
1416
  return { status: windowDenial.status, body: windowDenial.body };
@@ -1408,7 +1422,7 @@ export function createResumeLegs(ctx) {
1408
1422
  const { spec, engineNotices } = resolved;
1409
1423
  const hostSpec = { ...spec, objective };
1410
1424
  const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
1411
- prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, acceptance: createRunAcceptance({}, { objective }), peerLease: resolved.peerLease, execution: resolved.execution, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
1425
+ prepared: { spec: hostSpec, runner: resolved.runner, legFacts: resolved.legFacts, auth, body: ctx.body, acceptance: createRunAcceptance({}, { objective }), peerLease: resolved.peerLease, execution: resolved.execution, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
1412
1426
  fleetScope: resumeFleetScope(req, auth),
1413
1427
  elicitOwner: principal ?? null,
1414
1428
  source: "workflow_parked_decide",
@@ -1427,7 +1441,7 @@ export function createResumeLegs(ctx) {
1427
1441
  resolved.peerLease.release();
1428
1442
  }
1429
1443
  }
1430
- async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowDecision, ownerLeaseAdmittedFor) {
1444
+ async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowDecision, ownerLeaseAdmittedFor, restatement) {
1431
1445
  const cs = deps.checkpointStore;
1432
1446
  const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
1433
1447
  const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
@@ -1484,7 +1498,7 @@ export function createResumeLegs(ctx) {
1484
1498
  return resumeCtxUnsealableAnswer(ctxRead);
1485
1499
  if (ctxRead.kind === "absent")
1486
1500
  return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
1487
- const ctx = ctxRead.ctx;
1501
+ const ctx = restateResumeCtx(ctxRead.ctx, restatement);
1488
1502
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1489
1503
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1490
1504
  if (!resolved.ok)
@@ -1495,7 +1509,8 @@ export function createResumeLegs(ctx) {
1495
1509
  return await driveResumeIntoRunLog({
1496
1510
  peerLease: resolved.peerLease,
1497
1511
  execution: resolved.execution,
1498
- runner: runnerFor(spec),
1512
+ runner: resolved.runner,
1513
+ legFacts: resolved.legFacts,
1499
1514
  token,
1500
1515
  sessionId: cp.sessionId,
1501
1516
  principal: auth.principal,
@@ -1595,8 +1610,9 @@ export function createResumeLegs(ctx) {
1595
1610
  return refuse(refuseStoredBodyReplay(sessionId, replay));
1596
1611
  return { ok: true, prepared: { sessionId, token, cp, ctx: ctxRead.ctx } };
1597
1612
  }
1598
- async function drivePlanReview(prepared, decision, editedPlan, reason, hostDecision, req, acceptEarly, permissionModeAfter) {
1599
- const { sessionId, token, cp, ctx } = prepared;
1613
+ async function drivePlanReview(prepared, decision, editedPlan, reason, hostDecision, req, acceptEarly, permissionModeAfter, restatement) {
1614
+ const { sessionId, token, cp } = prepared;
1615
+ const ctx = restateResumeCtx(prepared.ctx, restatement);
1600
1616
  if ((await deps.checkpointStore.findPendingTokenBySession(sessionId, undefined, { gateKinds: ["plan_review"] })) !== token)
1601
1617
  return notResumableAnswer();
1602
1618
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
@@ -1621,20 +1637,26 @@ export function createResumeLegs(ctx) {
1621
1637
  hostDecision,
1622
1638
  };
1623
1639
  const selectedGate = durableRowIdentity("decision-receipt", cp.checkpointId, token);
1624
- const out = await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, commitment: { kind: "gate_resolved", ...selectedGate }, verifyRounds, resumeCtx: { sessionId, ctx: rebuildCtx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1640
+ const out = await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: resolved.runner, legFacts: resolved.legFacts, token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, commitment: { kind: "gate_resolved", ...selectedGate }, verifyRounds, resumeCtx: { sessionId, ctx: rebuildCtx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1625
1641
  return out;
1626
1642
  }
1627
1643
  finally {
1628
1644
  resolved.peerLease.release();
1629
1645
  }
1630
1646
  }
1647
+ const sessionNotifyDoor = createSessionNotifyDoor({
1648
+ activeTaskIdOf: async (sessionId) => deps.runStore?.getActiveTaskId?.(sessionId),
1649
+ liveRuns: steerableRuns,
1650
+ inbox: deps.workflowCompletionInbox,
1651
+ headOf: headReadOf(deps.sessionStorage),
1652
+ });
1631
1653
  const routeCtxBase = {
1632
1654
  deps,
1633
1655
  registry: { idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters },
1634
1656
  helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, runOwnerOk, sessionOwnerOk, runSessionAcceptOk },
1635
1657
  local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal(), capabilities: createCapabilitiesLocal() },
1636
- legs: { prepareSpec, finalizeTaskResult, resumeCheckpoint, driveResumeIntoRunLog, resumePreempted, resumeWake, preparePlanReview, drivePlanReview },
1658
+ legs: { prepareSpec, finalizeTaskResult, resumeCheckpoint, driveResumeIntoRunLog, resumePreempted, resumeWake, preparePlanReview, drivePlanReview, sessionNotify: sessionNotifyDoor },
1637
1659
  };
1638
- return { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake, preparePlanReview, drivePlanReview };
1660
+ return { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake, preparePlanReview, drivePlanReview, sessionNotifyDoor };
1639
1661
  }
1640
1662
  //# sourceMappingURL=resume-legs.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 续跑请求的**重述键** —— 「我此刻的对端表」随续跑请求一起说出来(唯一属主)。
3
+ *
4
+ * ## 律(一条)
5
+ * 续跑请求体可带 {@link RESUME_RESTATABLE_KEYS} 里的键(今天两只:`mcpServers` / `a2aPeers`),键名与值形 = 提交体(`POST /v1/tasks`)的同一键:
6
+ * · **在场**(值不是 `undefined` / `null`)⇒ 先过提交腿**同一只**判官(`assertRequestMcpEntries` / `assertRequestA2aEntries`;坏形 = 与提交腿同码同句的
7
+ * 400,判在续跑腿任何状态改动之前),再**取代**续跑重建输入(`checkpoint_ctx` 体)里的那一键:本次重建读它,续跑赢下 CAS 之后随重建输入一起写回
8
+ * (`DriveResumeArgs.resumeCtx` 那一只写点)⇒ 之后的续跑在没有新重述时重放的也是它;
9
+ * · **缺席**(或 `null`,与提交体「缺席或 `null` = 没声明」同一读法)⇒ 存量体那一键原样重放(冻结读法)。
10
+ * 重建之后的路与存量体逐字同一条(`resolveSpec` 的续跑腿:部署锁、闸、部署保留名、合并、对端座登记、用户面通告),所以一份重述在本部署上能挂上什么,
11
+ * 与它作为一次新提交能挂上什么是同一个答案;引擎从 `ResumeTaskConfig.mcp` / `.a2a`(与 `TaskSpec` 同键)收到的就是这张表。
12
+ *
13
+ * ## 不在本仓判的
14
+ * 「停驻快照里那一只调用的目标台还在不在当前表里」是引擎续跑准入的判(引擎 7.61.0 起三码各答各形,以 core 为准:工具不在了 ⇒ `resume.tool_unavailable`、同名而已不是批准时那一只 ⇒ `resume.tool_contract_mismatch`(两者重开检查点)、还是那一只而面或声明变了 ⇒ `resume.target_moved`(否决);本服务逐字过境)—— 本服务只负责把当前表送到,不自判、不预拒。
15
+ *
16
+ * ## 为什么是一张闭表
17
+ * 与提交腿的一次性动作那张闭表(`resume-legs.ts` 的 `ONE_SHOT_SUBMIT_KEYS`:重建时剥掉)对称 —— 那张说「哪些提交键不随重建走」,这张说「哪些提交键由续跑请求重述」。
18
+ * 加一键 = 改这条律本身:判官表 {@link RESTATEMENT_JUDGES} 缺行即编译红,接收它的续跑门各自在键闭集里声明它。
19
+ */
20
+ import type { TaskRequestBody } from "./wire-types.js";
21
+ import type { CheckpointCtx } from "./route-ctx.js";
22
+ /** 续跑请求可重述的提交键(闭集;`satisfies` 钉在提交体的具名键上 —— 写一个提交体上没有的词 = 编译红)。 */
23
+ export declare const RESUME_RESTATABLE_KEYS: readonly ["a2aPeers", "mcpServers"];
24
+ export type ResumeRestatableKey = (typeof RESUME_RESTATABLE_KEYS)[number];
25
+ /** 一次续跑请求读出的重述(只含在场的键;读者 {@link readResumeRestatement},用者 {@link restateResumeCtx})。 */
26
+ export type ResumeRestatement = {
27
+ readonly [K in ResumeRestatableKey]?: TaskRequestBody[K];
28
+ };
29
+ /**
30
+ * 从一份续跑请求体里读出重述:在场的键逐一过判官,全部过 ⇒ 交回只含在场键的重述;一键都不在 ⇒ `undefined`(冻结读法)。
31
+ * 判官的拒原样抛出(调用方是路由,`HttpError` 由分发器按提交腿同一只渲染器落 wire)。体不是对象 ⇒ `undefined`(体形由各门自己的根形门答)。
32
+ */
33
+ export declare function readResumeRestatement(body: unknown): ResumeRestatement | undefined;
34
+ /**
35
+ * 把重述盖进续跑重建输入:在场的键取代体里的同名键,其余位(记忆域 / 无人值守标 / 读法记号 / 写者版本)原样随行。`undefined` ⇒ 原 ctx 原样(同一个对象)。
36
+ * 各续跑腿在读出 ctx 的那一拍调一次,之后重建(`resolveResumeSpecOrBlocked`)与写回(`resumeCtx`)读的都是这一份。
37
+ */
38
+ export declare function restateResumeCtx(ctx: CheckpointCtx, restatement: ResumeRestatement | undefined): CheckpointCtx;
39
+ //# sourceMappingURL=resume-restatement.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { assertRequestMcpEntries } from "../task-mcp.js";
2
+ import { assertRequestA2aEntries } from "../task-a2a.js";
3
+ export const RESUME_RESTATABLE_KEYS = ["a2aPeers", "mcpServers"];
4
+ const RESTATEMENT_JUDGES = {
5
+ a2aPeers: assertRequestA2aEntries,
6
+ mcpServers: assertRequestMcpEntries,
7
+ };
8
+ export function readResumeRestatement(body) {
9
+ if (body === null || typeof body !== "object" || Array.isArray(body))
10
+ return undefined;
11
+ const raw = body;
12
+ let out;
13
+ for (const key of RESUME_RESTATABLE_KEYS) {
14
+ const value = Object.hasOwn(raw, key) ? raw[key] : undefined;
15
+ if (value === undefined || value === null)
16
+ continue;
17
+ RESTATEMENT_JUDGES[key](value);
18
+ (out ??= {})[key] = value;
19
+ }
20
+ return out;
21
+ }
22
+ export function restateResumeCtx(ctx, restatement) {
23
+ return restatement === undefined ? ctx : { ...ctx, body: { ...ctx.body, ...restatement } };
24
+ }
25
+ //# sourceMappingURL=resume-restatement.js.map