@sema-agent/server 7.139.0 → 7.141.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 (328) hide show
  1. package/MIGRATION.md +471 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +94 -26
  5. package/dist/adoption/runner.js +1 -1
  6. package/dist/adoption/sql.js +1 -1
  7. package/dist/approval-batch-census.d.ts +27 -0
  8. package/dist/approval-batch-census.js +27 -0
  9. package/dist/approval-card.d.ts +37 -22
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval.d.ts +15 -0
  12. package/dist/approval.js +10 -0
  13. package/dist/audit.d.ts +4 -2
  14. package/dist/audit.js +2 -2
  15. package/dist/bench/s1/live-deps.js +3 -2
  16. package/dist/bench/s1/row.d.ts +5 -2
  17. package/dist/bench/s1/row.js +2 -22
  18. package/dist/boot/adoption.js +1 -1
  19. package/dist/boot/device-lane.d.ts +11 -0
  20. package/dist/boot/device-lane.js +16 -0
  21. package/dist/boot/engine-lease.d.ts +24 -3
  22. package/dist/boot/engine-lease.js +9 -2
  23. package/dist/boot/execution-env.js +18 -12
  24. package/dist/boot/execution-lane.d.ts +8 -0
  25. package/dist/boot/execution-lane.js +6 -1
  26. package/dist/boot/memory-boundary.d.ts +16 -53
  27. package/dist/boot/memory-boundary.js +10 -20
  28. package/dist/boot/memory-consolidation.d.ts +16 -12
  29. package/dist/boot/memory-consolidation.js +24 -13
  30. package/dist/boot/reapers.d.ts +2 -2
  31. package/dist/boot/resolve-spec.d.ts +3 -5
  32. package/dist/boot/resolve-spec.js +9 -8
  33. package/dist/boot/runner-deps.d.ts +3 -3
  34. package/dist/boot/runner-deps.js +1 -0
  35. package/dist/boot/session-faces.d.ts +1 -1
  36. package/dist/boot/session-faces.js +4 -3
  37. package/dist/boot/shutdown.js +5 -0
  38. package/dist/boot/stage-03-store-backend.d.ts +2 -6
  39. package/dist/boot/stage-03-store-backend.js +2 -3
  40. package/dist/boot/stage-04-stores.d.ts +2 -6
  41. package/dist/boot/stage-04-stores.js +5 -5
  42. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  43. package/dist/boot/stage-05-execution-env.js +3 -1
  44. package/dist/boot/stage-06-runners.d.ts +2 -8
  45. package/dist/boot/stage-06-runners.js +3 -3
  46. package/dist/boot/stage-07-capability-layer.d.ts +2 -7
  47. package/dist/boot/stage-07-capability-layer.js +8 -10
  48. package/dist/boot/stage-08-reapers.d.ts +2 -7
  49. package/dist/boot/stage-09-leader.d.ts +2 -7
  50. package/dist/boot/stage-10-http-server.d.ts +2 -7
  51. package/dist/boot/stage-10-http-server.js +6 -4
  52. package/dist/boot/stores.d.ts +3 -6
  53. package/dist/boot/stores.js +37 -44
  54. package/dist/boot/task-list-lane.d.ts +1 -1
  55. package/dist/boot/task-list-lane.js +1 -1
  56. package/dist/boot/web-admin.js +36 -9
  57. package/dist/capabilities/execution-lane.d.ts +2 -2
  58. package/dist/capabilities/scenarios.d.ts +1 -1
  59. package/dist/capability-facts.d.ts +9 -0
  60. package/dist/capability-facts.js +9 -0
  61. package/dist/card-void.d.ts +38 -0
  62. package/dist/card-void.js +23 -0
  63. package/dist/config-catalog.js +12 -8
  64. package/dist/config-provider.d.ts +18 -17
  65. package/dist/config-provider.js +28 -36
  66. package/dist/config-types.d.ts +36 -9
  67. package/dist/config.js +56 -46
  68. package/dist/delegated-session-owner.d.ts +23 -20
  69. package/dist/delegated-session-owner.js +2 -2
  70. package/dist/deployment-shape.d.ts +9 -8
  71. package/dist/deployment-shape.js +4 -4
  72. package/dist/device-store.d.ts +7 -0
  73. package/dist/device-store.js +3 -0
  74. package/dist/execution-config-root.d.ts +22 -0
  75. package/dist/execution-config-root.js +35 -0
  76. package/dist/execution-lane-caps.d.ts +145 -107
  77. package/dist/execution-lane-caps.js +144 -83
  78. package/dist/execution-placement.d.ts +3 -0
  79. package/dist/execution-placement.js +7 -0
  80. package/dist/fleet/drain-entry.d.ts +3 -0
  81. package/dist/hosted-posture.d.ts +8 -0
  82. package/dist/hosted-posture.js +4 -0
  83. package/dist/http/admission.js +3 -3
  84. package/dist/http/caller-trust.d.ts +3 -2
  85. package/dist/http/caller-trust.js +2 -1
  86. package/dist/http/dispatch.js +4 -0
  87. package/dist/http/live-frame.d.ts +24 -0
  88. package/dist/http/live-frame.js +16 -0
  89. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  90. package/dist/http/memory-compliance-failure-reply.js +5 -0
  91. package/dist/http/memory-layer-reply.d.ts +30 -0
  92. package/dist/http/memory-layer-reply.js +24 -0
  93. package/dist/http/resume-legs.js +49 -35
  94. package/dist/http/retired-service-deps.d.ts +1 -1
  95. package/dist/http/retired-service-deps.js +1 -1
  96. package/dist/http/route-table.js +2 -0
  97. package/dist/http/routes/adoption.js +1 -1
  98. package/dist/http/routes/approvals-assistant.js +1 -0
  99. package/dist/http/routes/capabilities.js +5 -4
  100. package/dist/http/routes/diagnostics.d.ts +0 -19
  101. package/dist/http/routes/diagnostics.js +16 -2
  102. package/dist/http/routes/memory-bundle.d.ts +2 -2
  103. package/dist/http/routes/memory-bundle.js +4 -1
  104. package/dist/http/routes/memory-compliance.js +9 -2
  105. package/dist/http/routes/memory-consolidation.js +12 -1
  106. package/dist/http/routes/memory-origin.js +14 -2
  107. package/dist/http/routes/memory-policy.js +7 -2
  108. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  109. package/dist/http/routes/run-permission-mode.js +170 -0
  110. package/dist/http/routes/session-sync.d.ts +3 -6
  111. package/dist/http/routes/session-sync.js +7 -2
  112. package/dist/http/routes/sessions.js +33 -15
  113. package/dist/http/routes/tasks.js +67 -68
  114. package/dist/http/run-read-face.js +3 -3
  115. package/dist/http/send.d.ts +1 -1
  116. package/dist/http/send.js +2 -2
  117. package/dist/http/server.d.ts +14 -27
  118. package/dist/http/server.js +2 -2
  119. package/dist/http/sse-log.d.ts +3 -8
  120. package/dist/http/stored-body-replay.d.ts +3 -3
  121. package/dist/http/stored-body-replay.js +3 -1
  122. package/dist/identity-source.d.ts +1 -1
  123. package/dist/identity-source.js +1 -1
  124. package/dist/instruction-source-notice.d.ts +7 -0
  125. package/dist/instruction-source-notice.js +6 -6
  126. package/dist/lane-home-probe.d.ts +68 -42
  127. package/dist/lane-home-probe.js +53 -30
  128. package/dist/leader/wire.js +5 -4
  129. package/dist/mcp-assertion-header.d.ts +4 -0
  130. package/dist/mcp-assertion-header.js +5 -0
  131. package/dist/memory-bundle-engine.d.ts +16 -9
  132. package/dist/memory-bundle-engine.js +29 -6
  133. package/dist/memory-operator-faces.d.ts +85 -30
  134. package/dist/memory-operator-faces.js +136 -30
  135. package/dist/memory-posture.d.ts +1 -1
  136. package/dist/memory-project-layer.d.ts +3 -21
  137. package/dist/memory-project-layer.js +11 -8
  138. package/dist/memory-scope-stores.d.ts +73 -0
  139. package/dist/memory-scope-stores.js +86 -0
  140. package/dist/memory-scope.d.ts +33 -9
  141. package/dist/memory-scope.js +17 -5
  142. package/dist/memory-sync-client.d.ts +34 -9
  143. package/dist/memory-sync-client.js +108 -28
  144. package/dist/observability/fail-open.d.ts +30 -2
  145. package/dist/observability/fail-open.js +30 -2
  146. package/dist/observability/metrics.js +0 -1
  147. package/dist/observability/run-terminal.d.ts +6 -2
  148. package/dist/observability/run-terminal.js +25 -26
  149. package/dist/observability/secret-env-scrub.d.ts +20 -1
  150. package/dist/observability/secret-env-scrub.js +14 -0
  151. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  152. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  153. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  154. package/dist/orchestration/workflow-notify-journal.js +2 -1
  155. package/dist/permission-mode-admission.d.ts +68 -0
  156. package/dist/permission-mode-admission.js +23 -0
  157. package/dist/permission-rule-list.d.ts +55 -6
  158. package/dist/permission-rule-list.js +52 -5
  159. package/dist/plugins/adoption-log-sql.js +4 -4
  160. package/dist/plugins/approval-ask-store-sql.js +14 -14
  161. package/dist/plugins/approval-nonce-store.js +4 -4
  162. package/dist/plugins/blob-backend.d.ts +4 -4
  163. package/dist/plugins/blob-backend.js +7 -7
  164. package/dist/plugins/breaker-state-sql.js +2 -2
  165. package/dist/plugins/caching-session-store.d.ts +6 -0
  166. package/dist/plugins/caching-session-store.js +3 -0
  167. package/dist/plugins/checkpoint-index-columns.js +2 -2
  168. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  169. package/dist/plugins/checkpoint-store-sql.js +90 -19
  170. package/dist/plugins/device-store-sql.d.ts +5 -2
  171. package/dist/plugins/device-store-sql.js +23 -20
  172. package/dist/plugins/ensure-index.d.ts +1 -2
  173. package/dist/plugins/execution-placement-sql.d.ts +19 -9
  174. package/dist/plugins/execution-placement-sql.js +20 -9
  175. package/dist/plugins/file-history-store-sql.d.ts +2 -2
  176. package/dist/plugins/file-history-store-sql.js +4 -4
  177. package/dist/plugins/file-run-store.d.ts +2 -2
  178. package/dist/plugins/file-run-store.js +1 -1
  179. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  180. package/dist/plugins/fork-routing-session-store.js +2 -61
  181. package/dist/plugins/image-bake-store-sql.js +9 -9
  182. package/dist/plugins/image-index-sql.js +11 -11
  183. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  184. package/dist/plugins/k8s-exec-protocol.js +6 -1
  185. package/dist/plugins/leader-run-store-sql.js +4 -4
  186. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  187. package/dist/plugins/local-checkpoint-store.js +22 -0
  188. package/dist/plugins/local-session-store.d.ts +227 -35
  189. package/dist/plugins/local-session-store.js +518 -37
  190. package/dist/plugins/local-session-window.d.ts +17 -3
  191. package/dist/plugins/local-session-window.js +7 -13
  192. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  193. package/dist/plugins/mailbox-store-sql.js +4 -4
  194. package/dist/plugins/memory-engine-tidb.js +5 -5
  195. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  196. package/dist/plugins/memory-run-store.d.ts +2 -2
  197. package/dist/plugins/memory-run-store.js +1 -1
  198. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  199. package/dist/plugins/one-time-migrations.d.ts +52 -11
  200. package/dist/plugins/one-time-migrations.js +101 -39
  201. package/dist/plugins/org-delivery-quota-store.js +2 -2
  202. package/dist/plugins/outcome-ledger-sql.js +2 -2
  203. package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
  204. package/dist/plugins/permission-rule-store-sql.js +16 -16
  205. package/dist/plugins/pg-pool.js +3 -2
  206. package/dist/plugins/pg-session-storage.d.ts +2 -0
  207. package/dist/plugins/pg-session-storage.js +3 -2
  208. package/dist/plugins/remote-env-adb.d.ts +7 -24
  209. package/dist/plugins/remote-env-adb.js +22 -5
  210. package/dist/plugins/remote-env-device.d.ts +2 -28
  211. package/dist/plugins/remote-env-device.js +2 -1
  212. package/dist/plugins/remote-env-e2b.d.ts +40 -27
  213. package/dist/plugins/remote-env-e2b.js +180 -20
  214. package/dist/plugins/remote-env-file-error.d.ts +1 -1
  215. package/dist/plugins/remote-env-file-error.js +8 -2
  216. package/dist/plugins/remote-env-host.d.ts +19 -35
  217. package/dist/plugins/remote-env-host.js +26 -3
  218. package/dist/plugins/remote-env-k8s.d.ts +22 -14
  219. package/dist/plugins/remote-env-k8s.js +150 -26
  220. package/dist/plugins/remote-env-local-docker.d.ts +27 -24
  221. package/dist/plugins/remote-env-local-docker.js +91 -24
  222. package/dist/plugins/remote-env-ssh.d.ts +4 -24
  223. package/dist/plugins/remote-env-ssh.js +4 -3
  224. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  225. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  226. package/dist/plugins/retention-lane-store-sql.js +2 -2
  227. package/dist/plugins/retention-store-sql.d.ts +1 -1
  228. package/dist/plugins/retention-store-sql.js +6 -6
  229. package/dist/plugins/run-store-sql.d.ts +3 -3
  230. package/dist/plugins/run-store-sql.js +19 -18
  231. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  232. package/dist/plugins/sandbox-read-floor.js +608 -0
  233. package/dist/plugins/schema-consistency.d.ts +6 -6
  234. package/dist/plugins/schema-consistency.js +1 -1
  235. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  236. package/dist/plugins/schema-ensure-legs.js +2 -2
  237. package/dist/plugins/schema-live-columns.d.ts +1 -1
  238. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  239. package/dist/plugins/session-ownership.d.ts +2 -1
  240. package/dist/plugins/session-placement.d.ts +17 -4
  241. package/dist/plugins/session-placement.js +2 -1
  242. package/dist/plugins/session-rules-column.d.ts +1 -1
  243. package/dist/plugins/session-rules-column.js +1 -1
  244. package/dist/plugins/session-store.d.ts +18 -20
  245. package/dist/plugins/session-store.js +4 -12
  246. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  247. package/dist/plugins/session-workspace-cell.js +1 -1
  248. package/dist/plugins/shared-memory-store-sql.js +2 -2
  249. package/dist/plugins/sql-driver.d.ts +10 -5
  250. package/dist/plugins/sql-driver.js +6 -6
  251. package/dist/plugins/sql-errors.js +1 -1
  252. package/dist/plugins/store-backend.d.ts +35 -6
  253. package/dist/plugins/store-backend.js +27 -10
  254. package/dist/plugins/store-contracts.d.ts +9 -2
  255. package/dist/plugins/task-list-store-sql.js +11 -11
  256. package/dist/plugins/tidb-pool.js +7 -6
  257. package/dist/plugins/tidb-session-storage.js +4 -4
  258. package/dist/plugins/tidb-session-store.d.ts +4 -1
  259. package/dist/plugins/tidb-session-store.js +16 -15
  260. package/dist/plugins/tool-result-store-sql.d.ts +2 -2
  261. package/dist/plugins/tool-result-store-sql.js +3 -3
  262. package/dist/plugins/user-input-ledger-sql.js +2 -2
  263. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  264. package/dist/plugins/workflow-run-store-sql.js +19 -19
  265. package/dist/plugins/write-behind-counter.d.ts +2 -3
  266. package/dist/plugins/write-behind-counter.js +5 -5
  267. package/dist/resource-suspend.d.ts +14 -12
  268. package/dist/resource-suspend.js +1 -1
  269. package/dist/resource-window.d.ts +4 -3
  270. package/dist/run-cancel-context.d.ts +34 -0
  271. package/dist/run-cancel-context.js +7 -0
  272. package/dist/run-local.js +14 -19
  273. package/dist/runs.d.ts +47 -16
  274. package/dist/runs.js +112 -113
  275. package/dist/security.d.ts +2 -2
  276. package/dist/server-secret-env.d.ts +2 -1
  277. package/dist/server-secret-env.js +2 -4
  278. package/dist/session-error-wire.d.ts +22 -4
  279. package/dist/session-error-wire.js +16 -0
  280. package/dist/session-incarnation.d.ts +10 -4
  281. package/dist/session-incarnation.js +1 -1
  282. package/dist/session-policy-wire.d.ts +1 -1
  283. package/dist/session-sync-kernel.d.ts +42 -1
  284. package/dist/session-sync-kernel.js +17 -0
  285. package/dist/session-sync.js +16 -4
  286. package/dist/sql-engine-coordinates.d.ts +49 -0
  287. package/dist/sql-engine-coordinates.js +27 -0
  288. package/dist/sql-engine-posture.d.ts +1 -1
  289. package/dist/sql-engine-posture.js +1 -1
  290. package/dist/standing-answer-grant.d.ts +31 -2
  291. package/dist/standing-answer-grant.js +14 -2
  292. package/dist/store-posture.d.ts +1 -1
  293. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  294. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  295. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  296. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  297. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  298. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  299. package/dist/task-a2a.d.ts +1 -1
  300. package/dist/task-a2a.js +5 -6
  301. package/dist/task-mcp.d.ts +21 -8
  302. package/dist/task-mcp.js +17 -9
  303. package/dist/task-settings.d.ts +52 -25
  304. package/dist/task-settings.js +91 -28
  305. package/dist/terminal.d.ts +103 -19
  306. package/dist/terminal.js +72 -20
  307. package/dist/trace/core-keyset-guard.d.ts +79 -2
  308. package/dist/trace/exit-seat.d.ts +7 -0
  309. package/dist/trace/exit-seat.js +1 -0
  310. package/dist/trace/model-segment.d.ts +2 -2
  311. package/dist/trace/redact.d.ts +3 -1
  312. package/dist/trace/redact.js +395 -110
  313. package/dist/trace/self-minted-wire.d.ts +226 -2
  314. package/dist/trace/self-minted-wire.js +79 -0
  315. package/dist/verifier-pause-card.d.ts +8 -0
  316. package/dist/verifier-pause-card.js +19 -0
  317. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  318. package/dist/web-admin-client/apply-effective.js +2 -2
  319. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  320. package/dist/web-admin-client/candidate-gate.js +1 -1
  321. package/dist/web-admin-client/http-client.d.ts +5 -4
  322. package/dist/web-admin-client/http-client.js +2 -2
  323. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  324. package/dist/web-admin-client/read-warnings.js +5 -9
  325. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  326. package/package.json +2 -2
  327. package/wire-changes/server@7.140.0.json +184 -0
  328. package/wire-changes/server@7.141.0.json +367 -0
