@sema-agent/server 7.140.0 → 7.142.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 (296) hide show
  1. package/MIGRATION.md +505 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +142 -25
  5. package/dist/approval-ask-audit-store.d.ts +54 -38
  6. package/dist/approval-ask-audit-store.js +58 -39
  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 +39 -24
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval-crash-convergence.d.ts +17 -7
  12. package/dist/approval-crash-convergence.js +94 -22
  13. package/dist/approval-deny-reasons.d.ts +34 -30
  14. package/dist/approval-deny-reasons.js +21 -6
  15. package/dist/approval-policy-settlement.d.ts +1 -1
  16. package/dist/approval-reconciler.d.ts +33 -1
  17. package/dist/approval-reconciler.js +43 -40
  18. package/dist/approval.d.ts +15 -0
  19. package/dist/approval.js +10 -0
  20. package/dist/audit.d.ts +4 -2
  21. package/dist/audit.js +2 -2
  22. package/dist/auth-bridge.d.ts +9 -6
  23. package/dist/auth-bridge.js +9 -3
  24. package/dist/boot/adoption.js +2 -1
  25. package/dist/boot/budget-tracing.d.ts +16 -0
  26. package/dist/boot/budget-tracing.js +10 -6
  27. package/dist/boot/device-lane.d.ts +11 -0
  28. package/dist/boot/device-lane.js +16 -0
  29. package/dist/boot/engine-lease.d.ts +24 -3
  30. package/dist/boot/engine-lease.js +9 -2
  31. package/dist/boot/execution-env.d.ts +24 -4
  32. package/dist/boot/execution-env.js +34 -27
  33. package/dist/boot/execution-lane.js +2 -1
  34. package/dist/boot/exit-boundary.js +5 -4
  35. package/dist/boot/memory-boundary.d.ts +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +1 -1
  37. package/dist/boot/memory-consolidation.js +3 -2
  38. package/dist/boot/parked-revive-gate.js +2 -1
  39. package/dist/boot/permission-rules-audit.d.ts +0 -28
  40. package/dist/boot/permission-rules-audit.js +2 -1
  41. package/dist/boot/read-deny-anchor-audit.js +2 -1
  42. package/dist/boot/reapers.d.ts +2 -2
  43. package/dist/boot/reapers.js +4 -3
  44. package/dist/boot/resolve-spec.js +18 -6
  45. package/dist/boot/retention-lane.js +5 -3
  46. package/dist/boot/runner-deps.js +16 -13
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.d.ts +1 -1
  49. package/dist/boot/session-faces.js +8 -6
  50. package/dist/boot/shutdown.js +8 -2
  51. package/dist/boot/stage-04-stores.js +3 -2
  52. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  53. package/dist/boot/stage-05-execution-env.js +11 -7
  54. package/dist/boot/stage-06-runners.d.ts +1 -3
  55. package/dist/boot/stage-06-runners.js +3 -2
  56. package/dist/boot/stage-07-capability-layer.d.ts +1 -2
  57. package/dist/boot/stage-07-capability-layer.js +10 -11
  58. package/dist/boot/stage-08-reapers.d.ts +1 -2
  59. package/dist/boot/stage-08-reapers.js +3 -2
  60. package/dist/boot/stage-09-leader.d.ts +1 -2
  61. package/dist/boot/stage-10-http-server.d.ts +1 -2
  62. package/dist/boot/stage-10-http-server.js +6 -4
  63. package/dist/boot/stores.js +13 -6
  64. package/dist/boot/web-admin.js +18 -17
  65. package/dist/boot/webfetch-summarize-lane.js +2 -1
  66. package/dist/boot/workflow-orchestration.js +3 -2
  67. package/dist/capabilities/builtin-tools.d.ts +2 -1
  68. package/dist/capabilities/builtin-tools.js +2 -4
  69. package/dist/capabilities/composite-members.d.ts +2 -1
  70. package/dist/capabilities/composite-members.js +2 -1
  71. package/dist/capabilities/scenarios.d.ts +1 -1
  72. package/dist/capability-facts.d.ts +33 -0
  73. package/dist/capability-facts.js +32 -0
  74. package/dist/card-void.d.ts +38 -0
  75. package/dist/card-void.js +23 -0
  76. package/dist/config-catalog.js +12 -8
  77. package/dist/config-provider.d.ts +13 -13
  78. package/dist/config-provider.js +19 -33
  79. package/dist/config-types.d.ts +39 -10
  80. package/dist/config.d.ts +6 -0
  81. package/dist/config.js +72 -49
  82. package/dist/delegated-session-owner.d.ts +21 -19
  83. package/dist/delegated-session-owner.js +1 -1
  84. package/dist/device-store.d.ts +7 -0
  85. package/dist/device-store.js +3 -0
  86. package/dist/execution-config-root.d.ts +22 -0
  87. package/dist/execution-config-root.js +35 -0
  88. package/dist/execution-lane-caps.d.ts +50 -2
  89. package/dist/execution-lane-caps.js +90 -13
  90. package/dist/execution-placement.d.ts +3 -0
  91. package/dist/execution-placement.js +7 -0
  92. package/dist/fleet/drain-entry.d.ts +3 -0
  93. package/dist/fleet/fleet-bus.d.ts +6 -3
  94. package/dist/hooks/hook-runner.d.ts +6 -0
  95. package/dist/hooks/hook-runner.js +30 -1
  96. package/dist/hosted-posture.d.ts +8 -0
  97. package/dist/hosted-posture.js +4 -0
  98. package/dist/http/admission.js +3 -3
  99. package/dist/http/caller-trust.d.ts +3 -2
  100. package/dist/http/caller-trust.js +2 -1
  101. package/dist/http/dispatch.js +8 -2
  102. package/dist/http/execution-context.d.ts +2 -2
  103. package/dist/http/idempotency.d.ts +1 -1
  104. package/dist/http/live-frame.d.ts +24 -0
  105. package/dist/http/live-frame.js +16 -0
  106. package/dist/http/principal-gate.d.ts +3 -1
  107. package/dist/http/principal-gate.js +17 -8
  108. package/dist/http/resume-legs.js +42 -29
  109. package/dist/http/route-table.js +4 -0
  110. package/dist/http/routes/approvals-assistant.js +1 -0
  111. package/dist/http/routes/attachments.js +18 -0
  112. package/dist/http/routes/capabilities.js +10 -6
  113. package/dist/http/routes/diagnostics.d.ts +0 -19
  114. package/dist/http/routes/diagnostics.js +16 -2
  115. package/dist/http/routes/memory-bundle.d.ts +2 -2
  116. package/dist/http/routes/memory-bundle.js +1 -1
  117. package/dist/http/routes/memory-compliance.js +1 -1
  118. package/dist/http/routes/memory-policy.js +27 -7
  119. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  120. package/dist/http/routes/run-permission-mode.js +170 -0
  121. package/dist/http/routes/runs.js +3 -3
  122. package/dist/http/routes/session-memory-sync.d.ts +63 -0
  123. package/dist/http/routes/session-memory-sync.js +157 -0
  124. package/dist/http/routes/session-sync.d.ts +3 -6
  125. package/dist/http/routes/session-sync.js +7 -2
  126. package/dist/http/routes/sessions-list.d.ts +2 -1
  127. package/dist/http/routes/sessions.js +5 -4
  128. package/dist/http/routes/tasks.js +70 -71
  129. package/dist/http/run-read-face.d.ts +7 -0
  130. package/dist/http/run-read-face.js +10 -4
  131. package/dist/http/send.d.ts +6 -1
  132. package/dist/http/send.js +10 -3
  133. package/dist/http/server.d.ts +20 -7
  134. package/dist/http/server.js +2 -4
  135. package/dist/http/sse-log.d.ts +3 -8
  136. package/dist/http/stored-body-replay.d.ts +3 -3
  137. package/dist/http/stored-body-replay.js +3 -1
  138. package/dist/http/submit-replay.d.ts +4 -3
  139. package/dist/http/submit-replay.js +2 -2
  140. package/dist/http/sync-submit-body.d.ts +21 -4
  141. package/dist/http/sync-submit-body.js +13 -0
  142. package/dist/identity-source.d.ts +21 -2
  143. package/dist/identity-source.js +6 -0
  144. package/dist/instruction-source-notice.d.ts +7 -0
  145. package/dist/instruction-source-notice.js +6 -6
  146. package/dist/mcp-assertion-header.d.ts +4 -0
  147. package/dist/mcp-assertion-header.js +5 -0
  148. package/dist/memory-incident-sink.d.ts +19 -6
  149. package/dist/memory-incident-sink.js +8 -4
  150. package/dist/memory-posture.d.ts +1 -1
  151. package/dist/memory-scope.d.ts +4 -4
  152. package/dist/memory-scope.js +1 -1
  153. package/dist/memory-sync-client.d.ts +35 -23
  154. package/dist/memory-sync-client.js +122 -52
  155. package/dist/memory-sync-identity.d.ts +24 -0
  156. package/dist/memory-sync-identity.js +57 -0
  157. package/dist/memory-sync.d.ts +7 -1
  158. package/dist/memory-sync.js +6 -3
  159. package/dist/observability/fail-open.d.ts +57 -12
  160. package/dist/observability/fail-open.js +50 -9
  161. package/dist/observability/metrics.js +0 -1
  162. package/dist/observability/run-terminal.d.ts +93 -31
  163. package/dist/observability/run-terminal.js +175 -171
  164. package/dist/observability/secret-env-scrub.d.ts +20 -1
  165. package/dist/observability/secret-env-scrub.js +14 -0
  166. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  167. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  168. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  169. package/dist/orchestration/workflow-notify-journal.js +2 -1
  170. package/dist/permission-mode-admission.d.ts +68 -0
  171. package/dist/permission-mode-admission.js +23 -0
  172. package/dist/permission-rule-list.d.ts +55 -6
  173. package/dist/permission-rule-list.js +52 -5
  174. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  175. package/dist/plugins/approval-ask-store-file.js +48 -7
  176. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  177. package/dist/plugins/approval-ask-store-memory.js +9 -8
  178. package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
  179. package/dist/plugins/approval-ask-store-sql.js +49 -19
  180. package/dist/plugins/caching-session-store.d.ts +6 -0
  181. package/dist/plugins/caching-session-store.js +3 -0
  182. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  183. package/dist/plugins/checkpoint-store-sql.js +74 -3
  184. package/dist/plugins/device-store-sql.d.ts +5 -2
  185. package/dist/plugins/device-store-sql.js +4 -1
  186. package/dist/plugins/execution-placement-sql.d.ts +11 -1
  187. package/dist/plugins/execution-placement-sql.js +12 -1
  188. package/dist/plugins/file-run-store.d.ts +7 -2
  189. package/dist/plugins/file-run-store.js +12 -2
  190. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  191. package/dist/plugins/fork-routing-session-store.js +2 -61
  192. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  193. package/dist/plugins/k8s-exec-protocol.js +6 -1
  194. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  195. package/dist/plugins/local-checkpoint-store.js +22 -0
  196. package/dist/plugins/local-session-store.d.ts +260 -39
  197. package/dist/plugins/local-session-store.js +582 -57
  198. package/dist/plugins/local-session-window.d.ts +17 -3
  199. package/dist/plugins/local-session-window.js +7 -13
  200. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  201. package/dist/plugins/local-task-attachment-store.js +67 -43
  202. package/dist/plugins/memory-key-guards.d.ts +3 -3
  203. package/dist/plugins/memory-run-store.d.ts +7 -2
  204. package/dist/plugins/memory-run-store.js +12 -2
  205. package/dist/plugins/one-time-migrations.d.ts +68 -2
  206. package/dist/plugins/one-time-migrations.js +120 -4
  207. package/dist/plugins/pg-pool.js +3 -2
  208. package/dist/plugins/pg-session-storage.d.ts +2 -0
  209. package/dist/plugins/pg-session-storage.js +3 -2
  210. package/dist/plugins/remote-env-e2b.d.ts +47 -7
  211. package/dist/plugins/remote-env-e2b.js +242 -46
  212. package/dist/plugins/remote-env-k8s.d.ts +51 -6
  213. package/dist/plugins/remote-env-k8s.js +234 -44
  214. package/dist/plugins/remote-env-local-docker.d.ts +40 -14
  215. package/dist/plugins/remote-env-local-docker.js +127 -33
  216. package/dist/plugins/run-store-sql.d.ts +11 -3
  217. package/dist/plugins/run-store-sql.js +13 -4
  218. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  219. package/dist/plugins/sandbox-read-floor.js +608 -0
  220. package/dist/plugins/session-ownership.d.ts +2 -1
  221. package/dist/plugins/session-placement.d.ts +17 -4
  222. package/dist/plugins/session-placement.js +2 -1
  223. package/dist/plugins/session-store.d.ts +18 -20
  224. package/dist/plugins/session-store.js +4 -12
  225. package/dist/plugins/store-backend.d.ts +41 -7
  226. package/dist/plugins/store-backend.js +22 -4
  227. package/dist/plugins/store-contracts.d.ts +35 -3
  228. package/dist/plugins/store-contracts.js +3 -0
  229. package/dist/plugins/task-attachment-store.d.ts +112 -14
  230. package/dist/plugins/task-attachment-store.js +221 -28
  231. package/dist/plugins/tidb-pool.js +2 -1
  232. package/dist/plugins/tidb-session-store.d.ts +4 -1
  233. package/dist/plugins/tidb-session-store.js +3 -2
  234. package/dist/plugins/workspace-fill.d.ts +60 -0
  235. package/dist/plugins/workspace-fill.js +106 -0
  236. package/dist/project-bundle.d.ts +38 -0
  237. package/dist/project-bundle.js +609 -0
  238. package/dist/run-cancel-context.d.ts +34 -0
  239. package/dist/run-cancel-context.js +7 -0
  240. package/dist/run-local.js +2 -8
  241. package/dist/runs.d.ts +39 -12
  242. package/dist/runs.js +69 -55
  243. package/dist/sandbox-egress-policy.d.ts +4 -1
  244. package/dist/sandbox-egress-policy.js +3 -1
  245. package/dist/sandbox-git-plane.d.ts +31 -12
  246. package/dist/sandbox-git-plane.js +24 -4
  247. package/dist/security.d.ts +11 -2
  248. package/dist/security.js +8 -0
  249. package/dist/server-secret-env.d.ts +2 -1
  250. package/dist/server-secret-env.js +2 -4
  251. package/dist/session-error-wire.d.ts +46 -4
  252. package/dist/session-error-wire.js +23 -0
  253. package/dist/session-incarnation.d.ts +9 -3
  254. package/dist/session-sync-kernel.d.ts +42 -1
  255. package/dist/session-sync-kernel.js +17 -0
  256. package/dist/session-sync.js +16 -4
  257. package/dist/sql-engine-coordinates.d.ts +49 -0
  258. package/dist/sql-engine-coordinates.js +27 -0
  259. package/dist/standing-answer-grant.d.ts +31 -2
  260. package/dist/standing-answer-grant.js +14 -2
  261. package/dist/store-posture.d.ts +1 -1
  262. package/dist/task-a2a.d.ts +1 -1
  263. package/dist/task-mcp.d.ts +1 -1
  264. package/dist/task-settings.d.ts +62 -27
  265. package/dist/task-settings.js +105 -29
  266. package/dist/terminal.d.ts +97 -20
  267. package/dist/terminal.js +107 -18
  268. package/dist/tool-approval.d.ts +4 -3
  269. package/dist/tool-approval.js +12 -14
  270. package/dist/trace/core-keyset-guard.d.ts +196 -2
  271. package/dist/trace/engine-notice-wire.d.ts +8 -2
  272. package/dist/trace/engine-notice-wire.js +4 -2
  273. package/dist/trace/exit-seat.d.ts +9 -1
  274. package/dist/trace/exit-seat.js +1 -0
  275. package/dist/trace/ledger-sink.d.ts +8 -4
  276. package/dist/trace/ledger-sink.js +2 -2
  277. package/dist/trace/model-segment.d.ts +2 -2
  278. package/dist/trace/project.d.ts +18 -1
  279. package/dist/trace/project.js +16 -0
  280. package/dist/trace/redact.js +323 -90
  281. package/dist/trace/self-minted-wire.d.ts +419 -4
  282. package/dist/trace/self-minted-wire.js +145 -0
  283. package/dist/trace/wire-projection-faces.d.ts +1 -1
  284. package/dist/trace/wire-projection-faces.js +1 -1
  285. package/dist/verifier-pause-card.d.ts +8 -0
  286. package/dist/verifier-pause-card.js +19 -0
  287. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  288. package/dist/web-admin-client/apply-effective.js +22 -2
  289. package/dist/web-admin-client/candidate-gate.d.ts +1 -1
  290. package/dist/web-admin-client/effective-keys.js +2 -0
  291. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  292. package/dist/web-admin-client/types.d.ts +4 -0
  293. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  294. package/package.json +2 -2
  295. package/wire-changes/server@7.141.0.json +367 -0
  296. package/wire-changes/server@7.142.0.json +639 -0
