@sema-agent/server 7.141.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 (341) hide show
  1. package/MIGRATION.md +657 -0
  2. package/USAGE.md +102 -30
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +61 -43
  5. package/dist/approval-ask-audit-store.js +69 -44
  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 +126 -27
  9. package/dist/approval-card.js +103 -23
  10. package/dist/approval-crash-convergence.d.ts +17 -7
  11. package/dist/approval-crash-convergence.js +94 -22
  12. package/dist/approval-deny-reasons.d.ts +34 -30
  13. package/dist/approval-deny-reasons.js +21 -6
  14. package/dist/approval-policy-settlement.d.ts +1 -1
  15. package/dist/approval-queue-kind.d.ts +27 -0
  16. package/dist/approval-queue-kind.js +6 -0
  17. package/dist/approval-reconciler.d.ts +41 -9
  18. package/dist/approval-reconciler.js +45 -42
  19. package/dist/audit.js +4 -2
  20. package/dist/auth-bridge.d.ts +9 -6
  21. package/dist/auth-bridge.js +9 -3
  22. package/dist/boot/adoption.js +2 -1
  23. package/dist/boot/budget-tracing.d.ts +17 -1
  24. package/dist/boot/budget-tracing.js +10 -6
  25. package/dist/boot/device-lane.d.ts +1 -1
  26. package/dist/boot/execution-env.d.ts +24 -4
  27. package/dist/boot/execution-env.js +26 -23
  28. package/dist/boot/execution-lane.js +2 -1
  29. package/dist/boot/exit-boundary.js +5 -4
  30. package/dist/boot/governance-seams.d.ts +26 -41
  31. package/dist/boot/governance-seams.js +61 -28
  32. package/dist/boot/late-binds.d.ts +7 -4
  33. package/dist/boot/late-binds.js +3 -1
  34. package/dist/boot/leader.js +2 -4
  35. package/dist/boot/memory-consolidation.d.ts +0 -8
  36. package/dist/boot/memory-consolidation.js +5 -4
  37. package/dist/boot/parked-revive-gate.js +2 -1
  38. package/dist/boot/permission-rules-audit.d.ts +0 -28
  39. package/dist/boot/permission-rules-audit.js +2 -1
  40. package/dist/boot/read-deny-anchor-audit.js +2 -1
  41. package/dist/boot/reapers.js +9 -7
  42. package/dist/boot/resolve-spec.d.ts +1 -1
  43. package/dist/boot/resolve-spec.js +19 -10
  44. package/dist/boot/retention-lane.js +5 -3
  45. package/dist/boot/runner-deps.d.ts +10 -7
  46. package/dist/boot/runner-deps.js +20 -29
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.js +4 -3
  49. package/dist/boot/shutdown.d.ts +9 -0
  50. package/dist/boot/shutdown.js +19 -4
  51. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  52. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  53. package/dist/boot/stage-03-store-backend.js +1 -2
  54. package/dist/boot/stage-04-stores.d.ts +5 -2
  55. package/dist/boot/stage-04-stores.js +6 -7
  56. package/dist/boot/stage-05-execution-env.d.ts +6 -3
  57. package/dist/boot/stage-05-execution-env.js +8 -6
  58. package/dist/boot/stage-06-runners.d.ts +6 -3
  59. package/dist/boot/stage-06-runners.js +3 -4
  60. package/dist/boot/stage-07-capability-layer.d.ts +6 -3
  61. package/dist/boot/stage-07-capability-layer.js +7 -2
  62. package/dist/boot/stage-08-reapers.d.ts +6 -3
  63. package/dist/boot/stage-08-reapers.js +3 -2
  64. package/dist/boot/stage-09-leader.d.ts +6 -3
  65. package/dist/boot/stage-10-http-server.d.ts +6 -3
  66. package/dist/boot/stage-10-http-server.js +14 -5
  67. package/dist/boot/stores.d.ts +5 -2
  68. package/dist/boot/stores.js +10 -9
  69. package/dist/boot/web-admin.d.ts +5 -1
  70. package/dist/boot/web-admin.js +48 -36
  71. package/dist/boot/webfetch-summarize-lane.js +2 -1
  72. package/dist/boot/workflow-orchestration.js +3 -2
  73. package/dist/capabilities/builtin-tools.d.ts +2 -1
  74. package/dist/capabilities/builtin-tools.js +2 -4
  75. package/dist/capabilities/composite-members.d.ts +2 -1
  76. package/dist/capabilities/composite-members.js +2 -1
  77. package/dist/capabilities/hands-lane.d.ts +9 -39
  78. package/dist/capabilities/hands-lane.js +8 -13
  79. package/dist/capabilities/mcp-probe.d.ts +3 -2
  80. package/dist/capabilities/scenarios.d.ts +11 -1
  81. package/dist/capabilities/scenarios.js +1 -0
  82. package/dist/capabilities/skills.d.ts +1 -17
  83. package/dist/capabilities/skills.js +0 -8
  84. package/dist/capability-facts.d.ts +31 -7
  85. package/dist/capability-facts.js +27 -4
  86. package/dist/card-void.d.ts +6 -9
  87. package/dist/config-catalog.js +2 -1
  88. package/dist/config-dir-trust.js +58 -29
  89. package/dist/config-invariants.d.ts +7 -1
  90. package/dist/config-invariants.js +7 -0
  91. package/dist/config-provider.d.ts +10 -0
  92. package/dist/config-provider.js +15 -4
  93. package/dist/config-root-migration.d.ts +13 -0
  94. package/dist/config-root-migration.js +99 -0
  95. package/dist/config-types.d.ts +19 -8
  96. package/dist/config.d.ts +10 -3
  97. package/dist/config.js +18 -4
  98. package/dist/delegated-session-owner.d.ts +5 -3
  99. package/dist/delegated-session-owner.js +1 -1
  100. package/dist/device-ws-protocol.d.ts +1 -0
  101. package/dist/device-ws-protocol.js +2 -1
  102. package/dist/env-model-table-gate.d.ts +53 -0
  103. package/dist/env-model-table-gate.js +53 -0
  104. package/dist/execution-lane-caps.d.ts +2 -1
  105. package/dist/fleet/fleet-bus.d.ts +34 -55
  106. package/dist/fleet/fleet-bus.js +35 -100
  107. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  108. package/dist/fleet/fleet-reconciler.js +1 -1
  109. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  110. package/dist/fleet/subagent-tail-bus.js +15 -28
  111. package/dist/hooks/hook-runner.d.ts +6 -0
  112. package/dist/hooks/hook-runner.js +58 -8
  113. package/dist/host-decision.d.ts +54 -20
  114. package/dist/host-decision.js +42 -14
  115. package/dist/http/admission.d.ts +1 -1
  116. package/dist/http/admission.js +7 -3
  117. package/dist/http/approval-sweeps.js +11 -8
  118. package/dist/http/caller-trust.d.ts +1 -1
  119. package/dist/http/dispatch.js +4 -2
  120. package/dist/http/durable-run-leg.js +2 -2
  121. package/dist/http/engine-refusal.d.ts +16 -0
  122. package/dist/http/engine-refusal.js +17 -0
  123. package/dist/http/execution-context.d.ts +2 -2
  124. package/dist/http/idempotency.d.ts +1 -1
  125. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  126. package/dist/http/memory-compliance-failure-reply.js +4 -6
  127. package/dist/http/owner-access.d.ts +11 -0
  128. package/dist/http/owner-access.js +13 -0
  129. package/dist/http/principal-gate.d.ts +3 -1
  130. package/dist/http/principal-gate.js +17 -8
  131. package/dist/http/resume-legs.d.ts +21 -7
  132. package/dist/http/resume-legs.js +76 -58
  133. package/dist/http/resume-restatement.d.ts +39 -0
  134. package/dist/http/resume-restatement.js +25 -0
  135. package/dist/http/route-ctx.d.ts +35 -10
  136. package/dist/http/route-ctx.js +1 -1
  137. package/dist/http/route-table.js +2 -0
  138. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  139. package/dist/http/routes/approvals-assistant.js +142 -31
  140. package/dist/http/routes/attachments.js +18 -0
  141. package/dist/http/routes/capabilities.js +16 -4
  142. package/dist/http/routes/memory-policy.d.ts +2 -2
  143. package/dist/http/routes/memory-policy.js +27 -7
  144. package/dist/http/routes/notify-wake.js +23 -25
  145. package/dist/http/routes/rules.js +48 -13
  146. package/dist/http/routes/runs.js +12 -11
  147. package/dist/http/routes/session-memory-sync.d.ts +91 -0
  148. package/dist/http/routes/session-memory-sync.js +174 -0
  149. package/dist/http/routes/session-sync.js +13 -20
  150. package/dist/http/routes/sessions-list.d.ts +2 -1
  151. package/dist/http/routes/sessions.d.ts +2 -2
  152. package/dist/http/routes/sessions.js +22 -23
  153. package/dist/http/routes/tasks.js +34 -54
  154. package/dist/http/run-read-face.d.ts +7 -0
  155. package/dist/http/run-read-face.js +9 -3
  156. package/dist/http/send.d.ts +5 -0
  157. package/dist/http/send.js +9 -2
  158. package/dist/http/server.d.ts +27 -15
  159. package/dist/http/server.js +4 -6
  160. package/dist/http/stored-body-replay.js +15 -4
  161. package/dist/http/submit-replay.d.ts +4 -3
  162. package/dist/http/submit-replay.js +2 -2
  163. package/dist/http/sync-submit-body.d.ts +21 -4
  164. package/dist/http/sync-submit-body.js +13 -0
  165. package/dist/http/wire-types.d.ts +17 -7
  166. package/dist/identity-source.d.ts +21 -2
  167. package/dist/identity-source.js +6 -0
  168. package/dist/leader/leader.js +5 -2
  169. package/dist/leader/wire.d.ts +7 -1
  170. package/dist/leader/wire.js +6 -5
  171. package/dist/leg-facts.d.ts +37 -0
  172. package/dist/leg-facts.js +15 -0
  173. package/dist/memory-bundle-engine.d.ts +1 -4
  174. package/dist/memory-bundle-engine.js +2 -2
  175. package/dist/memory-incident-sink.d.ts +19 -6
  176. package/dist/memory-incident-sink.js +8 -4
  177. package/dist/memory-operator-faces.d.ts +30 -66
  178. package/dist/memory-operator-faces.js +17 -88
  179. package/dist/memory-scope-stores.d.ts +38 -40
  180. package/dist/memory-scope-stores.js +51 -36
  181. package/dist/memory-scope.d.ts +0 -9
  182. package/dist/memory-scope.js +0 -9
  183. package/dist/memory-sync-client.d.ts +21 -21
  184. package/dist/memory-sync-client.js +62 -43
  185. package/dist/memory-sync-identity.d.ts +24 -0
  186. package/dist/memory-sync-identity.js +57 -0
  187. package/dist/memory-sync.d.ts +27 -5
  188. package/dist/memory-sync.js +29 -7
  189. package/dist/model-plane-face.d.ts +13 -7
  190. package/dist/model-plane-face.js +2 -1
  191. package/dist/observability/fail-open.d.ts +47 -14
  192. package/dist/observability/fail-open.js +37 -11
  193. package/dist/observability/run-terminal.d.ts +92 -34
  194. package/dist/observability/run-terminal.js +171 -162
  195. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  196. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  197. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  198. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  199. package/dist/orchestration/workflow-park-decision.js +5 -3
  200. package/dist/org-delivery-gate.d.ts +11 -0
  201. package/dist/org-delivery-gate.js +19 -1
  202. package/dist/parked-decide.d.ts +13 -9
  203. package/dist/parked-decide.js +7 -7
  204. package/dist/parked-revive-surface.js +1 -1
  205. package/dist/peer-inbound-face.d.ts +127 -0
  206. package/dist/peer-inbound-face.js +158 -0
  207. package/dist/permission-rule-list.d.ts +13 -9
  208. package/dist/permission-rule-list.js +7 -3
  209. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  210. package/dist/plugins/approval-ask-store-file.js +64 -11
  211. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  212. package/dist/plugins/approval-ask-store-memory.js +17 -14
  213. package/dist/plugins/approval-ask-store-sql.d.ts +70 -23
  214. package/dist/plugins/approval-ask-store-sql.js +79 -30
  215. package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
  216. package/dist/plugins/checkpoint-store-sql.js +9 -7
  217. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  218. package/dist/plugins/file-history-store-sql.js +24 -19
  219. package/dist/plugins/file-run-store.d.ts +35 -1
  220. package/dist/plugins/file-run-store.js +108 -9
  221. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  222. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  223. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  224. package/dist/plugins/local-checkpoint-store.js +2 -2
  225. package/dist/plugins/local-session-store.d.ts +42 -9
  226. package/dist/plugins/local-session-store.js +83 -33
  227. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  228. package/dist/plugins/local-task-attachment-store.js +67 -43
  229. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  230. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  231. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  232. package/dist/plugins/mailbox-store-sql.js +76 -7
  233. package/dist/plugins/memory-key-guards.d.ts +3 -3
  234. package/dist/plugins/memory-run-store.d.ts +6 -1
  235. package/dist/plugins/memory-run-store.js +11 -1
  236. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  237. package/dist/plugins/memory-sync-store-pg.js +21 -0
  238. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  239. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  240. package/dist/plugins/one-time-migrations.d.ts +58 -2
  241. package/dist/plugins/one-time-migrations.js +161 -4
  242. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  243. package/dist/plugins/org-delivery-quota-store.js +2 -12
  244. package/dist/plugins/park-commit-watch.d.ts +1 -1
  245. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  246. package/dist/plugins/permission-rule-store-file.js +15 -2
  247. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  248. package/dist/plugins/permission-rule-store-sql.js +35 -2
  249. package/dist/plugins/remote-env-device.d.ts +8 -11
  250. package/dist/plugins/remote-env-device.js +6 -3
  251. package/dist/plugins/remote-env-e2b.d.ts +23 -4
  252. package/dist/plugins/remote-env-e2b.js +111 -28
  253. package/dist/plugins/remote-env-k8s.d.ts +61 -22
  254. package/dist/plugins/remote-env-k8s.js +133 -33
  255. package/dist/plugins/remote-env-local-docker.d.ts +15 -3
  256. package/dist/plugins/remote-env-local-docker.js +40 -12
  257. package/dist/plugins/retention-store-sql.d.ts +2 -2
  258. package/dist/plugins/run-store-sql.d.ts +9 -1
  259. package/dist/plugins/run-store-sql.js +9 -1
  260. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  261. package/dist/plugins/sandbox-read-floor.js +2 -7
  262. package/dist/plugins/sql-errors.d.ts +10 -1
  263. package/dist/plugins/sql-errors.js +13 -0
  264. package/dist/plugins/store-backend.d.ts +33 -35
  265. package/dist/plugins/store-backend.js +10 -1
  266. package/dist/plugins/store-contracts.d.ts +50 -1
  267. package/dist/plugins/store-contracts.js +6 -0
  268. package/dist/plugins/task-attachment-store.d.ts +112 -14
  269. package/dist/plugins/task-attachment-store.js +207 -29
  270. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  271. package/dist/plugins/tool-result-store-sql.js +23 -2
  272. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  273. package/dist/plugins/wiring-section-codec.js +55 -0
  274. package/dist/plugins/workspace-fill.d.ts +60 -0
  275. package/dist/plugins/workspace-fill.js +106 -0
  276. package/dist/project-bundle.d.ts +38 -0
  277. package/dist/project-bundle.js +609 -0
  278. package/dist/rule-entry-list.d.ts +3 -2
  279. package/dist/rule-entry-list.js +5 -1
  280. package/dist/rules-consent.d.ts +28 -5
  281. package/dist/rules-consent.js +40 -28
  282. package/dist/run-local.js +11 -13
  283. package/dist/runs.d.ts +12 -8
  284. package/dist/runs.js +6 -8
  285. package/dist/runtime-governance.d.ts +1 -1
  286. package/dist/sandbox-egress-policy.d.ts +4 -1
  287. package/dist/sandbox-egress-policy.js +3 -1
  288. package/dist/sandbox-git-plane.d.ts +31 -12
  289. package/dist/sandbox-git-plane.js +24 -4
  290. package/dist/security.d.ts +9 -0
  291. package/dist/security.js +8 -0
  292. package/dist/server-secret-env.d.ts +4 -0
  293. package/dist/session-error-wire.d.ts +36 -0
  294. package/dist/session-error-wire.js +13 -0
  295. package/dist/session-incarnation.d.ts +1 -1
  296. package/dist/session-notify-door.d.ts +106 -0
  297. package/dist/session-notify-door.js +93 -0
  298. package/dist/session-sync.d.ts +9 -14
  299. package/dist/session-sync.js +5 -5
  300. package/dist/session-workspace.d.ts +3 -13
  301. package/dist/session-workspace.js +0 -9
  302. package/dist/standing-answer-grant.d.ts +5 -0
  303. package/dist/standing-answer-grant.js +10 -0
  304. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  305. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  306. package/dist/task-mcp.d.ts +1 -1
  307. package/dist/task-settings.d.ts +21 -10
  308. package/dist/task-settings.js +46 -13
  309. package/dist/terminal.d.ts +39 -19
  310. package/dist/terminal.js +65 -22
  311. package/dist/tool-approval.d.ts +56 -37
  312. package/dist/tool-approval.js +78 -71
  313. package/dist/trace/core-keyset-guard.d.ts +134 -14
  314. package/dist/trace/engine-notice-wire.d.ts +9 -3
  315. package/dist/trace/engine-notice-wire.js +7 -3
  316. package/dist/trace/exit-seat.d.ts +9 -2
  317. package/dist/trace/exit-seat.js +1 -0
  318. package/dist/trace/ledger-sink.d.ts +10 -6
  319. package/dist/trace/ledger-sink.js +2 -2
  320. package/dist/trace/model-segment.d.ts +55 -25
  321. package/dist/trace/model-segment.js +101 -33
  322. package/dist/trace/project.d.ts +25 -5
  323. package/dist/trace/project.js +22 -0
  324. package/dist/trace/redact.d.ts +61 -9
  325. package/dist/trace/redact.js +1490 -468
  326. package/dist/trace/self-minted-wire.d.ts +745 -24
  327. package/dist/trace/self-minted-wire.js +239 -9
  328. package/dist/trace/wire-projection-faces.d.ts +2 -2
  329. package/dist/trace/wire-projection-faces.js +2 -2
  330. package/dist/verifier-pause-card.d.ts +33 -5
  331. package/dist/verifier-pause-card.js +15 -10
  332. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  333. package/dist/web-admin-client/apply-effective.js +109 -9
  334. package/dist/web-admin-client/effective-keys.js +2 -0
  335. package/dist/web-admin-client/types.d.ts +4 -0
  336. package/dist/workspace-space.d.ts +19 -3
  337. package/dist/workspace-space.js +8 -4
  338. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  339. package/package.json +3 -2
  340. package/wire-changes/server@7.142.0.json +639 -0
  341. package/wire-changes/server@7.143.0.json +1167 -0