@@ -8,6 +8,8 @@ import { signalNumber } from "./host-platform.js";
8
8
  import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
9
9
  import { buildCapturedOutput, createCutError } from "../exec-cut.js";
10
10
  import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
11
+ import { capabilitiesFor } from "../execution-lane-caps.js";
12
+ import { ADAPTER_START, BOOTSTRAP_START, createSandboxReadFloor, dockerIdentityFlags, engineStart, floorArgv, } from "./sandbox-read-floor.js";
11
13
  import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
12
14
  import { createPosixShellFs } from "./posix-shell-fs.js";
13
15
  const PROVIDER = "local-docker";
@@ -20,7 +22,8 @@ const unsupported = (op) => ({
20
22
  error: new RemoteExecutionError("unsupported", `${op} is not supported on a local-docker target (v1: a container is an isolation boundary but not a managed memory snapshot; capabilities.suspendable=false)`),
21
23
  });
22
24
  export class RemoteLocalDockerExecutionEnv {
23
- capabilities = { isolation: true, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
25
+ capabilities;
26
+ floor;
24
27
  cwd;
25
28
  get homeDir() {
26
29
  return this.homeSeat.home;
@@ -49,8 +52,10 @@ export class RemoteLocalDockerExecutionEnv {
49
52
  controlTimeoutMs: config.controlTimeoutMs ?? DEFAULT_CONTROL_TIMEOUT_MS,
50
53
  };
51
54
  this.cwd = this.cfg.mountPath;
52
- this.homeSeat = new LaneHomeSeat({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
55
+ this.homeSeat = new LaneHomeSeat(PROVIDER, config);
53
56
  this.spawnFn = deps.spawn ?? spawn;
57
+ this.floor = deps.readFloor ?? createSandboxReadFloor({ lane: PROVIDER, knob: config.sandboxReadFloor });
58
+ this.capabilities = capabilitiesFor({ remoteExec: { provider: PROVIDER }, sandboxReadFloor: config.sandboxReadFloor });
54
59
  const id = config.id ?? randomBytes(6).toString("hex");
55
60
  const suffix = randomBytes(4).toString("hex");
56
61
  this.containerName = `sema-docker-${sanitizeId(id)}-${suffix}`;
@@ -129,6 +134,7 @@ export class RemoteLocalDockerExecutionEnv {
129
134
  await this.forceRemove().catch(() => { });
130
135
  return { ok: false, error: home };
131
136
  }
137
+ await this.floor.attach(this.floorTransport(), { origin: "created", workspaceRoot: this.cfg.mountPath });
132
138
  if (this.destroyed) {
133
139
  await this.forceRemove().catch(() => { });
134
140
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
@@ -142,7 +148,8 @@ export class RemoteLocalDockerExecutionEnv {
142
148
  async homeVerdict(signal) {
143
149
  if (!this.homeSeat.asks)
144
150
  return undefined;
145
- const probe = await this.docker(this.execRaw(LANE_HOME_PROBE_COMMAND), { signal, timeoutMs: this.cfg.controlTimeoutMs });
151
+ const argv = this.startArgv(ADAPTER_START, LANE_HOME_PROBE_COMMAND);
152
+ const probe = argv.ok ? await this.docker(argv.value, { signal, timeoutMs: this.cfg.controlTimeoutMs }) : argv;
146
153
  if (!probe.ok && probe.error.code === "aborted")
147
154
  return new RemoteExecutionError("aborted", "connect aborted while reading the environment's home");
148
155
  const verdict = this.homeSeat.settle(probe.ok && probe.value.exitCode === 0 ? { ok: true, stdout: probe.value.stdout } : { ok: false, reason: probe.ok ? `exit ${probe.value.exitCode}` : probe.error.message });
@@ -198,6 +205,7 @@ export class RemoteLocalDockerExecutionEnv {
198
205
  this.connecting = undefined;
199
206
  this.containerId = undefined;
200
207
  await inflight?.catch(() => { });
208
+ this.floor.dispose();
201
209
  await this.forceRemove().catch(() => { });
202
210
  }
203
211
  async cleanup() {
@@ -212,8 +220,10 @@ export class RemoteLocalDockerExecutionEnv {
212
220
  const ready = await this.ensureConnectedExec();
213
221
  if (!ready.ok)
214
222
  return ready;
215
- const execArgs = this.execArgv(command, options?.cwd, options?.env);
216
- const r = await this.docker(execArgs, {
223
+ const execArgs = this.startArgv(engineStart(options?.readFloor), command, { cwd: options?.cwd, env: options?.env });
224
+ if (!execArgs.ok)
225
+ return execArgs;
226
+ const r = await this.docker(execArgs.value, {
217
227
  signal: options?.abortSignal,
218
228
  timeoutMs: options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs,
219
229
  onStdout: options?.onStdout,
@@ -227,8 +237,10 @@ export class RemoteLocalDockerExecutionEnv {
227
237
  const ready = await this.ensureConnectedExec();
228
238
  if (!ready.ok)
229
239
  throw new RemoteExecutionError("connect_failed", ready.error.message, ready.error);
230
- const execArgs = this.execArgv(command, options?.cwd, options?.env);
231
- const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(execArgs), { stdio: ["ignore", "pipe", "pipe"], env: this.cliEnv() });
240
+ const execArgs = this.startArgv(engineStart(undefined), command, { cwd: options?.cwd, env: options?.env });
241
+ if (!execArgs.ok)
242
+ throw new RemoteExecutionError("unsupported", execArgs.error.message, execArgs.error);
243
+ const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(execArgs.value), { stdio: ["ignore", "pipe", "pipe"], env: this.cliEnv() });
232
244
  const queue = [];
233
245
  let finished = false;
234
246
  let failure;
@@ -360,7 +372,8 @@ export class RemoteLocalDockerExecutionEnv {
360
372
  if (!ready.ok)
361
373
  return ready;
362
374
  const abs = this.resolve(p);
363
- const r = await this.docker(this.execRaw(`cat ${shellQuote(abs)}`), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, binary: true });
375
+ const argv = this.startArgv(ADAPTER_START, `cat ${shellQuote(abs)}`);
376
+ const r = argv.ok ? await this.docker(argv.value, { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, binary: true }) : argv;
364
377
  if (!r.ok)
365
378
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
366
379
  if (r.value.exitCode !== 0)
@@ -420,9 +433,12 @@ export class RemoteLocalDockerExecutionEnv {
420
433
  if (!this.containerId) {
421
434
  const c = await this.connect();
422
435
  if (!c.ok)
423
- return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}`) };
436
+ return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}`, c.error) };
424
437
  }
425
- return this.docker(this.execArgv(cmd, undefined, undefined, input !== undefined), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
438
+ const argv = this.startArgv(ADAPTER_START, cmd, { stdin: input !== undefined });
439
+ if (!argv.ok)
440
+ return argv;
441
+ return this.docker(argv.value, { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
426
442
  }
427
443
  async fileInfo(p, abortSignal) {
428
444
  return this.posixFs.statFileInfo(p, abortSignal);
@@ -460,18 +476,68 @@ export class RemoteLocalDockerExecutionEnv {
460
476
  withHostFlag(args) {
461
477
  return this.cfg.dockerHost ? ["-H", this.cfg.dockerHost, ...args] : args;
462
478
  }
463
- execArgv(command, cwd, env, stdin = false) {
464
- const args = stdin ? ["exec", "-i"] : ["exec"];
465
- const dir = cwd != null ? this.resolve(cwd) : this.cwd;
466
- args.push("-w", dir);
467
- const mergedEnv = { ...(this.cfg.env ?? {}), ...(env ?? {}) };
468
- for (const [k, v] of Object.entries(mergedEnv))
469
- args.push("-e", `${k}=${v}`);
470
- args.push(this.containerName, this.containerShell, "-c", command);
471
- return args;
472
- }
473
- execRaw(command) {
474
- return this.execArgv(command);
479
+ startArgv(start, command, o = {}) {
480
+ const launch = this.floor.wrap(start);
481
+ if (!launch.ok)
482
+ return launch;
483
+ const args = o.stdin ? ["exec", "-i"] : ["exec"];
484
+ args.push(...dockerIdentityFlags(launch.value));
485
+ if (o.containerDefaults !== true) {
486
+ args.push("-w", o.cwd != null ? this.resolve(o.cwd) : this.cwd);
487
+ const mergedEnv = { ...(this.cfg.env ?? {}), ...(o.env ?? {}) };
488
+ for (const [k, v] of Object.entries(mergedEnv))
489
+ args.push("-e", `${k}=${v}`);
490
+ }
491
+ args.push(this.containerName, ...floorArgv(launch.value, [o.shell ?? this.containerShell, "-c", command]));
492
+ return ok(args);
493
+ }
494
+ floorTransport() {
495
+ const control = { timeoutMs: this.cfg.controlTimeoutMs };
496
+ return {
497
+ bootstrap: async (command) => {
498
+ const argv = this.startArgv(BOOTSTRAP_START, command, { shell: "/bin/sh" });
499
+ if (!argv.ok)
500
+ return { ok: false, error: argv.error.message };
501
+ const r = await this.docker(argv.value, control);
502
+ return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
503
+ },
504
+ place: async (dest, bytes) => {
505
+ const dir = path.posix.dirname(dest);
506
+ const tmp = `${dest}.${randomBytes(6).toString("hex")}`;
507
+ const cmd = `umask 022 && mkdir -p ${shellQuote(dir)} && cat > ${shellQuote(tmp)} && chmod 0755 ${shellQuote(tmp)} && mv -f ${shellQuote(tmp)} ${shellQuote(dest)}`;
508
+ const argv = this.startArgv(BOOTSTRAP_START, cmd, { shell: "/bin/sh", stdin: true });
509
+ if (!argv.ok)
510
+ return { ok: false, error: argv.error.message };
511
+ const r = await this.docker(argv.value, { ...control, input: bytes });
512
+ if (!r.ok)
513
+ return { ok: false, error: r.error.message };
514
+ return r.value.exitCode === 0 ? { ok: true } : { ok: false, error: r.value.stderr.trim() || `exit ${r.value.exitCode}` };
515
+ },
516
+ openRelay: (identity, command, events) => {
517
+ const args = ["exec", "-i", ...dockerIdentityFlags({ mode: "control", identity }), this.containerName, "/bin/sh", "-c", command];
518
+ const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(args), { stdio: ["pipe", "pipe", "pipe"], env: this.cliEnv() });
519
+ let closed = false;
520
+ const close = (why) => {
521
+ if (closed)
522
+ return;
523
+ closed = true;
524
+ events.closed(why);
525
+ };
526
+ child.stdout?.setEncoding("utf8");
527
+ child.stdout?.on("data", (d) => events.data(d));
528
+ child.stderr?.on("data", () => { });
529
+ child.stdin?.on("error", () => { });
530
+ child.on("error", (e) => close(`docker exec failed: ${e.message}`));
531
+ child.on("close", (code, signal) => close(`relay exited (${code ?? signal ?? "unknown"})`));
532
+ return {
533
+ send: (line) => void child.stdin?.write(line),
534
+ close: () => {
535
+ child.stdin?.end();
536
+ child.kill("SIGKILL");
537
+ },
538
+ };
539
+ },
540
+ };
475
541
  }
476
542
  docker(args, opts) {
477
543
  return new Promise((resolve) => {
@@ -570,7 +636,8 @@ export class RemoteLocalDockerExecutionEnv {
570
636
  }
571
637
  async detectContainerShell() {
572
638
  try {
573
- const r = await this.docker(["exec", this.containerName, "/bin/sh", "-c", "command -v bash"], { timeoutMs: this.cfg.controlTimeoutMs });
639
+ const argv = this.startArgv(ADAPTER_START, "command -v bash", { shell: "/bin/sh", containerDefaults: true });
640
+ const r = argv.ok ? await this.docker(argv.value, { timeoutMs: this.cfg.controlTimeoutMs }) : argv;
574
641
  if (r.ok && r.value.exitCode === 0 && r.value.stdout.trim().length > 0)
575
642
  this.containerShell = "bash";
576
643
  }
@@ -597,7 +664,7 @@ export class RemoteLocalDockerExecutionEnv {
597
664
  return ok(undefined);
598
665
  const c = await this.connect();
599
666
  if (!c.ok)
600
- return { ok: false, error: new FileError("unknown", `local-docker connect failed: ${c.error.message}`, this.resolve(p), c.error) };
667
+ return { ok: false, error: fileErrorFromExec(c.error, this.resolve(p)) };
601
668
  return ok(undefined);
602
669
  }
603
670
  dockerToRemoteError(e, fallback) {
@@ -53,28 +53,8 @@ export interface SshEnvDeps {
53
53
  connectOnce?: (signal?: AbortSignal) => Promise<ConnectResult>;
54
54
  }
55
55
  export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
56
- /**
57
- * 🔴 **[ref] / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
58
- * 就是**命令真正跑的那块文件系统**的权威答案(`posix-shell-fs` 的 `canonicalPath` 原语,在真机上解到落点)。声明它 = 告诉引擎:整链答不出来是**解析器出了事**
59
- * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
60
- * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
61
- * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
62
- * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
63
- * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
64
- * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
65
- */
66
- /** `sharesProcessTable`(core 7.57.0,必填):命令跑在 SSH 目标机上;目标恰是本机(或本进程所在容器的宿主)时命令与本进程同表,这一形今天没有配置面可声明 `true`。 */
67
- /** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
68
- readonly capabilities: {
69
- isolation: false;
70
- suspendable: false;
71
- canonicalPathAuthoritative: true;
72
- sharesProcessTable: false;
73
- hostLocalPaths: false;
74
- externalContentTarget: false;
75
- io: true;
76
- keepsReadFloor: false;
77
- };
56
+ /** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
57
+ readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
78
58
  /** Working directory; relative paths resolve against it (ExecutionEnv contract). Starts at mountPath. */
79
59
  cwd: string;
80
60
  private conn?;
@@ -91,9 +71,9 @@ export declare class RemoteSshExecutionEnv implements RemoteExecutionEnv {
91
71
  * =重连风暴);②共享尝试**不携带任何 caller 信号**(首创者的 pre-abort 不得毒化 joiner;单 caller
92
72
  * 取消只退它自己的等待,底层握手余命 ≤ readyTimeout 有界,彻底取消归 destroy 代际闸)。 */
93
73
  private inFlight?;
94
- /** core `ExecutionEnv.homeDir`([ref]):目标机上登录用户的 home —— 声明胜,否则连上之后实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
74
+ /** core `ExecutionEnv.homeDir`([ref]):目标机上登录用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则连上之后实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
95
75
  get homeDir(): string | undefined;
96
- /** 家目录的座(声明胜 · 否则连上之后实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。本车道对声明对证。 */
76
+ /** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则连上之后实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
97
77
  private readonly homeSeat;
98
78
  /** 家目录声明 × 实测的判定,按连接记(同一条连接只问一次;重连换了连接再问 —— 登录用户不变时答案相同,代价一次往返;没声明且已量到绝对路径之后不再问)。 */
99
79
  private homeChecked?;
@@ -7,6 +7,7 @@ import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
7
7
  import { buildCapturedOutput, createCutError } from "../exec-cut.js";
8
8
  import { createPosixShellFs } from "./posix-shell-fs.js";
9
9
  import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
10
+ import { capabilitiesFor } from "../execution-lane-caps.js";
10
11
  import { fileErrorFromExec } from "./remote-env-file-error.js";
11
12
  import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
12
13
  const PROVIDER = "ssh";
@@ -19,7 +20,7 @@ function exitCodeForSignalKill(signal) {
19
20
  return 128 + (signalNumber(`SIG${signal}`) ?? 9);
20
21
  }
21
22
  export class RemoteSshExecutionEnv {
22
- capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
23
+ capabilities = capabilitiesFor({ remoteExec: { provider: "ssh" } });
23
24
  cwd;
24
25
  conn;
25
26
  sftpPromise;
@@ -52,7 +53,7 @@ export class RemoteSshExecutionEnv {
52
53
  hostKey: config.hostKey,
53
54
  };
54
55
  this.cwd = this.cfg.mountPath;
55
- this.homeSeat = new LaneHomeSeat({ lane: PROVIDER, declared: config.homeDir, verifiesDeclared: true, onAbsent: config.onHomeAbsent });
56
+ this.homeSeat = new LaneHomeSeat(PROVIDER, config);
56
57
  }
57
58
  resolve(p) {
58
59
  return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
@@ -579,7 +580,7 @@ export class RemoteSshExecutionEnv {
579
580
  async sftp(abortSignal) {
580
581
  const ready = await this.ensureConnected(abortSignal);
581
582
  if (!ready.ok)
582
- return Promise.reject(ready.error.code === "aborted" ? fileErrorFromExec(ready.error) : new Error(ready.error.message));
583
+ return Promise.reject(ready.error.code === "aborted" ? fileErrorFromExec(ready.error) : new FileError("target_unavailable", ready.error.message, undefined, ready.error));
583
584
  if (!this.conn)
584
585
  return Promise.reject(new Error("SSH not connected"));
585
586
  if (!this.sftpPromise) {
@@ -4,8 +4,8 @@ export class SqlResumeAnchorStore {
4
4
  constructor(db) {
5
5
  this.db = db;
6
6
  }
7
- q(tidb, pg) {
8
- return this.db.dialect === "tidb" ? tidb : pg;
7
+ q(mysql, pg) {
8
+ return this.db.dialect === "mysql" ? mysql : pg;
9
9
  }
10
10
  async put(sessionId, eventId, entryId, owner) {
11
11
  await this.db.query(this.q("INSERT INTO resume_anchor (session_id, event_id, entry_id, owner, created_at) VALUES (?,?,?,?,?) " +
@@ -118,7 +118,7 @@ export declare class SqlRetentionLaneStore implements RetentionLaneStore {
118
118
  protected readonly db: SqlDriver;
119
119
  constructor(db: SqlDriver);
120
120
  /** 方言取文(两条语句都写在调用点;A12 判据)。 */
121
- protected q(tidb: string, pg: string): string;
121
+ protected q(mysql: string, pg: string): string;
122
122
  /**
123
123
  * 抢/续租一次(单行 CAS)。抢到 ⇒ `{held:true, fencingToken}`;别人正持着 ⇒ `{held:false}`,调用方
124
124
  * **本 tick 零写静默跳过**。
@@ -7,8 +7,8 @@ export class SqlRetentionLaneStore {
7
7
  constructor(db) {
8
8
  this.db = db;
9
9
  }
10
- q(tidb, pg) {
11
- return this.db.dialect === "tidb" ? tidb : pg;
10
+ q(mysql, pg) {
11
+ return this.db.dialect === "mysql" ? mysql : pg;
12
12
  }
13
13
  async acquire(holder, ttlMs, nowMs) {
14
14
  const conn = await this.db.connect();
@@ -183,7 +183,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
183
183
  readonly retention: RetentionDeclaration;
184
184
  constructor(db: SqlDriver, opts: SqlRetentionStoreOptions);
185
185
  /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE (A12 判据)。 */
186
- protected q(tidb: string, pg: string): string;
186
+ protected q(mysql: string, pg: string): string;
187
187
  /**
188
188
  * 域谓词。**四种写法逐条写出来**(方言 × 有主/无主):
189
189
  * · 有主:`col = ?` / `col = $n`;
@@ -155,8 +155,8 @@ export class SqlRetentionStore {
155
155
  this.db = db;
156
156
  this.opts = opts;
157
157
  }
158
- q(tidb, pg) {
159
- return this.db.dialect === "tidb" ? tidb : pg;
158
+ q(mysql, pg) {
159
+ return this.db.dialect === "mysql" ? mysql : pg;
160
160
  }
161
161
  domainWhere(col, domain, pgIndex) {
162
162
  return domain === ""
@@ -164,7 +164,7 @@ export class SqlRetentionStore {
164
164
  : { sql: this.q(`${col} = ?`, `${col} = $${pgIndex}`), params: [domain] };
165
165
  }
166
166
  idSet(col, ids, pgIndex) {
167
- return this.db.dialect === "tidb"
167
+ return this.db.dialect === "mysql"
168
168
  ? { sql: `${col} IN (${ids.map(() => "?").join(",")})`, params: [...ids] }
169
169
  : { sql: `${col} = ANY($${pgIndex}::text[])`, params: [ids] };
170
170
  }
@@ -210,7 +210,7 @@ export class SqlRetentionStore {
210
210
  if (keys.length === 0)
211
211
  return 0;
212
212
  const now = Date.now();
213
- if (this.db.dialect === "tidb") {
213
+ if (this.db.dialect === "mysql") {
214
214
  const params = [];
215
215
  for (const k of keys)
216
216
  params.push(domain, kind, k, now);
@@ -267,7 +267,7 @@ export class SqlRetentionStore {
267
267
  }
268
268
  }
269
269
  horizonArms(pgFirst) {
270
- return this.db.dialect === "tidb"
270
+ return this.db.dialect === "mysql"
271
271
  ? `((c.deadline IS NOT NULL AND c.deadline <= ?) OR (c.deadline IS NULL AND c.created_at_ms <= ?))`
272
272
  : `((c.deadline IS NOT NULL AND c.deadline <= $${pgFirst}) OR (c.deadline IS NULL AND c.created_at_ms <= $${pgFirst + 1}))`;
273
273
  }
@@ -287,7 +287,7 @@ export class SqlRetentionStore {
287
287
  return Number(rows[0]?.n ?? 0);
288
288
  }
289
289
  liveReferenceExists() {
290
- const workflow = this.db.dialect === "tidb"
290
+ const workflow = this.db.dialect === "mysql"
291
291
  ? `EXISTS (SELECT 1 FROM workflow_run wr WHERE wr.status = 'running' AND JSON_UNQUOTE(JSON_EXTRACT(wr.run, '$.originatingSessionId')) = sm.session_id)`
292
292
  : `EXISTS (SELECT 1 FROM workflow_run wr WHERE wr.status = 'running' AND wr.run::json->>'originatingSessionId' = sm.session_id)`;
293
293
  return (`(sm.placement_kind IS NOT NULL` +
@@ -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 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 } 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";
@@ -171,7 +171,7 @@ export declare class SqlRunStore {
171
171
  /**
172
172
  * 认领当写栅栏用(进程内两孪生的契约 = `store-contracts.ts` 的 `InProcessClaimFence`;本孪生是它的事务形):一个事务 ——
173
173
  * ① 锁读这条 run 行且它在跑(`task_run … FOR UPDATE`);② 锁读会话的认领行且它是这条 run 的(`task_active … FOR UPDATE`);
174
- * ③ 刷新这条 run 的活性戳;④ `write` 在**同一个事务连接**上跑;提交即放锁。任一没读到 ⇒ 不持有,不刷新、`write` 不调。`write` 抛或提交失败 ⇒ 整只抛。
174
+ * ③ 刷新这条 run 的活性戳;④ `write` 在**同一个事务连接**上跑(另收锁住那一行的 {@link FencedRun});提交即放锁。任一没读到 ⇒ 不持有,不刷新、`write` 不调。`write` 抛或提交失败 ⇒ 整只抛。
175
175
  * 🔴 **取锁序 = task_run → task_active**,与 `setTerminal` / `reapStale` / `reapSuspended` 同序(它们先改 run 行、再删认领行):两行用两条语句按序锁,
176
176
  * 不用连表锁读(那一形的先后由计划器定,与终局 / 收割相反时两只事务各攥一行成环)。`createRun` 先锁根会话行再插认领行(本序从不碰 `session_meta`)、
177
177
  * `deleteBySession` 先锁根会话行、见认领行在就中止,都不与本序成环。锁住之后:收割的改行 / 终局与收割的删认领 / 别人的建行都排在本事务之后,收割器提交后重判 `updated_at` 看到的是刚刷的。
@@ -180,7 +180,7 @@ export declare class SqlRunStore {
180
180
  * 🔴 **`write` 不许写 `session_meta`**:本事务持着 `task_active`,而 `createRun` 与改根行的写者都先锁根会话行再碰 `task_*`(全局锁序见 {@link createRun})——
181
181
  * 回调去锁根行就是反序,与等着本会话认领的 `createRun` 各攥一行成环。门:`test/submission-root-contract.test.ts` 扫每个调用点的回调体。
182
182
  */
183
- whileHoldingClaim<T>(sessionId: string, taskId: string, write: (tx: SqlTxConn) => Promise<T>): Promise<ClaimFenced<T>>;
183
+ whileHoldingClaim<T>(sessionId: string, taskId: string, write: (tx: SqlTxConn, run: FencedRun) => Promise<T>): Promise<ClaimFenced<T>>;
184
184
  /** The active run's taskId for a session (the `task_active` claim) — lets resume find the suspended run row. */
185
185
  getActiveTaskId(sessionId: string): Promise<string | undefined>;
186
186
  /** Shared row→record mapper — byte-identical between dialects (both drivers hand back the same column
@@ -47,8 +47,8 @@ export class SqlRunStore {
47
47
  constructor(db) {
48
48
  this.db = db;
49
49
  }
50
- q(tidb, pg) {
51
- return this.db.dialect === "tidb" ? tidb : pg;
50
+ q(mysql, pg) {
51
+ return this.db.dialect === "mysql" ? mysql : pg;
52
52
  }
53
53
  isDupKey(err) {
54
54
  return isDupKeyError(this.db.dialect, err);
@@ -103,7 +103,7 @@ export class SqlRunStore {
103
103
  cells.identitySource,
104
104
  cells.identityScope,
105
105
  cells.identityPrincipalInc,
106
- this.db.dialect === "tidb"
106
+ this.db.dialect === "mysql"
107
107
  ? meta.objectivePreview ?? null
108
108
  : meta.objectivePreview == null
109
109
  ? null
@@ -181,8 +181,8 @@ export class SqlRunStore {
181
181
  const w = await conn.query(this.q("UPDATE task_run SET status = ?, result = ?, error = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND status IN ('running','suspended','needs_review')", "UPDATE task_run SET status = $1, result = $2::jsonb, error = $3, error_code = $4, updated_at = $5 WHERE task_id = $6 AND status IN ('running','suspended','needs_review')"), [
182
182
  status,
183
183
  result == null ? null : this.json(result),
184
- this.db.dialect === "tidb" ? error : error == null ? null : pgSanitizeText(error),
185
- this.db.dialect === "tidb" ? errorCode : errorCode == null ? null : pgSanitizeText(errorCode),
184
+ this.db.dialect === "mysql" ? error : error == null ? null : pgSanitizeText(error),
185
+ this.db.dialect === "mysql" ? errorCode : errorCode == null ? null : pgSanitizeText(errorCode),
186
186
  new Date(),
187
187
  taskId,
188
188
  ]);
@@ -253,7 +253,7 @@ export class SqlRunStore {
253
253
  }
254
254
  async claimSpaceCommitIn(conn, spaceKey, taskId, state, claim) {
255
255
  if (!claim.requireOwn) {
256
- await conn.query(this.q(`INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES (?,?,?,${SQL_DB_NOW_MS.tidb} + ?) ON DUPLICATE KEY UPDATE space_key = space_key`, `INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES ($1,$2,$3,${SQL_DB_NOW_MS.pg} + $4::bigint) ON CONFLICT (space_key) DO UPDATE SET space_key = EXCLUDED.space_key`), [spaceKey, taskId, state, claim.boundMs]);
256
+ await conn.query(this.q(`INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES (?,?,?,${SQL_DB_NOW_MS.mysql} + ?) ON DUPLICATE KEY UPDATE space_key = space_key`, `INSERT INTO workspace_space_commit (space_key, task_id, state, expires_at_ms) VALUES ($1,$2,$3,${SQL_DB_NOW_MS.pg} + $4::bigint) ON CONFLICT (space_key) DO UPDATE SET space_key = EXCLUDED.space_key`), [spaceKey, taskId, state, claim.boundMs]);
257
257
  }
258
258
  const row = await this.readSpaceCommitIn(conn, spaceKey, true);
259
259
  switch (decideSpaceCommitClaim(row, taskId, claim)) {
@@ -262,7 +262,7 @@ export class SqlRunStore {
262
262
  case "insert":
263
263
  throw new Error("workspace_space_commit: a space's row vanished under this transaction's own lock");
264
264
  case "update": {
265
- const w = await conn.query(this.q(`UPDATE workspace_space_commit SET task_id = ?, state = ?, expires_at_ms = ${SQL_DB_NOW_MS.tidb} + ? WHERE space_key = ? AND task_id = ?`, `UPDATE workspace_space_commit SET task_id = $1, state = $2, expires_at_ms = ${SQL_DB_NOW_MS.pg} + $3::bigint WHERE space_key = $4 AND task_id = $5`), [taskId, state, claim.boundMs, spaceKey, row.taskId]);
265
+ const w = await conn.query(this.q(`UPDATE workspace_space_commit SET task_id = ?, state = ?, expires_at_ms = ${SQL_DB_NOW_MS.mysql} + ? WHERE space_key = ? AND task_id = ?`, `UPDATE workspace_space_commit SET task_id = $1, state = $2, expires_at_ms = ${SQL_DB_NOW_MS.pg} + $3::bigint WHERE space_key = $4 AND task_id = $5`), [taskId, state, claim.boundMs, spaceKey, row.taskId]);
266
266
  return w.affected > 0;
267
267
  }
268
268
  }
@@ -282,15 +282,16 @@ export class SqlRunStore {
282
282
  async whileHoldingClaim(sessionId, taskId, write) {
283
283
  let fenced = { held: false };
284
284
  await this.tx(async (conn) => {
285
- const run = await conn.query(this.q("SELECT task_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
286
- if (run.rows.length === 0)
285
+ const run = await conn.query(this.q("SELECT task_id, instance_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id, instance_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
286
+ const row = run.rows[0];
287
+ if (row === undefined)
287
288
  return;
288
289
  const claim = await conn.query(this.q("SELECT task_id FROM task_active WHERE session_id = ? FOR UPDATE", "SELECT task_id FROM task_active WHERE session_id = $1 FOR UPDATE"), [sessionId]);
289
290
  const holder = claim.rows[0];
290
291
  if (holder === undefined || String(holder.task_id) !== taskId)
291
292
  return;
292
293
  await conn.query(this.q("UPDATE task_run SET updated_at = ? WHERE task_id = ?", "UPDATE task_run SET updated_at = $1 WHERE task_id = $2"), [new Date(), taskId]);
293
- fenced = { held: true, value: await write(conn) };
294
+ fenced = { held: true, value: await write(conn, { instanceId: row.instance_id ?? null }) };
294
295
  });
295
296
  return fenced;
296
297
  }
@@ -318,20 +319,20 @@ export class SqlRunStore {
318
319
  };
319
320
  }
320
321
  async getRun(taskId) {
321
- const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("tidb")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
322
+ const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
322
323
  return rows[0] ? this.rowToRun(rows[0]) : undefined;
323
324
  }
324
325
  async observeSubmittedRow(conn, taskId, idemKey) {
325
326
  if (idemKey !== null) {
326
- const { rows } = await conn.query(this.q(`SELECT ${runRowCols("tidb")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
327
+ const { rows } = await conn.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
327
328
  if (rows[0])
328
329
  return this.rowToRun(rows[0]);
329
330
  }
330
- const { rows } = await conn.query(this.q(`SELECT ${runRowCols("tidb")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
331
+ const { rows } = await conn.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE task_id = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE task_id = $1`), [taskId]);
331
332
  return rows[0] ? this.rowToRun(rows[0]) : undefined;
332
333
  }
333
334
  async getRunByIdem(idemKey) {
334
- const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("tidb")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
335
+ const { rows } = await this.db.query(this.q(`SELECT ${runRowCols("mysql")} FROM task_run WHERE idem_key = ?`, `SELECT ${runRowCols("pg")} FROM task_run WHERE idem_key = $1`), [idemKey]);
335
336
  return rows[0] ? this.rowToRun(rows[0]) : undefined;
336
337
  }
337
338
  async releaseIdemKey(taskId) {
@@ -346,7 +347,7 @@ export class SqlRunStore {
346
347
  async listRuns(opts) {
347
348
  if (opts.cursor && Number.isNaN(Date.parse(opts.cursor.createdAt)))
348
349
  return [];
349
- if (this.db.dialect === "tidb") {
350
+ if (this.db.dialect === "mysql") {
350
351
  const where = [];
351
352
  const params = [];
352
353
  if (opts.status) {
@@ -372,7 +373,7 @@ export class SqlRunStore {
372
373
  where.push("(created_at < ? OR (created_at = ? AND task_id < ?))");
373
374
  params.push(new Date(opts.cursor.createdAt), new Date(opts.cursor.createdAt), opts.cursor.taskId);
374
375
  }
375
- const sql = `SELECT ${runRowCols("tidb")} FROM task_run` +
376
+ const sql = `SELECT ${runRowCols("mysql")} FROM task_run` +
376
377
  (where.length ? " WHERE " + where.join(" AND ") : "") +
377
378
  " ORDER BY created_at DESC, task_id DESC LIMIT ?";
378
379
  params.push(opts.limit);
@@ -413,7 +414,7 @@ export class SqlRunStore {
413
414
  return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
414
415
  }
415
416
  async listSessions(opts) {
416
- if (this.db.dialect === "tidb") {
417
+ if (this.db.dialect === "mysql") {
417
418
  const params = [];
418
419
  let innerWhere = "";
419
420
  if (opts.owner !== undefined) {
@@ -533,7 +534,7 @@ export class SqlRunStore {
533
534
  return m == null ? 0 : Number(m);
534
535
  }
535
536
  async getEvents(taskId, afterSeq) {
536
- const { rows } = await this.db.query(this.q(`SELECT seq, type, ${jsonColumnText("tidb", "data")}, ts FROM task_event WHERE task_id = ? AND seq > ? ORDER BY seq ASC`, `SELECT seq, type, ${jsonColumnText("pg", "data")}, ts FROM task_event WHERE task_id = $1 AND seq > $2 ORDER BY seq ASC`), [taskId, afterSeq]);
537
+ const { rows } = await this.db.query(this.q(`SELECT seq, type, ${jsonColumnText("mysql", "data")}, ts FROM task_event WHERE task_id = ? AND seq > ? ORDER BY seq ASC`, `SELECT seq, type, ${jsonColumnText("pg", "data")}, ts FROM task_event WHERE task_id = $1 AND seq > $2 ORDER BY seq ASC`), [taskId, afterSeq]);
537
538
  return rows.map((r) => ({
538
539
  seq: Number(r.seq),
539
540
  type: String(r.type),