@@ -1,6 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { type LaneHomeConfig } from "../lane-home-probe.js";
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 } from "@sema-agent/core";
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
+ import { type SandboxReadFloorKnob } from "../execution-lane-caps.js";
5
+ import { type SandboxReadFloor } from "./sandbox-read-floor.js";
6
+ import type { WorkspaceFill, WorkspaceFillOf } from "./workspace-fill.js";
4
7
  /** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
5
8
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
6
9
  /** Construction config for {@link RemoteLocalDockerExecutionEnv}. */
@@ -55,14 +58,22 @@ export interface LocalDockerEnvConfig extends LaneHomeConfig {
55
58
  /** Stable id mixed into the container NAME (e.g. the sessionId). A random suffix is always appended so two
56
59
  * envs sharing an id never collide. Default = a random id. */
57
60
  id?: string;
61
+ /** `SANDBOX_READ_FLOOR`(keep | off;缺席 ⇒ keep):本容器守不守读拒表的字节地板 = 读地板模块服务本车道 ∧ 本键不是 off(`sandbox-read-floor.ts`)。 */
62
+ sandboxReadFloor?: SandboxReadFloorKnob;
63
+ /** 恢复点的填充座(`workspace-fill.ts`;工厂按 run 装配):容器新建之后、交给本环境之前,经本适配器自己的 `docker exec` 往工作区里放内容。缺席 ⇒ 不放。 */
64
+ workspaceFill?: WorkspaceFill;
58
65
  }