@@ -8,7 +8,7 @@ import { appendFileStaged, buildCwdEnvCommand, createCwdGuardedCall, shellQuote,
8
8
  import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
9
9
  import { WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "../object-store-buckets.js";
10
10
  import { SNAPSHOT_BUCKET_MISSING, isNoSuchBucketAnswer } from "./s3-answer.js";
11
- import { K8S_WORKSPACE_MOUNT_DEFAULT, SNAPSHOT_KEY_SEGMENT, WORKSPACE_SNAPSHOT_PREFIX_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, mintSnapshotId, snapshotKeyPrefixProblem, snapshotNameOwnedBy } from "../workspace-space.js";
11
+ import { K8S_WORKSPACE_MOUNT_DEFAULT, SNAPSHOT_KEY_SEGMENT, WORKSPACE_SNAPSHOT_PREFIX_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, mintSnapshotId, snapshotIdHolds, snapshotKeyPrefixProblem, snapshotNameOwnedBy } from "../workspace-space.js";
12
12
  import { spaceCommitAlive } from "../workspace-space-commit.js";
13
13
  import { incarnationLogPrefix, redactIncarnations } from "../session-incarnation.js";
14
14
  import { WORKSPACE_CACHE_SPACE_LABEL, workspaceCacheClaimOf, workspaceCacheEnv } from "../workspace-cache.js";
@@ -48,6 +48,9 @@ export { applyPodSpecPatch } from "./k8s-exec-protocol.js";
48
48
  import { sandboxRunEndHoldMs } from "./k8s-pod-reaper.js";
49
49
  import { BROKER_COMMAND, BROKER_CONTAINER_NAME, BROKER_LEASE_DIR, BROKER_LEASE_KEY, BROKER_PROBE_ARG, BROKER_VOLUME_NAME } from "../broker/constants.js";
50
50
  import { buildSandboxLeaseFile, placeholderLeaseEntries, sandboxGitRouteEnv } from "../sandbox-git-lease.js";
51
+ function minted(c) {
52
+ return c.ok ? { ...c, origin: "minted" } : c;
53
+ }
51
54
  const CACHE_VOLUME = ADAPTER_VOLUME_NAMES.cache;
52
55
  function occupantKeyOf(occupant) {
53
56
  if (occupant === undefined)
@@ -213,6 +216,7 @@ export class RemoteK8sExecutionEnv {
213
216
  inFlight = new Set();
214
217
  idleWaiters = [];
215
218
  connectInFlight;
219
+ resumeInFlight;
216
220
  connectRefusal;
217
221
  get homeDir() {
218
222
  return this.homeSeat.home;
@@ -231,6 +235,7 @@ export class RemoteK8sExecutionEnv {
231
235
  cacheUnavailableSaid = false;
232
236
  logger;
233
237
  onWorkspaceSnapshot;
238
+ workspaceFill;
234
239
  cacheMisses;
235
240
  constructor(config, deps) {
236
241
  this.cfg = {
@@ -270,6 +275,7 @@ export class RemoteK8sExecutionEnv {
270
275
  this.cacheLane = cacheLaneOf(config.cache, config.spaceOccupant);
271
276
  this.logger = config.logger;
272
277
  this.onWorkspaceSnapshot = config.onWorkspaceSnapshot;
278
+ this.workspaceFill = config.workspaceFill;
273
279
  this.cacheMisses = config.cacheMisses;
274
280
  this.floor = this.deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor, ...(config.logger !== undefined ? { logger: config.logger } : {}) });
275
281
  this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER, s3Snapshot: this.cfg.s3Snapshot }, sandboxReadFloor: config.sandboxReadFloor });
@@ -374,10 +380,14 @@ export class RemoteK8sExecutionEnv {
374
380
  });
375
381
  }
376
382
  connect(config) {
377
- this.connectInFlight ??= this.establish(config).finally(() => {
378
- this.connectInFlight = undefined;
383
+ if (this.connectInFlight)
384
+ return this.connectInFlight;
385
+ const walk = this.establish(config).finally(() => {
386
+ if (this.connectInFlight === walk)
387
+ this.connectInFlight = undefined;
379
388
  });
380
- return this.connectInFlight;
389
+ this.connectInFlight = walk;
390
+ return walk;
381
391
  }
382
392
  async establish(config) {
383
393
  if (this.connectRefusal)
@@ -386,10 +396,10 @@ export class RemoteK8sExecutionEnv {
386
396
  const lane = this.spaceLane;
387
397
  switch (lane.state) {
388
398
  case "off":
389
- return this.provision(signal);
399
+ return this.settle(minted(await this.provision(signal)));
390
400
  case "unavailable":
391
401
  this.sayUnavailable(lane.reason);
392
- return this.provision(signal);
402
+ return this.settle(minted(await this.provision(signal)));
393
403
  case "unreadable":
394
404
  return this.provision(signal);
395
405
  case "open": {
@@ -399,7 +409,7 @@ export class RemoteK8sExecutionEnv {
399
409
  const latest = await this.readLatest(lane.space, signal);
400
410
  if (!latest.ok)
401
411
  return latest;
402
- const c = latest.value === undefined ? await this.provision(signal) : await this.restoreSnapshot({ lane: "space", space: lane.space }, latest.value, signal);
412
+ const c = await this.settle(latest.value === undefined ? minted(await this.provision(signal)) : await this.restoreSnapshot({ lane: "space", space: lane.space }, latest.value, signal));
403
413
  if (c.ok)
404
414
  this.armSpace();
405
415
  return c;
@@ -410,6 +420,43 @@ export class RemoteK8sExecutionEnv {
410
420
  }
411
421
  }
412
422
  }
423
+ async settle(c) {
424
+ if (!c.ok)
425
+ return c;
426
+ const fill = this.workspaceFill;
427
+ if (fill !== undefined) {
428
+ const filled = await fill.fill(this.fillChannel(), this.cwd, c.origin);
429
+ if (!filled.ok) {
430
+ await this.releasePod();
431
+ return filled.standing ? this.holdRefusal(filled.error) : { ok: false, error: filled.error };
432
+ }
433
+ }
434
+ if (this.destroyed) {
435
+ await this.releasePod();
436
+ return { ok: false, error: new RemoteExecutionError("connect_failed", "env destroyed during connect") };
437
+ }
438
+ return { ok: true, value: c.value };
439
+ }
440
+ fillChannel() {
441
+ const exec = (command) => this.runExec(ADAPTER_START, command, { capMs: this.cfg.dataTimeoutMs });
442
+ return {
443
+ run: async (command) => {
444
+ const r = await exec(command);
445
+ return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
446
+ },
447
+ write: async (abs, bytes) => {
448
+ const w = await this.writeChunked(abs, bytes, true, undefined, exec);
449
+ return w.ok ? { ok: true } : { ok: false, error: w.error.message };
450
+ },
451
+ };
452
+ }
453
+ async releasePod() {
454
+ const name = this.podName;
455
+ this.podName = undefined;
456
+ this.podIP = undefined;
457
+ if (name)
458
+ await this.deletePod(name).catch(() => { });
459
+ }
413
460
  armSpace() {
414
461
  if (this.spaceLane.state === "open")
415
462
  this.spaceLane.space.fence.arm(this.cfg.dataTimeoutMs);
@@ -462,8 +509,10 @@ export class RemoteK8sExecutionEnv {
462
509
  return spaceReadFailed("occupant", ` (${code}) — refusing to start this run on a workspace that could be neither restored nor saved; retry the run once the session store answers, or set ${ways}`);
463
510
  }
464
511
  spaceLine(event, space, fields) {
465
- const safe = Object.fromEntries(Object.entries(fields).map(([k, v]) => [k, typeof v === "string" ? redactIncarnations(v) : v]));
466
- this.logger?.warn(event, { space: incarnationLogPrefix(space.incarnation), ...safe });
512
+ this.snapshotLine(event, { space: incarnationLogPrefix(space.incarnation), ...fields });
513
+ }
514
+ snapshotLine(event, fields) {
515
+ this.logger?.warn(event, Object.fromEntries(Object.entries(fields).map(([k, v]) => [k, typeof v === "string" ? redactIncarnations(v) : v])));
467
516
  }
468
517
  provision(signal) {
469
518
  const space = this.spaceLane;
@@ -1177,12 +1226,12 @@ export class RemoteK8sExecutionEnv {
1177
1226
  async uploadSnapshot(target, boundMs = this.cfg.dataTimeoutMs) {
1178
1227
  if (target.lane === "park" && this.inFlight.size > 0)
1179
1228
  return { ok: false, error: new RemoteExecutionError("command_in_flight", "cannot suspend while a command is in flight (would snapshot a torn workspace)") };
1180
- const snapshotId = mintSnapshotId(this.occupantIncarnation);
1181
1229
  if (!this.podName) {
1182
1230
  return target.lane === "space"
1183
1231
  ? { ok: false, error: new RemoteExecutionError("connect_failed", "the sandbox pod was gone when the run-end snapshot reached the upload — nothing was saved (the workspace could not be read), and `latest` keeps naming the previous snapshot") }
1184
- : ok(snapshotId);
1232
+ : ok(mintSnapshotId(this.occupantIncarnation, "nothing"));
1185
1233
  }
1234
+ const snapshotId = mintSnapshotId(this.occupantIncarnation, "archive");
1186
1235
  const presigned = this.presignSnapshot({ ...target, method: "PUT", snapshotId });
1187
1236
  if (!presigned.ok)
1188
1237
  return presigned;
@@ -1202,16 +1251,50 @@ export class RemoteK8sExecutionEnv {
1202
1251
  await this.deletePod(name).catch(() => { });
1203
1252
  return ok(snapshotId);
1204
1253
  }
1205
- async resumeVM(snapshotId, options) {
1254
+ resumeVM(snapshotId, options) {
1206
1255
  if (!this.cfg.s3Snapshot)
1207
- return unsupported("resumeVM");
1208
- const r = await this.restoreSnapshot({ lane: "park" }, snapshotId, options?.abortSignal);
1256
+ return Promise.resolve(unsupported("resumeVM"));
1257
+ const same = this.resumeInFlight;
1258
+ if (same?.snapshotId === snapshotId)
1259
+ return same.walk;
1260
+ const prior = this.connectInFlight;
1261
+ const walk = this.resumeWalk(prior, snapshotId, options).finally(() => {
1262
+ if (this.connectInFlight === walk)
1263
+ this.connectInFlight = undefined;
1264
+ if (this.resumeInFlight?.walk === walk)
1265
+ this.resumeInFlight = undefined;
1266
+ });
1267
+ this.connectInFlight = walk;
1268
+ this.resumeInFlight = { snapshotId, walk };
1269
+ return walk;
1270
+ }
1271
+ async resumeWalk(prior, snapshotId, options) {
1272
+ if (prior)
1273
+ await prior;
1274
+ if (this.podName) {
1275
+ this.logger?.warn("sandbox_pod_released_before_restore", { pod: this.podName, ...(this.cfg.sessionId ? { sessionId: this.cfg.sessionId } : {}), effect: "this env held a pod minted before the park snapshot's restore — it is deleted; the restore provisions the leg's one pod" });
1276
+ await this.releasePod();
1277
+ }
1278
+ const r = await this.restorePointOf(snapshotId, options?.abortSignal);
1209
1279
  if (!r.ok)
1210
1280
  return r;
1211
1281
  this.handleSnapshotId = snapshotId;
1212
1282
  this.armSpace();
1213
1283
  return ok(this.workspaceHandle());
1214
1284
  }
1285
+ async restorePointOf(snapshotId, abortSignal) {
1286
+ const holds = snapshotIdHolds(snapshotId);
1287
+ switch (holds) {
1288
+ case "archive":
1289
+ return this.settle(await this.restoreSnapshot({ lane: "park" }, snapshotId, abortSignal));
1290
+ case "nothing":
1291
+ return this.establish(abortSignal ? { abortSignal } : undefined);
1292
+ default: {
1293
+ const unreachable = holds;
1294
+ throw new Error(`unhandled snapshot id family: ${String(unreachable)}`);
1295
+ }
1296
+ }
1297
+ }
1215
1298
  async restoreSnapshot(target, snapshotId, abortSignal) {
1216
1299
  const presigned = this.presignSnapshot({ ...target, method: "GET", snapshotId });
1217
1300
  if (!presigned.ok)
@@ -1234,15 +1317,13 @@ export class RemoteK8sExecutionEnv {
1234
1317
  ? { cls: restoreClassOfExit(r.value.exitCode), detail: `workspace snapshot restore exited ${r.value.exitCode}: ${this.redactUrl(r.value.stderr, url).slice(0, 300)}` }
1235
1318
  : undefined;
1236
1319
  if (!failure) {
1237
- if (target.lane === "space" && r.ok && r.value.stdout.includes("EMPTY_SNAPSHOT"))
1238
- await this.dropDanglingLatest(target.space, snapshotId);
1239
- return ok(this.workspaceHandle());
1320
+ if (r.ok && r.value.stdout.includes("EMPTY_SNAPSHOT")) {
1321
+ await this.dropDangling(target, snapshotId);
1322
+ return { ok: true, value: this.workspaceHandle(), origin: "minted" };
1323
+ }
1324
+ return { ok: true, value: this.workspaceHandle(), origin: "restored" };
1240
1325
  }
1241
- const name = this.podName;
1242
- this.podName = undefined;
1243
- this.podIP = undefined;
1244
- if (name)
1245
- await this.deletePod(name).catch(() => { });
1326
+ await this.releasePod();
1246
1327
  if (attempt < RESTORE_MAX_ATTEMPTS[target.lane] && restoreFailureIsTransient(failure.cls)) {
1247
1328
  if (await this.restorePause(abortSignal))
1248
1329
  continue;
@@ -1280,15 +1361,34 @@ export class RemoteK8sExecutionEnv {
1280
1361
  signal?.addEventListener("abort", onAbort, { once: true });
1281
1362
  });
1282
1363
  }
1283
- async dropDanglingLatest(space, snapshotId) {
1284
- const prefix = this.snapshotKeyPrefix();
1285
- const dropped = await this.writePointer(space, "latest", null);
1286
- this.spaceLine("workspace_latest_dangling", space, {
1287
- key: `${prefix}/${space.incarnation}/space/${snapshotId}.tar.gz`,
1288
- deleted: dropped.ok,
1289
- ...(dropped.ok ? {} : { error: dropped.error.message }),
1290
- effect: "the space's latest snapshot is gone (removed by a bucket lifecycle rule or by hand) — this run starts from an empty /workspace; its run-end snapshot becomes the new latest",
1291
- });
1364
+ async dropDangling(target, snapshotId) {
1365
+ const key = `${this.snapshotBase(target)}${snapshotId}.tar.gz`;
1366
+ switch (target.lane) {
1367
+ case "space": {
1368
+ const dropped = await this.writePointer(target.space, "latest", null);
1369
+ this.spaceLine("workspace_latest_dangling", target.space, {
1370
+ lane: "space",
1371
+ key,
1372
+ deleted: dropped.ok,
1373
+ ...(dropped.ok ? {} : { error: dropped.error.message }),
1374
+ effect: "the space's latest snapshot is gone (removed by a bucket lifecycle rule or by hand) — this run starts from an empty /workspace; its run-end snapshot becomes the new latest",
1375
+ });
1376
+ return;
1377
+ }
1378
+ case "park":
1379
+ this.snapshotLine("workspace_latest_dangling", {
1380
+ lane: "park",
1381
+ key,
1382
+ ...(this.cfg.sessionId ? { sessionId: this.cfg.sessionId } : {}),
1383
+ effect: "the parked run's snapshot is gone (removed by a bucket lifecycle rule or by hand) — the resumed leg starts from an empty /workspace" +
1384
+ (this.spaceLane.state === "open" ? "; its run-end snapshot becomes the space's new latest" : ""),
1385
+ });
1386
+ return;
1387
+ default: {
1388
+ const unreachable = target;
1389
+ throw new Error(`unhandled snapshot lane: ${String(unreachable.lane)}`);
1390
+ }
1391
+ }
1292
1392
  }
1293
1393
  async postResumeInit() {
1294
1394
  if (!this.cfg.s3Snapshot)
@@ -2081,12 +2181,12 @@ export class RemoteK8sExecutionEnv {
2081
2181
  const abs = this.resolve(p);
2082
2182
  return appendFileStaged(this.stagedLane(content), abs, abortSignal, (ctx) => ctx.inPlace((signal) => this.writeChunked(abs, content, false, signal)));
2083
2183
  }
2084
- async writeChunked(abs, content, truncateFirst, abortSignal) {
2184
+ async writeChunked(abs, content, truncateFirst, abortSignal, exec = (command) => this.execData(command, abortSignal)) {
2085
2185
  const bytes = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
2086
2186
  for (let off = 0, first = true; off < bytes.length || first; off += WRITE_CHUNK_BYTES, first = false) {
2087
2187
  const b64 = bytes.subarray(off, off + WRITE_CHUNK_BYTES).toString("base64");
2088
2188
  const redirect = first && truncateFirst ? ">" : ">>";
2089
- const r = await this.execData(`printf '%s' ${shellQuote(b64)} | base64 -d ${redirect} ${shellQuote(abs)}`, abortSignal);
2189
+ const r = await exec(`printf '%s' ${shellQuote(b64)} | base64 -d ${redirect} ${shellQuote(abs)}`);
2090
2190
  if (!r.ok)
2091
2191
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
2092
2192
  if (r.value.exitCode !== 0)
@@ -3,6 +3,7 @@ import { type LaneHomeConfig } from "../lane-home-probe.js";
3
3
  import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type ExecutionEnvCapabilities } from "@sema-agent/core";
4
4
  import { type SandboxReadFloorKnob } from "../execution-lane-caps.js";
5
5
  import { type SandboxReadFloor } from "./sandbox-read-floor.js";
6
+ import type { WorkspaceFill, WorkspaceFillOf } from "./workspace-fill.js";
6
7
  /** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
7
8
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
8
9
  /** Construction config for {@link RemoteLocalDockerExecutionEnv}. */
@@ -59,6 +60,8 @@ export interface LocalDockerEnvConfig extends LaneHomeConfig {
59
60
  id?: string;
60
61
  /** `SANDBOX_READ_FLOOR`(keep | off;缺席 ⇒ keep):本容器守不守读拒表的字节地板 = 读地板模块服务本车道 ∧ 本键不是 off(`sandbox-read-floor.ts`)。 */
61
62
  sandboxReadFloor?: SandboxReadFloorKnob;
63
+ /** 恢复点的填充座(`workspace-fill.ts`;工厂按 run 装配):容器新建之后、交给本环境之前,经本适配器自己的 `docker exec` 往工作区里放内容。缺席 ⇒ 不放。 */
64
+ workspaceFill?: WorkspaceFill;
62
65
  }
63
66
  /** Inject `spawn` for tests (a fake docker CLI). Defaults to node's child_process spawn. */
64
67
  export interface LocalDockerEnvDeps {
@@ -92,12 +95,15 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
92
95
  private destroyed;
93
96
  /** In-flight connect, memoized so concurrent lazy first-use starts exactly one container (no double-run). */
94
97
  private connecting?;
95
- /** 家目录:环境答了而与声明不一致 ⇒ 这只环境的拒,记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。 */
96
- private homeRefusal?;
98
+ /** 这只环境的拒 —— 记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。两个写者:家目录(环境答了而与声明不一致)
99
+ * 与恢复点的填充(换一只容器也是同一个答的拒:种子不是项目包、摘要不符、镜像没有 `tar`、镜像预置撞名 —— `FillOutcome.standing`)。 */
100
+ private connectRefusal?;
97
101
  /** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则开容器那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
98
102
  private readonly homeSeat;
99
103
  constructor(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps);
100
104
  private resolve;
105
+ /** 收据句柄。还没连上(懒环境:续跑腿的引擎在恢复相**之前**先取它比车道)⇒ 未连接形 `{ sandboxId: "(unconnected)", provider, mountPath }`(与 k8s / e2b 车道同形),
106
+ * 不抛 —— 抛了,续跑腿在恢复相就失败。 */
101
107
  workspaceHandle(): WorkspaceHandle;
102
108
  connect(config?: RemoteConnectConfig): Promise<{
103
109
  ok: true;
@@ -107,6 +113,10 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
107
113
  error: RemoteExecutionError;
108
114
  }>;
109
115
  private doConnect;
116
+ /** 恢复点的填充:有座 ⇒ 经 {@link fillChannel} 放(容器恒是新起的 ⇒ `minted`;本车道不可挂起);没有 ⇒ 什么都不放。 */
117
+ private fillWorkspace;
118
+ /** 恢复点手里的通道:`docker exec` 经唯一起动接缝(起动形 `adapter`,与引擎命令同一执行用户;连接进行中,不经懒连接的文件面);写 = 容器内 `cat >` 从 stdin 收整份字节。 */
119
+ private fillChannel;
110
120
  /**
111
121
  * 家目录:座要问({@link LaneHomeSeat.asks}:声明在场 ⇒ 对证;没声明 ⇒ 实测)⇒ 在本容器里问一次 `$HOME`(与命令同一条 `docker exec` 通道、同一份 `-e` env),读数交座。
112
122
  * 声明在场:环境答了而不一致 / 不是绝对路径 ⇒ 回拒并记在环境上(同一镜像同一答案:此后每次连接原样回它,不再起容器、不再问);没答上(exec 失败 / 超时 / 非零退出)⇒ 这一次同样不开,
@@ -258,6 +268,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
258
268
  * owns the lifetime and calls `destroy()` (= `docker rm -f`) on task end. Folds the per-task `ctx.taskId`/
259
269
  * `ctx.sessionId` into the container name so two concurrent tasks never share a container.
260
270
  */
261
- export declare function localDockerExecutionEnvFactory(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps): ExecutionEnvFactory;
271
+ export declare function localDockerExecutionEnvFactory(config: Omit<LocalDockerEnvConfig, "workspaceFill"> & {
272
+ readonly workspaceFillOf?: WorkspaceFillOf;
273
+ }, deps?: LocalDockerEnvDeps): ExecutionEnvFactory;
262
274
  export {};
263
275
  //# sourceMappingURL=remote-env-local-docker.d.ts.map
@@ -37,7 +37,7 @@ export class RemoteLocalDockerExecutionEnv {
37
37
  handle;
38
38
  destroyed = false;
39
39
  connecting;
40
- homeRefusal;
40
+ connectRefusal;
41
41
  homeSeat;
42
42
  constructor(config, deps = {}) {
43
43
  if (!config.image || !config.image.trim()) {
@@ -64,17 +64,15 @@ export class RemoteLocalDockerExecutionEnv {
64
64
  return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
65
65
  }
66
66
  workspaceHandle() {
67
- if (!this.handle)
68
- throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
69
- return this.handle;
67
+ return this.handle ?? { sandboxId: "(unconnected)", provider: PROVIDER, mountPath: this.cfg.mountPath };
70
68
  }
71
69
  async connect(config) {
72
70
  if (config?.snapshotId)
73
71
  return unsupported("connect(snapshotId)");
74
72
  if (this.destroyed)
75
73
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
76
- if (this.homeRefusal)
77
- return { ok: false, error: this.homeRefusal };
74
+ if (this.connectRefusal)
75
+ return { ok: false, error: this.connectRefusal };
78
76
  if (config?.abortSignal?.aborted)
79
77
  return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
80
78
  if (this.containerId)
@@ -139,12 +137,38 @@ export class RemoteLocalDockerExecutionEnv {
139
137
  await this.forceRemove().catch(() => { });
140
138
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
141
139
  }
140
+ const filled = await this.fillWorkspace();
141
+ if (!filled.ok || this.destroyed) {
142
+ await this.forceRemove().catch(() => { });
143
+ if (!filled.ok && filled.standing)
144
+ this.connectRefusal = filled.error;
145
+ return { ok: false, error: filled.ok ? new RemoteExecutionError("connect_failed", "execution env destroyed during connect") : filled.error };
146
+ }
142
147
  this.containerId = run.value.stdout.trim() || this.containerName;
143
148
  this.handle = { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
144
149
  if (config?.secrets?.length) {
145
150
  }
146
151
  return ok(this.handle);
147
152
  }
153
+ fillWorkspace() {
154
+ return this.cfg.workspaceFill === undefined ? Promise.resolve({ ok: true }) : this.cfg.workspaceFill.fill(this.fillChannel(), this.cfg.mountPath, "minted");
155
+ }
156
+ fillChannel() {
157
+ const run = async (command, input) => {
158
+ const argv = this.startArgv(ADAPTER_START, command, { shell: "/bin/sh", ...(input !== undefined ? { stdin: true } : {}) });
159
+ if (!argv.ok)
160
+ return { ok: false, error: argv.error.message };
161
+ const r = await this.docker(argv.value, { timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
162
+ return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
163
+ };
164
+ return {
165
+ run: (command) => run(command),
166
+ write: async (abs, bytes) => {
167
+ const r = await run(`cat > ${shellQuote(abs)}`, bytes);
168
+ return !r.ok ? r : r.exitCode === 0 ? { ok: true } : { ok: false, error: r.stderr.trim() || `exit ${r.exitCode}` };
169
+ },
170
+ };
171
+ }
148
172
  async homeVerdict(signal) {
149
173
  if (!this.homeSeat.asks)
150
174
  return undefined;
@@ -157,7 +181,7 @@ export class RemoteLocalDockerExecutionEnv {
157
181
  return undefined;
158
182
  const error = new RemoteExecutionError("connect_failed", verdict.refusal);
159
183
  if (verdict.answered)
160
- this.homeRefusal = error;
184
+ this.connectRefusal = error;
161
185
  return error;
162
186
  }
163
187
  async suspendVM(_options) {
@@ -172,8 +196,8 @@ export class RemoteLocalDockerExecutionEnv {
172
196
  async reconnect(sessionToken) {
173
197
  if (this.destroyed)
174
198
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
175
- if (this.homeRefusal)
176
- return { ok: false, error: this.homeRefusal };
199
+ if (this.connectRefusal)
200
+ return { ok: false, error: this.connectRefusal };
177
201
  if (sessionToken && sessionToken !== this.containerName) {
178
202
  return { ok: false, error: new RemoteExecutionError("connect_failed", `reconnect token '${String(sessionToken)}' does not match this env's container '${this.containerName}'`) };
179
203
  }
@@ -189,8 +213,8 @@ export class RemoteLocalDockerExecutionEnv {
189
213
  const home = this.containerId === undefined ? await this.homeVerdict() : undefined;
190
214
  if (home !== undefined)
191
215
  return { ok: false, error: home };
192
- if (this.destroyed || this.homeRefusal)
193
- return { ok: false, error: this.homeRefusal ?? new RemoteExecutionError("connect_failed", "execution env destroyed during reconnect") };
216
+ if (this.destroyed || this.connectRefusal)
217
+ return { ok: false, error: this.connectRefusal ?? new RemoteExecutionError("connect_failed", "execution env destroyed during reconnect") };
194
218
  this.containerId = this.containerName;
195
219
  this.handle ??= { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
196
220
  return ok(this.handle);
@@ -682,6 +706,10 @@ function errMsg(e) {
682
706
  return e instanceof Error ? e.message : String(e);
683
707
  }
684
708
  export function localDockerExecutionEnvFactory(config, deps = {}) {
685
- return (ctx) => new RemoteLocalDockerExecutionEnv({ ...config, id: config.id ?? ctx.taskId ?? ctx.sessionId }, deps);
709
+ const { workspaceFillOf, ...base } = config;
710
+ return (ctx) => {
711
+ const workspaceFill = workspaceFillOf?.(ctx);
712
+ return new RemoteLocalDockerExecutionEnv({ ...base, id: base.id ?? ctx.taskId ?? ctx.sessionId, ...(workspaceFill !== undefined ? { workspaceFill } : {}) }, deps);
713
+ };
686
714
  }
687
715
  //# sourceMappingURL=remote-env-local-docker.js.map
@@ -387,7 +387,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
387
387
  * 它的域完全靠「属主 sessionId 的墓碑」推。而会话 id 是**调用方可自选**的 ⇒ 两个租户先后用过同一个 id
388
388
  * 完全合法;两边都删过之后,同一个 row_key 上会有**两个域**的墓碑,这时任何一边的 sweep 拿自己的墓碑
389
389
  * 去认领这些字节都只是猜。判据因此是:**存在第二个域的同名墓碑 ⇒ 这批行不可归属 ⇒ 一根不删**,
390
- * 实体清理退回既有的 TTL 腿(`reapOlderThan`)。少删一次是可接受的;删掉另一个租户的字节、还跳过它的
390
+ * 实体清理退回既有的 TTL 腿(core `ToolResultStore.reap`)。少删一次是可接受的;删掉另一个租户的字节、还跳过它的
391
391
  * legal hold 与审计域,不可接受。
392
392
  */
393
393
  protected orphanToolResultCandidates(exec: {
@@ -475,7 +475,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
475
475
  * 也只计 `skipped` 不删。会话本体的清理归 {@link SqlRetentionStore.deleteExpiredSessions}(它会连着
476
476
  * 这行一起删),两条腿因此不重叠也不互相抢行。
477
477
  * · **一根不碰的那一支**:`owner_session_id IS NULL` 的**无出处**行。没有出处就没有域,按任何域去删它
478
- * 都是猜;实体清理归既有的 TTL 腿(`reapOlderThan`,`boot/reapers.ts` 驱动)。这与 core 的
478
+ * 都是猜;实体清理归既有的 TTL 腿(core `ToolResultStore.reap`,`boot/reapers.ts` 驱动)。这与 core 的
479
479
  * 「an UNOWNED entry is never deleted」是同一条纪律的两处执行点。
480
480
  *
481
481
  * 事务形与另外两只方法逐字同款(hold 哨兵行锁读 → 删除+墓碑 → 审计行,同 commit 同 rollback)。
@@ -2,7 +2,7 @@ import type { TaskStatus } from "@sema-agent/core";
2
2
  import type { Pool as MySqlPool } from "mysql2/promise";
3
3
  import type { Pool as PgPool } from "pg";
4
4
  import { type UsageRow } from "../usage-analytics.js";
5
- import { type ClaimFenced, type CreateRunOutcome, type FencedRun, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
5
+ import { type ClaimFenced, type CreateRunOutcome, type FencedRun, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef, type SettledLegMemoryObservation } from "./store-contracts.js";
6
6
  import { type SqlDriver, type SqlTxConn } from "./sql-driver.js";
7
7
  import type { LedgerEventType } from "../trace/ledger-events.js";
8
8
  import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
@@ -228,6 +228,14 @@ export declare class SqlRunStore {
228
228
  * 两方言只差占位符(动态子句为零,不像 listRuns/listSessions 那样需要各自的构造器臂)。
229
229
  */
230
230
  latestRunForSession(sessionId: string): Promise<LatestRunRef | undefined>;
231
+ /**
232
+ * [ref] —— 这个会话**最近一条已结腿**与它的记忆观测(规则属主 = `store-contracts.ts` 的 {@link SettledLegMemoryObservation};
233
+ * 文件 / 内存两店是它的孪生)。排序口径与 {@link latestRunForSession} 逐字同一句;已结词集按本文件头注的惯例写字面(两方言并排可读),
234
+ * 与属主谓词 `isTerminalRunStatus` 的同源由方言对 parity 门钉(`test/source-hygiene-gate.test.ts` 的语句规则 `settled-leg-observation-read`)。
235
+ * 同一条索引路径(`idx_task_run_session_status` 的 `(session_id, status)` 前缀),**不新加索引**。
236
+ * 结果列走 {@link rowToRun} 同一只解码口与同一个失败方向(读不出 ⇒ 拒,不把一条结了的腿读成「没有观测」)。
237
+ */
238
+ latestMemoryObservationForSession(sessionId: string): Promise<SettledLegMemoryObservation | undefined>;
231
239
  /** GET /v1/sessions (CC /resume picker): DISTINCT sessions newest-first by last activity, keyset-paginated on
232
240
  * (last_activity, session_id). Aggregates task_run by session_id — one window pass picks the latest run per
233
241
  * session (rn=1) for the preview/status and rolls up first/last/count. Owner-scoped when set (the per-user view).
@@ -6,7 +6,7 @@ import { pgSanitizeText } from "./pg-safe-json.js";
6
6
  import { toIso as iso } from "./sql-row-helpers.js";
7
7
  import { recordFailOpen } from "../observability/fail-open.js";
8
8
  import { createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonObjectColumn, readNonNullJsonValueColumn } from "./sql-json-column.js";
9
- import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, readLegacyHolderDeathCode } from "./store-contracts.js";
9
+ import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, readLegacyHolderDeathCode, settledLegObservationOf } from "./store-contracts.js";
10
10
  import { notifyRunTerminal, runTerminalLogLevel } from "../observability/run-terminal.js";
11
11
  import { mysqlDriver, pgDriver, dialectJsonEncoder, SQL_DB_NOW_MS, sqlDbNowMs } from "./sql-driver.js";
12
12
  import { isDupKeyError } from "./sql-errors.js";
@@ -413,6 +413,14 @@ export class SqlRunStore {
413
413
  const r = rows[0];
414
414
  return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
415
415
  }
416
+ async latestMemoryObservationForSession(sessionId) {
417
+ const { rows } = await this.db.query(this.q("SELECT task_id, " + jsonColumnText("mysql", "result") + " FROM task_run WHERE session_id = ? AND status IN ('completed','failed','blocked') ORDER BY created_at DESC, task_id DESC LIMIT 1", "SELECT task_id, " + jsonColumnText("pg", "result") + " FROM task_run WHERE session_id = $1 AND status IN ('completed','failed','blocked') ORDER BY created_at DESC, task_id DESC LIMIT 1"), [sessionId]);
418
+ const r = rows[0];
419
+ if (r === undefined)
420
+ return undefined;
421
+ const taskId = String(r.task_id);
422
+ return settledLegObservationOf(taskId, refusing("read a settled leg as if it had observed no memory", () => decodeResultColumn(r.result, taskId)) ?? null);
423
+ }
416
424
  async listSessions(opts) {
417
425
  if (this.db.dialect === "mysql") {
418
426
  const params = [];
@@ -5,11 +5,9 @@ export declare const READ_FLOOR_UNAVAILABLE_REASONS: readonly ["arch", "user_not
5
5
  export type ReadFloorUnavailableReason = (typeof READ_FLOOR_UNAVAILABLE_REASONS)[number];
6
6
  /** F2:附着不过的环境上,带地板起动的拒句(纯字符串 ⇒ `build*`)。首句点名原因词;点名表的首行与旋钮。 */
7
7
  export declare function buildReadFloorUnavailableMessage(reason: ReadFloorUnavailableReason, detail: string | undefined, floor: ReadFloor | undefined): string;
8
- /** F6:守地板的环境上,不带地板的引擎起动的拒句。 */
9
- export declare function buildReadFloorMissingMessage(lane: SandboxFloorLane): string;
10
8
  /**
11
9
  * 一次起动是什么(闭集,调用方必须表态 —— 没有缺省臂):
12
- * · `engine` —— 经执行环境公共起动口要起的命令(引擎的,或替引擎干活的服务端调用方的);带不带地板是它交来的事实。`hold` = 适配器之后还会以
10
+ * · `engine` —— 经执行环境公共起动口要起的命令(引擎的,或替引擎干活的服务端调用方的);它带的地板就是它的表,缺席 = 表无行。`hold` = 适配器之后还会以
13
11
  * 同一起动号再起 `run`(k8s 后台作业的轮询 / 停 / 收尾读的是这次起动的产物,按这次起动的地板跑),登记留到 {@link SandboxReadFloor.release};
14
12
  * · `under` —— 以一次已登记(`hold`)起动的号再起一只 `run`(上面那类轮询);
15
13
  * · `adapter` —— 适配器自己的固定命令(文件面助手、探针、数据搬运、全杀);附着成功之后经监督者的 `plain` 起;
@@ -29,7 +27,7 @@ export type FloorStart = {
29
27
  };
30
28
  export declare const ADAPTER_START: FloorStart;
31
29
  export declare const BOOTSTRAP_START: FloorStart;
32
- /** 引擎起动:`readFloor` 原样交(`undefined` = 这次起动不带地板)。 */
30
+ /** 引擎起动:`readFloor` 原样交(`undefined` = 这次起动的表一行都没有)。 */
33
31
  export declare function engineStart(readFloor: ReadFloor | undefined, hold?: boolean): FloorStart;
34
32
  /** 起动身份:`root` = 车道以 root 起(e2b `user:"root"`、local-docker `-u 0`);`exec-user` = 车道自己的执行身份(改前的形)。 */
35
33
  export type FloorLaunchIdentity = "root" | "exec-user";
@@ -125,8 +123,8 @@ export interface RelayAnswer {
125
123
  export declare const UNJUDGED_VERDICT = "read floor: no verdict for this start";
126
124
  /** 判官:适配器进程里唯一回答「这条路径拒不拒」的东西。live object ⇒ `create*`。 */
127
125
  export interface FloorJudge {
128
- /** 登记一次带地板的起动,回它的起动号(随机、不可猜)。`held` ⇒ 之后还会以这个号再起 `run`(`under`),直到 {@link release};否则这一次就是它唯一的起动。 */
129
- register(floor: ReadFloor, held: boolean): string;
126
+ /** 登记一次引擎起动(带它的表;`undefined` = 表无行),回它的起动号(随机、不可猜)。`held` ⇒ 之后还会以这个号再起 `run`(`under`),直到 {@link release};否则这一次就是它唯一的起动。 */
127
+ register(floor: ReadFloor | undefined, held: boolean): string;
130
128
  /** 以这个号再起一次 `run`(`under`)之前问:在册且适配器没说过「不再起」⇒ `true`(记下又有一只监督者将连上);否则 `false`(起动拒)。 */
131
129
  launch(startId: string): boolean;
132
130
  /** 适配器不会再以这个号起 `run` 了。登记要两件都到才撤(协议 §5.5):这一句 ∧ 此后的 `gone` —— `gone` 已在 ⇒ 当即撤;否则等它(还连着的监督者照旧有判官答)。 */
@@ -202,7 +200,7 @@ export interface SandboxReadFloor {
202
200
  readonly state: SandboxReadFloorState;
203
201
  /** 这只模块钉住的监督者二进制(守地板 ⇒ 在;裸模块 ⇒ 缺席)。k8s 的 pod 座({@link buildFloorPodSeat})只读它 —— 放进 pod 的与自检核的是同一份钉。 */
204
202
  readonly binaries: FloorBinaries | undefined;
205
- /** 唯一接缝:一次起动 → 答案,或 `spawn_error`(F2 / F6 / 时机不对的引导起动 / 已撤的号)。 */
203
+ /** 唯一接缝:一次起动 → 答案,或 `spawn_error`(F2 / 时机不对的引导起动 / 已撤的号)。 */
206
204
  wrap(start: FloorStart): Result<FloorLaunch, ExecutionError>;
207
205
  /** 适配器不会再以这个号起 `run`(`hold` 起动的收尾)。登记在其后的 `gone` 到时撤(还连着的监督者照旧有判官答)。 */
208
206
  release(startId: string | undefined): void;
@@ -35,11 +35,6 @@ export function buildReadFloorUnavailableMessage(reason, detail, floor) {
35
35
  `This command carries a read-deny table${first !== undefined ? ` (first row "${first}")` : ""} this sandbox cannot keep, so it is refused rather than run with fewer rows. ` +
36
36
  `To run this lane without the byte floor, set ${SANDBOX_READ_FLOOR_ENV}=off (the lane then declares keepsReadFloor=false: each run with read-deny rows is announced, or refused when READ_DENY_FLOOR_REQUIRED=true).`);
37
37
  }
38
- export function buildReadFloorMissingMessage(lane) {
39
- return (`read floor: this ${lane} sandbox keeps the read-deny byte floor, and this command start carries no floor — it is refused rather than run a process the floor does not judge ` +
40
- `(every start on a lane that keeps the floor must carry the run's read-deny table). ` +
41
- `To run this lane without the byte floor, set ${SANDBOX_READ_FLOOR_ENV}=off.`);
42
- }
43
38
  export const ADAPTER_START = Object.freeze({ kind: "adapter" });
44
39
  export const BOOTSTRAP_START = Object.freeze({ kind: "bootstrap" });
45
40
  export function engineStart(readFloor, hold = false) {
@@ -121,6 +116,8 @@ export function parseRelayFrame(line) {
121
116
  }
122
117
  export const UNJUDGED_VERDICT = "read floor: no verdict for this start";
123
118
  function judgeOne(floor, path) {
119
+ if (floor === undefined)
120
+ return null;
124
121
  let v;
125
122
  try {
126
123
  v = floor.denies(path);
@@ -433,8 +430,6 @@ export function createKeptSandboxReadFloor(opts) {
433
430
  }
434
431
  case "engine": {
435
432
  const floor = start.readFloor;
436
- if (floor === undefined)
437
- return spawnError(buildReadFloorMissingMessage(lane));
438
433
  if (phase.kind === "unavailable")
439
434
  return spawnError(buildReadFloorUnavailableMessage(phase.reason, phase.detail, floor));
440
435
  if (phase.kind !== "ready" || bin === undefined)
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * SQL 驱动错误的**方言判别**——「这是不是唯一键冲突」的单一属主([ref] P1-①,病族
3
3
  * single-semantic-multi-site-drift)。`sql-escape.ts` / `sql-row-helpers.ts` / `store-contracts.ts`
4
- * 的同级叶子:零 store 逻辑、零驱动依赖(不 import mysql2/pg),纯谓词。
4
+ * 的同级叶子:零 store 逻辑、零驱动依赖(不 import mysql2/pg),纯谓词 —— 外加死锁判据唯一的消费形 {@link retryOnDeadlock}
5
+ * (「撞死锁重来几次」与「什么算死锁」住在一处:上界只有一个数,不在各店各写一个)。
5
6
  *
6
7
  * ## 为什么必须只有一份
7
8
  * 收编前这条谓词在 src/ 手铸 ≥7 处,而且**识别集互相漂移**:一半站点只认 `code === "ER_DUP_ENTRY"`,
@@ -96,6 +97,14 @@ export declare const PG_DEADLOCK_DETECTED_SQLSTATE = "40P01";
96
97
  * **MySQL 协议腿**,两方言都认只是为了让店不必自己判引擎。
97
98
  */
98
99
  export declare function isDeadlockError(dialect: SqlDialect, err: unknown): boolean;
100
+ /** 引擎仲裁之后整段重来的**唯一**上界(全仓每一条「撞死锁就重来」的腿共用这一个数)。8 = 锁形并发套的并发度:缺行首写的 N 路竞争每轮至多多走一轮([ref] 读数 1 成 7 死锁)。 */
101
+ export declare const DEADLOCK_RETRY_LIMIT = 8;
102
+ /**
103
+ * 「引擎判了死锁 ⇒ 整段重来」的**唯一**写法:有界({@link DEADLOCK_RETRY_LIMIT}),判据 {@link isDeadlockError};用尽 / 其它错误原样抛。
104
+ * `attempt` 必须整段重做语义等价(读-判-写全在里面的一只事务,或一条幂等语句)。`alsoArbitrated`:店自己认的另一种仲裁产物(例:缺行首写撞主键 ——
105
+ * 并发的另一只首写赢了,重读即走更新腿),与死锁同族、同一个上界。锁等待超时不在任何一族里(延迟信号,见 {@link isDeadlockError} 的注):它照抛。
106
+ */
107
+ export declare function retryOnDeadlock<T>(dialect: SqlDialect, attempt: () => Promise<T>, alsoArbitrated?: (err: unknown) => boolean): Promise<T>;
99
108
  /** TiDB `ErrEntryTooLarge` (one row's key + value over `txn-entry-size-limit`, 6 MiB by default) — TiDB's own
100
109
  * errno, which mysql2 carries without a symbolic name. */
101
110
  export declare const TIDB_ER_ENTRY_TOO_LARGE_ERRNO = 8025;