59
66
  /** Inject `spawn` for tests (a fake docker CLI). Defaults to node's child_process spawn. */
60
67
  export interface LocalDockerEnvDeps {
61
68
  spawn?: typeof spawn;
69
+ /** 这只环境手里的读地板模块(缺席 ⇒ `createSandboxReadFloor` 按旋钮选;钉注入守地板的模块)。能力块的 `keepsReadFloor` 读它的状态。 */
70
+ readFloor?: SandboxReadFloor;
62
71
  }
63
72
  export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv {
64
- /** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。 */
65
- readonly capabilities: import("@sema-agent/core").ExecutionEnvCapabilities;
73
+ /** 能力块的唯一作者 = `capabilitiesFor`(`execution-lane-caps.ts`;逐键表态与理由在那里,本类零能力字面量)。读地板旋钮是它的输入,{@link floor} 由同一只判据、同一个旋钮值选出。 */
74
+ readonly capabilities: ExecutionEnvCapabilities;
75
+ /** 读拒表字节地板模块:容器里每一次 `docker exec` 起动都先经它的 `wrap`({@link startArgv},唯一接缝)。 */
76
+ private readonly floor;
66
77
  /** Working directory inside the container; relative paths resolve against it (ExecutionEnv contract). */
67
78
  cwd: string;
68
79
  /** core `ExecutionEnv.homeDir`([ref]):本容器里执行用户的 home —— 声明在场 ⇒ 声明(开环境那一拍与 `$HOME` 对证过),否则开容器那一拍实测的 `$HOME`,见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
@@ -84,12 +95,15 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
84
95
  private destroyed;
85
96
  /** In-flight connect, memoized so concurrent lazy first-use starts exactly one container (no double-run). */
86
97
  private connecting?;
87
- /** 家目录:环境答了而与声明不一致 ⇒ 这只环境的拒,记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。 */
88
- private homeRefusal?;
98
+ /** 这只环境的拒 —— 记一次、此后每次连接原样回它(同一镜像同一答案:不再起容器、不再问)。两个写者:家目录(环境答了而与声明不一致)
99
+ * 与恢复点的填充(换一只容器也是同一个答的拒:种子不是项目包、摘要不符、镜像没有 `tar`、镜像预置撞名 —— `FillOutcome.standing`)。 */
100
+ private connectRefusal?;
89
101
  /** 家目录的座(声明在场 ⇒ 开环境那一拍对证 · 否则开容器那一拍实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。与其余读得出家目录的车道同一只判官、同一句拒。 */
90
102
  private readonly homeSeat;
91
103
  constructor(config: LocalDockerEnvConfig, deps?: LocalDockerEnvDeps);
92
104
  private resolve;
105
+ /** 收据句柄。还没连上(懒环境:续跑腿的引擎在恢复相**之前**先取它比车道)⇒ 未连接形 `{ sandboxId: "(unconnected)", provider, mountPath }`(与 k8s / e2b 车道同形),
106
+ * 不抛 —— 抛了,续跑腿在恢复相就失败。 */
93
107
  workspaceHandle(): WorkspaceHandle;
94
108
  connect(config?: RemoteConnectConfig): Promise<{
95
109
  ok: true;
@@ -99,6 +113,10 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
99
113
  error: RemoteExecutionError;
100
114
  }>;
101
115
  private doConnect;
116
+ /** 恢复点的填充:有座 ⇒ 经 {@link fillChannel} 放(容器恒是新起的 ⇒ `minted`;本车道不可挂起);没有 ⇒ 什么都不放。 */
117
+ private fillWorkspace;
118
+ /** 恢复点手里的通道:`docker exec` 经唯一起动接缝(起动形 `adapter`,与引擎命令同一执行用户;连接进行中,不经懒连接的文件面);写 = 容器内 `cat >` 从 stdin 收整份字节。 */
119
+ private fillChannel;
102
120
  /**
103
121
  * 家目录:座要问({@link LaneHomeSeat.asks}:声明在场 ⇒ 对证;没声明 ⇒ 实测)⇒ 在本容器里问一次 `$HOME`(与命令同一条 `docker exec` 通道、同一份 `-e` env),读数交座。
104
122
  * 声明在场:环境答了而不一致 / 不是绝对路径 ⇒ 回拒并记在环境上(同一镜像同一答案:此后每次连接原样回它,不再起容器、不再问);没答上(exec 失败 / 超时 / 非零退出)⇒ 这一次同样不开,
@@ -159,7 +177,7 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
159
177
  }): Promise<Result<string[], FileError>>;
160
178
  /** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts` `writeFileStaged` / `appendFileStaged`;父目录与私有暂存目录由编排的准备那一步建):
161
179
  * 上传 = 容器内 `cat` 从 stdin 收整份字节落到私有目录里的暂存件({@link pipeIn});提交 = 容器内一次 shell 往返。两者同一身份 ——
162
- * 同一只 argv 构造({@link execArgv}),都是镜像的用户。不用 `docker cp`:它由守护进程以 root 落盘,镜像用户 ≠ root 时暂存件属 root、
180
+ * 同一只 argv 构造({@link startArgv}),都是镜像的用户。不用 `docker cp`:它由守护进程以 root 落盘,镜像用户 ≠ root 时暂存件属 root、
163
181
  * 提交方拿不走也清不掉,「目录不收新名字」也探不出来(root 哪里都写得进)。 */
164
182
  private stagedLane;
165
183
  /** 整旧或整新(暂存 → 一次 `mv -f`)。修前 `docker cp` 直落目标(以守护进程的 root 身份):传输中途断 / 取消时目标剩什么取决于守护进程的解包实现,本适配器不依赖它。 */
@@ -205,17 +223,23 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
205
223
  /**
206
224
  * 交给宿主 `docker` CLI 的 env —— 宿主子进程 env 的唯一组合(`server-secret-env.ts` `hostSubprocessEnv`),继承词 `"all"`:本服务自有凭据(起服声明的名与值)
207
225
  * 任何词都不交,CLI 与它拉起的凭据助手 / CLI 插件要读的变量(`DOCKER_*` / `AWS_*` …)照交 —— 形状剥会剥 `AWS_SECRET_ACCESS_KEY` 一类,ECR / GCR 凭据助手因此断。
208
- * 命令本身跑在容器里,容器的 env 只由 `execArgv` 的 `-e K=V` 显式给,本 env 到不了容器。每次起进程现算一次(与 host shell 同)。
226
+ * 命令本身跑在容器里,容器的 env 只由 `startArgv` 的 `-e K=V` 显式给,本 env 到不了容器。每次起进程现算一次(与 host shell 同)。
209
227
  */
210
228
  private cliEnv;
211
229
  /** Prepend `-H <dockerHost>` to a docker argv when configured (per-invocation, not the process env). */
212
230
  private withHostFlag;
213
- /** Build the `docker exec` argv for a command, applying cwd (`-w`) and per-command env (`-e K=V`).
214
- * argv elements are SEPARATE (no host-side shell join) — the command runs in the CONTAINER's `/bin/sh -c`.
215
- * `stdin` ⇒ `-i` (the payload of {@link pipeIn}); nothing else differs, so a piped upload runs as the same user as every other exec. */
216
- private execArgv;
217
- /** Like {@link execArgv} but for an internal FileSystem helper command (uses the workspace cwd + adapter env). */
218
- private execRaw;
231
+ /**
232
+ * **唯一起动接缝**:容器里的每一次 `docker exec` 起动(引擎的命令、文件面助手、探针、地板的引导命令)都从这里拿 argv —— 先问地板模块
233
+ * `wrap(起动形)`,再按答案编码(身份旗标 + 监督者命令行头;不守地板 ⇒ 与改前逐字同)。本适配器对地板零判断;模块拒 ⇒ `spawn_error`,命令从未起。
234
+ * argv elements are SEPARATE (no host-side shell join) — the command runs in the CONTAINER's shell (`-c`). `stdin` ⇒ `-i` (the payload of
235
+ * {@link pipeIn}); nothing else differs, so a piped upload runs as the same user as every other exec. cwd (`-w`) and per-command env (`-e K=V`;
236
+ * adapter base env under, per-command wins) — except `containerDefaults`: the command asks about the container's OWN defaults (the shell probe),
237
+ * so it carries neither (the bare argv is then `exec <name> <shell> -c <cmd>`, byte-identical to the pre-floor probe).
238
+ */
239
+ private startArgv;
240
+ /** 地板模块的三件原语(引导命令 / 放置 / 中继)。引导与放置经 {@link startArgv}(起动形 `bootstrap`:只在附着中合法);中继以模块给的身份起一只
241
+ * 长连的 `docker exec -i`(stdin / stdout 作双工),随本环境 `destroy` 由模块关。 */
242
+ private floorTransport;
219
243
  /** Spawn the docker binary with argv; collect stdout/stderr (+ raw bytes when binary), bound by timeout/abort. */
220
244
  private docker;
221
245
  /** Lazy connect for an exec path → ExecutionError on failure (fail closed, never throws). */
@@ -244,6 +268,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
244
268
  * owns the lifetime and calls `destroy()` (= `docker rm -f`) on task end. Folds the per-task `ctx.taskId`/
245
269
  * `ctx.sessionId` into the container name so two concurrent tasks never share a container.
246
270
  */
247
- 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;
248
274
  export {};
249
275
  //# sourceMappingURL=remote-env-local-docker.d.ts.map
@@ -9,6 +9,7 @@ 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
11
  import { capabilitiesFor } from "../execution-lane-caps.js";
12
+ import { ADAPTER_START, BOOTSTRAP_START, createSandboxReadFloor, dockerIdentityFlags, engineStart, floorArgv, } from "./sandbox-read-floor.js";
12
13
  import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
13
14
  import { createPosixShellFs } from "./posix-shell-fs.js";
14
15
  const PROVIDER = "local-docker";
@@ -21,7 +22,8 @@ const unsupported = (op) => ({
21
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)`),
22
23
  });
23
24
  export class RemoteLocalDockerExecutionEnv {
24
- capabilities = capabilitiesFor({ remoteExec: { provider: "local-docker" } });
25
+ capabilities;
26
+ floor;
25
27
  cwd;
26
28
  get homeDir() {
27
29
  return this.homeSeat.home;
@@ -35,7 +37,7 @@ export class RemoteLocalDockerExecutionEnv {
35
37
  handle;
36
38
  destroyed = false;
37
39
  connecting;
38
- homeRefusal;
40
+ connectRefusal;
39
41
  homeSeat;
40
42
  constructor(config, deps = {}) {
41
43
  if (!config.image || !config.image.trim()) {
@@ -52,6 +54,8 @@ export class RemoteLocalDockerExecutionEnv {
52
54
  this.cwd = this.cfg.mountPath;
53
55
  this.homeSeat = new LaneHomeSeat(PROVIDER, config);
54
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 });
55
59
  const id = config.id ?? randomBytes(6).toString("hex");
56
60
  const suffix = randomBytes(4).toString("hex");
57
61
  this.containerName = `sema-docker-${sanitizeId(id)}-${suffix}`;
@@ -60,17 +64,15 @@ export class RemoteLocalDockerExecutionEnv {
60
64
  return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
61
65
  }
62
66
  workspaceHandle() {
63
- if (!this.handle)
64
- throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
65
- return this.handle;
67
+ return this.handle ?? { sandboxId: "(unconnected)", provider: PROVIDER, mountPath: this.cfg.mountPath };
66
68
  }
67
69
  async connect(config) {
68
70
  if (config?.snapshotId)
69
71
  return unsupported("connect(snapshotId)");
70
72
  if (this.destroyed)
71
73
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
72
- if (this.homeRefusal)
73
- return { ok: false, error: this.homeRefusal };
74
+ if (this.connectRefusal)
75
+ return { ok: false, error: this.connectRefusal };
74
76
  if (config?.abortSignal?.aborted)
75
77
  return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
76
78
  if (this.containerId)
@@ -130,20 +132,48 @@ export class RemoteLocalDockerExecutionEnv {
130
132
  await this.forceRemove().catch(() => { });
131
133
  return { ok: false, error: home };
132
134
  }
135
+ await this.floor.attach(this.floorTransport(), { origin: "created", workspaceRoot: this.cfg.mountPath });
133
136
  if (this.destroyed) {
134
137
  await this.forceRemove().catch(() => { });
135
138
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env destroyed during connect") };
136
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
+ }
137
147
  this.containerId = run.value.stdout.trim() || this.containerName;
138
148
  this.handle = { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
139
149
  if (config?.secrets?.length) {
140
150
  }
141
151
  return ok(this.handle);
142
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
+ }
143
172
  async homeVerdict(signal) {
144
173
  if (!this.homeSeat.asks)
145
174
  return undefined;
146
- const probe = await this.docker(this.execRaw(LANE_HOME_PROBE_COMMAND), { signal, timeoutMs: this.cfg.controlTimeoutMs });
175
+ const argv = this.startArgv(ADAPTER_START, LANE_HOME_PROBE_COMMAND);
176
+ const probe = argv.ok ? await this.docker(argv.value, { signal, timeoutMs: this.cfg.controlTimeoutMs }) : argv;
147
177
  if (!probe.ok && probe.error.code === "aborted")
148
178
  return new RemoteExecutionError("aborted", "connect aborted while reading the environment's home");
149
179
  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 });
@@ -151,7 +181,7 @@ export class RemoteLocalDockerExecutionEnv {
151
181
  return undefined;
152
182
  const error = new RemoteExecutionError("connect_failed", verdict.refusal);
153
183
  if (verdict.answered)
154
- this.homeRefusal = error;
184
+ this.connectRefusal = error;
155
185
  return error;
156
186
  }
157
187
  async suspendVM(_options) {
@@ -166,8 +196,8 @@ export class RemoteLocalDockerExecutionEnv {
166
196
  async reconnect(sessionToken) {
167
197
  if (this.destroyed)
168
198
  return { ok: false, error: new RemoteExecutionError("connect_failed", "execution env already destroyed") };
169
- if (this.homeRefusal)
170
- return { ok: false, error: this.homeRefusal };
199
+ if (this.connectRefusal)
200
+ return { ok: false, error: this.connectRefusal };
171
201
  if (sessionToken && sessionToken !== this.containerName) {
172
202
  return { ok: false, error: new RemoteExecutionError("connect_failed", `reconnect token '${String(sessionToken)}' does not match this env's container '${this.containerName}'`) };
173
203
  }
@@ -183,8 +213,8 @@ export class RemoteLocalDockerExecutionEnv {
183
213
  const home = this.containerId === undefined ? await this.homeVerdict() : undefined;
184
214
  if (home !== undefined)
185
215
  return { ok: false, error: home };
186
- if (this.destroyed || this.homeRefusal)
187
- 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") };
188
218
  this.containerId = this.containerName;
189
219
  this.handle ??= { sandboxId: this.containerName, provider: PROVIDER, mountPath: this.cfg.mountPath, sessionToken: this.containerName };
190
220
  return ok(this.handle);
@@ -199,6 +229,7 @@ export class RemoteLocalDockerExecutionEnv {
199
229
  this.connecting = undefined;
200
230
  this.containerId = undefined;
201
231
  await inflight?.catch(() => { });
232
+ this.floor.dispose();
202
233
  await this.forceRemove().catch(() => { });
203
234
  }
204
235
  async cleanup() {
@@ -213,8 +244,10 @@ export class RemoteLocalDockerExecutionEnv {
213
244
  const ready = await this.ensureConnectedExec();
214
245
  if (!ready.ok)
215
246
  return ready;
216
- const execArgs = this.execArgv(command, options?.cwd, options?.env);
217
- const r = await this.docker(execArgs, {
247
+ const execArgs = this.startArgv(engineStart(options?.readFloor), command, { cwd: options?.cwd, env: options?.env });
248
+ if (!execArgs.ok)
249
+ return execArgs;
250
+ const r = await this.docker(execArgs.value, {
218
251
  signal: options?.abortSignal,
219
252
  timeoutMs: options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs,
220
253
  onStdout: options?.onStdout,
@@ -228,8 +261,10 @@ export class RemoteLocalDockerExecutionEnv {
228
261
  const ready = await this.ensureConnectedExec();
229
262
  if (!ready.ok)
230
263
  throw new RemoteExecutionError("connect_failed", ready.error.message, ready.error);
231
- const execArgs = this.execArgv(command, options?.cwd, options?.env);
232
- const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(execArgs), { stdio: ["ignore", "pipe", "pipe"], env: this.cliEnv() });
264
+ const execArgs = this.startArgv(engineStart(undefined), command, { cwd: options?.cwd, env: options?.env });
265
+ if (!execArgs.ok)
266
+ throw new RemoteExecutionError("unsupported", execArgs.error.message, execArgs.error);
267
+ const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(execArgs.value), { stdio: ["ignore", "pipe", "pipe"], env: this.cliEnv() });
233
268
  const queue = [];
234
269
  let finished = false;
235
270
  let failure;
@@ -361,7 +396,8 @@ export class RemoteLocalDockerExecutionEnv {
361
396
  if (!ready.ok)
362
397
  return ready;
363
398
  const abs = this.resolve(p);
364
- const r = await this.docker(this.execRaw(`cat ${shellQuote(abs)}`), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, binary: true });
399
+ const argv = this.startArgv(ADAPTER_START, `cat ${shellQuote(abs)}`);
400
+ const r = argv.ok ? await this.docker(argv.value, { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, binary: true }) : argv;
365
401
  if (!r.ok)
366
402
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
367
403
  if (r.value.exitCode !== 0)
@@ -423,7 +459,10 @@ export class RemoteLocalDockerExecutionEnv {
423
459
  if (!c.ok)
424
460
  return { ok: false, error: new ExecutionError("shell_unavailable", `local-docker connect failed: ${c.error.message}`, c.error) };
425
461
  }
426
- return this.docker(this.execArgv(cmd, undefined, undefined, input !== undefined), { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
462
+ const argv = this.startArgv(ADAPTER_START, cmd, { stdin: input !== undefined });
463
+ if (!argv.ok)
464
+ return argv;
465
+ return this.docker(argv.value, { signal: abortSignal, timeoutMs: this.cfg.commandTimeoutMs, ...(input !== undefined ? { input } : {}) });
427
466
  }
428
467
  async fileInfo(p, abortSignal) {
429
468
  return this.posixFs.statFileInfo(p, abortSignal);
@@ -461,18 +500,68 @@ export class RemoteLocalDockerExecutionEnv {
461
500
  withHostFlag(args) {
462
501
  return this.cfg.dockerHost ? ["-H", this.cfg.dockerHost, ...args] : args;
463
502
  }
464
- execArgv(command, cwd, env, stdin = false) {
465
- const args = stdin ? ["exec", "-i"] : ["exec"];
466
- const dir = cwd != null ? this.resolve(cwd) : this.cwd;
467
- args.push("-w", dir);
468
- const mergedEnv = { ...(this.cfg.env ?? {}), ...(env ?? {}) };
469
- for (const [k, v] of Object.entries(mergedEnv))
470
- args.push("-e", `${k}=${v}`);
471
- args.push(this.containerName, this.containerShell, "-c", command);
472
- return args;
473
- }
474
- execRaw(command) {
475
- return this.execArgv(command);
503
+ startArgv(start, command, o = {}) {
504
+ const launch = this.floor.wrap(start);
505
+ if (!launch.ok)
506
+ return launch;
507
+ const args = o.stdin ? ["exec", "-i"] : ["exec"];
508
+ args.push(...dockerIdentityFlags(launch.value));
509
+ if (o.containerDefaults !== true) {
510
+ args.push("-w", o.cwd != null ? this.resolve(o.cwd) : this.cwd);
511
+ const mergedEnv = { ...(this.cfg.env ?? {}), ...(o.env ?? {}) };
512
+ for (const [k, v] of Object.entries(mergedEnv))
513
+ args.push("-e", `${k}=${v}`);
514
+ }
515
+ args.push(this.containerName, ...floorArgv(launch.value, [o.shell ?? this.containerShell, "-c", command]));
516
+ return ok(args);
517
+ }
518
+ floorTransport() {
519
+ const control = { timeoutMs: this.cfg.controlTimeoutMs };
520
+ return {
521
+ bootstrap: async (command) => {
522
+ const argv = this.startArgv(BOOTSTRAP_START, command, { shell: "/bin/sh" });
523
+ if (!argv.ok)
524
+ return { ok: false, error: argv.error.message };
525
+ const r = await this.docker(argv.value, control);
526
+ return r.ok ? { ok: true, exitCode: r.value.exitCode, stdout: r.value.stdout, stderr: r.value.stderr } : { ok: false, error: r.error.message };
527
+ },
528
+ place: async (dest, bytes) => {
529
+ const dir = path.posix.dirname(dest);
530
+ const tmp = `${dest}.${randomBytes(6).toString("hex")}`;
531
+ const cmd = `umask 022 && mkdir -p ${shellQuote(dir)} && cat > ${shellQuote(tmp)} && chmod 0755 ${shellQuote(tmp)} && mv -f ${shellQuote(tmp)} ${shellQuote(dest)}`;
532
+ const argv = this.startArgv(BOOTSTRAP_START, cmd, { shell: "/bin/sh", stdin: true });
533
+ if (!argv.ok)
534
+ return { ok: false, error: argv.error.message };
535
+ const r = await this.docker(argv.value, { ...control, input: bytes });
536
+ if (!r.ok)
537
+ return { ok: false, error: r.error.message };
538
+ return r.value.exitCode === 0 ? { ok: true } : { ok: false, error: r.value.stderr.trim() || `exit ${r.value.exitCode}` };
539
+ },
540
+ openRelay: (identity, command, events) => {
541
+ const args = ["exec", "-i", ...dockerIdentityFlags({ mode: "control", identity }), this.containerName, "/bin/sh", "-c", command];
542
+ const child = this.spawnFn(this.cfg.dockerPath, this.withHostFlag(args), { stdio: ["pipe", "pipe", "pipe"], env: this.cliEnv() });
543
+ let closed = false;
544
+ const close = (why) => {
545
+ if (closed)
546
+ return;
547
+ closed = true;
548
+ events.closed(why);
549
+ };
550
+ child.stdout?.setEncoding("utf8");
551
+ child.stdout?.on("data", (d) => events.data(d));
552
+ child.stderr?.on("data", () => { });
553
+ child.stdin?.on("error", () => { });
554
+ child.on("error", (e) => close(`docker exec failed: ${e.message}`));
555
+ child.on("close", (code, signal) => close(`relay exited (${code ?? signal ?? "unknown"})`));
556
+ return {
557
+ send: (line) => void child.stdin?.write(line),
558
+ close: () => {
559
+ child.stdin?.end();
560
+ child.kill("SIGKILL");
561
+ },
562
+ };
563
+ },
564
+ };
476
565
  }
477
566
  docker(args, opts) {
478
567
  return new Promise((resolve) => {
@@ -571,7 +660,8 @@ export class RemoteLocalDockerExecutionEnv {
571
660
  }
572
661
  async detectContainerShell() {
573
662
  try {
574
- const r = await this.docker(["exec", this.containerName, "/bin/sh", "-c", "command -v bash"], { timeoutMs: this.cfg.controlTimeoutMs });
663
+ const argv = this.startArgv(ADAPTER_START, "command -v bash", { shell: "/bin/sh", containerDefaults: true });
664
+ const r = argv.ok ? await this.docker(argv.value, { timeoutMs: this.cfg.controlTimeoutMs }) : argv;
575
665
  if (r.ok && r.value.exitCode === 0 && r.value.stdout.trim().length > 0)
576
666
  this.containerShell = "bash";
577
667
  }
@@ -616,6 +706,10 @@ function errMsg(e) {
616
706
  return e instanceof Error ? e.message : String(e);
617
707
  }
618
708
  export function localDockerExecutionEnvFactory(config, deps = {}) {
619
- 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
+ };
620
714
  }
621
715
  //# sourceMappingURL=remote-env-local-docker.js.map
@@ -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, 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";
@@ -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
@@ -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";
@@ -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
  }
@@ -412,6 +413,14 @@ export class SqlRunStore {
412
413
  const r = rows[0];
413
414
  return r === undefined ? undefined : { taskId: String(r.task_id), owner: r.owner ?? null, createdAt: iso(r.created_at) };
414
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
+ }
415
424
  async listSessions(opts) {
416
425
  if (this.db.dialect === "mysql") {
417
426
  const params = [];