@sema-agent/server 7.137.0 → 7.139.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 +600 -142
  2. package/README.md +7 -3
  3. package/README.zh-CN.md +6 -3
  4. package/USAGE.md +132 -41
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/lib/lanes.json +3 -2
  7. package/dist/adoption/wire.d.ts +3 -3
  8. package/dist/approval-card.d.ts +23 -16
  9. package/dist/approval-card.js +1 -0
  10. package/dist/audit.js +1 -0
  11. package/dist/background-agent-name.d.ts +5 -0
  12. package/dist/background-agent-name.js +5 -0
  13. package/dist/bench/s1/arms.d.ts +1 -1
  14. package/dist/bench/s1/arms.js +1 -3
  15. package/dist/bench/s1/run-firm.js +2 -2
  16. package/dist/boot/budget-tracing.d.ts +1 -12
  17. package/dist/boot/deployment-mods.d.ts +46 -16
  18. package/dist/boot/deployment-mods.js +82 -6
  19. package/dist/boot/device-lane.d.ts +8 -3
  20. package/dist/boot/device-lane.js +7 -2
  21. package/dist/boot/execution-env.d.ts +4 -2
  22. package/dist/boot/execution-env.js +16 -24
  23. package/dist/boot/execution-lane.d.ts +1 -1
  24. package/dist/boot/governance-seams.d.ts +12 -18
  25. package/dist/boot/governance-seams.js +10 -24
  26. package/dist/boot/leader.d.ts +3 -0
  27. package/dist/boot/leader.js +1 -0
  28. package/dist/boot/memory-consolidation.js +2 -2
  29. package/dist/boot/org-delivery.js +15 -2
  30. package/dist/boot/resolve-spec.d.ts +4 -1
  31. package/dist/boot/resolve-spec.js +29 -19
  32. package/dist/boot/retention-lane.d.ts +14 -8
  33. package/dist/boot/retention-lane.js +2 -8
  34. package/dist/boot/runner-deps.d.ts +10 -4
  35. package/dist/boot/runner-deps.js +2 -0
  36. package/dist/boot/session-faces.d.ts +0 -3
  37. package/dist/boot/session-faces.js +15 -8
  38. package/dist/boot/stage-02-web-admin-pull.js +2 -2
  39. package/dist/boot/stage-04-stores.d.ts +1 -12
  40. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  41. package/dist/boot/stage-05-execution-env.js +8 -5
  42. package/dist/boot/stage-06-runners.d.ts +3 -12
  43. package/dist/boot/stage-06-runners.js +5 -3
  44. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  45. package/dist/boot/stage-07-capability-layer.js +2 -2
  46. package/dist/boot/stage-08-reapers.d.ts +3 -12
  47. package/dist/boot/stage-09-leader.d.ts +3 -12
  48. package/dist/boot/stage-09-leader.js +2 -3
  49. package/dist/boot/stage-10-http-server.d.ts +3 -12
  50. package/dist/boot/stage-10-http-server.js +5 -3
  51. package/dist/boot/stores.js +4 -4
  52. package/dist/boot/web-admin.d.ts +5 -2
  53. package/dist/boot/web-admin.js +5 -5
  54. package/dist/brain.js +5 -5
  55. package/dist/budget.d.ts +17 -15
  56. package/dist/budget.js +2 -2
  57. package/dist/capabilities/mcp-probe.d.ts +36 -11
  58. package/dist/capabilities/mcp-probe.js +44 -5
  59. package/dist/capabilities/scenarios.d.ts +1 -1
  60. package/dist/capability-facts.d.ts +21 -7
  61. package/dist/capability-facts.js +13 -3
  62. package/dist/checkpoint-identity.d.ts +13 -6
  63. package/dist/config-catalog.js +14 -8
  64. package/dist/config-invariants.d.ts +23 -3
  65. package/dist/config-invariants.js +42 -0
  66. package/dist/config-provider.d.ts +19 -24
  67. package/dist/config-provider.js +27 -12
  68. package/dist/config-types.d.ts +25 -12
  69. package/dist/config.d.ts +33 -0
  70. package/dist/config.js +52 -20
  71. package/dist/delegated-session-owner.d.ts +2 -2
  72. package/dist/deployment-shape.d.ts +7 -7
  73. package/dist/device-enrollment.d.ts +41 -2
  74. package/dist/device-enrollment.js +27 -12
  75. package/dist/device-store.d.ts +58 -85
  76. package/dist/device-store.js +19 -49
  77. package/dist/device-ws-hub.js +2 -1
  78. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  79. package/dist/exec-cut.d.ts +60 -0
  80. package/dist/exec-cut.js +36 -0
  81. package/dist/execution-lane-caps.d.ts +19 -0
  82. package/dist/execution-lane-caps.js +10 -0
  83. package/dist/execution-placement.d.ts +259 -0
  84. package/dist/execution-placement.js +199 -0
  85. package/dist/field-conflict.d.ts +3 -2
  86. package/dist/field-conflict.js +1 -0
  87. package/dist/gate-settlement.d.ts +71 -0
  88. package/dist/gate-settlement.js +40 -0
  89. package/dist/hooks/classic-stations.d.ts +1 -0
  90. package/dist/hooks/classic-stations.js +1 -0
  91. package/dist/hooks/command-spawn.d.ts +2 -2
  92. package/dist/hooks/hook-runner.js +14 -14
  93. package/dist/hosted-posture.d.ts +2 -2
  94. package/dist/http/admission.js +10 -19
  95. package/dist/http/checkpoint-ctx.d.ts +21 -12
  96. package/dist/http/checkpoint-ctx.js +4 -2
  97. package/dist/http/claimed-run.d.ts +1 -1
  98. package/dist/http/dispatch.d.ts +4 -0
  99. package/dist/http/dispatch.js +3 -0
  100. package/dist/http/durable-run-leg.js +12 -6
  101. package/dist/http/execution-context.d.ts +218 -0
  102. package/dist/http/execution-context.js +330 -0
  103. package/dist/http/memory-compliance-failure-reply.js +3 -0
  104. package/dist/http/resume-legs.d.ts +5 -11
  105. package/dist/http/resume-legs.js +179 -104
  106. package/dist/http/route-ctx.d.ts +44 -59
  107. package/dist/http/route-ctx.js +2 -5
  108. package/dist/http/routes/a2a-serve.js +10 -4
  109. package/dist/http/routes/agents-roster.d.ts +1 -1
  110. package/dist/http/routes/agents-roster.js +5 -1
  111. package/dist/http/routes/approvals-assistant.js +6 -6
  112. package/dist/http/routes/background.d.ts +29 -1
  113. package/dist/http/routes/background.js +29 -2
  114. package/dist/http/routes/capabilities.js +12 -10
  115. package/dist/http/routes/devices.d.ts +13 -19
  116. package/dist/http/routes/devices.js +21 -26
  117. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  118. package/dist/http/routes/mcp-reconnect.js +7 -5
  119. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  120. package/dist/http/routes/memory-origin.d.ts +1 -1
  121. package/dist/http/routes/rules.js +3 -3
  122. package/dist/http/routes/runs.js +11 -7
  123. package/dist/http/routes/session-sync.js +110 -15
  124. package/dist/http/routes/sessions.d.ts +6 -0
  125. package/dist/http/routes/sessions.js +119 -13
  126. package/dist/http/routes/side-query.d.ts +12 -4
  127. package/dist/http/routes/side-query.js +6 -8
  128. package/dist/http/routes/tasks.js +800 -782
  129. package/dist/http/server.d.ts +61 -39
  130. package/dist/http/server.js +11 -0
  131. package/dist/http/session-inherit-env.d.ts +3 -4
  132. package/dist/http/session-inherit-env.js +1 -1
  133. package/dist/http/wire-types.d.ts +22 -18
  134. package/dist/identity-source.d.ts +1 -1
  135. package/dist/index.d.ts +3 -0
  136. package/dist/index.js +1 -0
  137. package/dist/leader/wire.d.ts +7 -0
  138. package/dist/leader/wire.js +5 -4
  139. package/dist/mcp-assertion-header.d.ts +51 -40
  140. package/dist/mcp-assertion-header.js +10 -11
  141. package/dist/mcp-authorization-face.d.ts +27 -0
  142. package/dist/mcp-authorization-face.js +15 -0
  143. package/dist/memory-operator-faces.d.ts +13 -4
  144. package/dist/memory-operator-faces.js +1 -17
  145. package/dist/memory-sync-client.d.ts +53 -14
  146. package/dist/memory-sync-client.js +187 -92
  147. package/dist/memory-sync.d.ts +1 -1
  148. package/dist/memory-sync.js +3 -2
  149. package/dist/model-route-gap.d.ts +6 -22
  150. package/dist/model-route-gap.js +7 -5
  151. package/dist/mods-declaration.d.ts +110 -0
  152. package/dist/mods-declaration.js +109 -0
  153. package/dist/observability/cost-taxonomy.d.ts +2 -2
  154. package/dist/observability/err-text.d.ts +5 -0
  155. package/dist/observability/err-text.js +9 -0
  156. package/dist/observability/fail-open.d.ts +17 -1
  157. package/dist/observability/fail-open.js +17 -1
  158. package/dist/observability/run-terminal.js +0 -1
  159. package/dist/observability/secret-env-scrub.d.ts +1 -1
  160. package/dist/observability/secret-env-scrub.js +1 -1
  161. package/dist/orchestration/workflow-notify-journal.js +4 -4
  162. package/dist/org-memory-admission.d.ts +4 -3
  163. package/dist/org-memory-admission.js +6 -6
  164. package/dist/parked-decide.d.ts +49 -11
  165. package/dist/parked-decide.js +25 -26
  166. package/dist/parked-revive-surface.d.ts +12 -15
  167. package/dist/parked-revive-surface.js +3 -6
  168. package/dist/peer-seat.d.ts +156 -0
  169. package/dist/peer-seat.js +273 -0
  170. package/dist/permission-rule-vocab.d.ts +7 -7
  171. package/dist/permission-rule-vocab.js +3 -3
  172. package/dist/plain-string-record.d.ts +38 -0
  173. package/dist/plain-string-record.js +38 -0
  174. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  175. package/dist/plugins/approval-ask-store-file.js +11 -0
  176. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  177. package/dist/plugins/approval-ask-store-memory.js +12 -0
  178. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  179. package/dist/plugins/approval-ask-store-sql.js +30 -12
  180. package/dist/plugins/background-agent-store-sql.js +6 -3
  181. package/dist/plugins/caching-session-store.d.ts +11 -2
  182. package/dist/plugins/caching-session-store.js +15 -3
  183. package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
  184. package/dist/plugins/checkpoint-store-sql.js +13 -15
  185. package/dist/plugins/device-store-sql.d.ts +25 -51
  186. package/dist/plugins/device-store-sql.js +49 -103
  187. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  188. package/dist/plugins/execution-placement-sql.js +223 -0
  189. package/dist/plugins/file-history-store-sql.d.ts +7 -6
  190. package/dist/plugins/file-history-store-sql.js +8 -7
  191. package/dist/plugins/file-run-store.d.ts +6 -1
  192. package/dist/plugins/file-run-store.js +4 -0
  193. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  194. package/dist/plugins/fork-routing-session-store.js +16 -0
  195. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  196. package/dist/plugins/k8s-pod-reaper.js +3 -3
  197. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  198. package/dist/plugins/local-checkpoint-store.js +0 -1
  199. package/dist/plugins/local-session-store.d.ts +183 -45
  200. package/dist/plugins/local-session-store.js +412 -105
  201. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  202. package/dist/plugins/mailbox-store-sql.js +103 -15
  203. package/dist/plugins/memory-key-guards.d.ts +4 -0
  204. package/dist/plugins/memory-key-guards.js +1 -0
  205. package/dist/plugins/memory-run-store.d.ts +1 -1
  206. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  207. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  208. package/dist/plugins/one-time-migrations.d.ts +117 -6
  209. package/dist/plugins/one-time-migrations.js +383 -14
  210. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
  212. package/dist/plugins/permission-rule-store-sql.js +34 -29
  213. package/dist/plugins/pg-pool.d.ts +7 -3
  214. package/dist/plugins/pg-session-storage.d.ts +41 -22
  215. package/dist/plugins/pg-session-storage.js +136 -55
  216. package/dist/plugins/remote-env-adb.d.ts +22 -4
  217. package/dist/plugins/remote-env-adb.js +70 -62
  218. package/dist/plugins/remote-env-device.d.ts +32 -12
  219. package/dist/plugins/remote-env-device.js +18 -17
  220. package/dist/plugins/remote-env-e2b.d.ts +19 -3
  221. package/dist/plugins/remote-env-e2b.js +41 -42
  222. package/dist/plugins/remote-env-file-error.d.ts +2 -0
  223. package/dist/plugins/remote-env-file-error.js +1 -1
  224. package/dist/plugins/remote-env-host.d.ts +23 -8
  225. package/dist/plugins/remote-env-host.js +66 -70
  226. package/dist/plugins/remote-env-k8s.d.ts +13 -16
  227. package/dist/plugins/remote-env-k8s.js +61 -54
  228. package/dist/plugins/remote-env-local-docker.d.ts +30 -5
  229. package/dist/plugins/remote-env-local-docker.js +46 -59
  230. package/dist/plugins/remote-env-ssh.d.ts +18 -4
  231. package/dist/plugins/remote-env-ssh.js +72 -56
  232. package/dist/plugins/remote-shell.d.ts +100 -3
  233. package/dist/plugins/remote-shell.js +166 -2
  234. package/dist/plugins/retention-store-sql.d.ts +23 -8
  235. package/dist/plugins/retention-store-sql.js +13 -1
  236. package/dist/plugins/run-store-sql.d.ts +13 -3
  237. package/dist/plugins/run-store-sql.js +5 -0
  238. package/dist/plugins/schema-consistency.d.ts +15 -5
  239. package/dist/plugins/schema-consistency.js +14 -8
  240. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  241. package/dist/plugins/schema-drift-sentence.js +38 -0
  242. package/dist/plugins/session-fork.d.ts +63 -12
  243. package/dist/plugins/session-fork.js +38 -21
  244. package/dist/plugins/session-ownership.d.ts +7 -0
  245. package/dist/plugins/session-ownership.js +5 -0
  246. package/dist/plugins/session-row-sql.d.ts +22 -0
  247. package/dist/plugins/session-row-sql.js +81 -0
  248. package/dist/plugins/session-store.d.ts +1 -1
  249. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  250. package/dist/plugins/session-workspace-cell.js +38 -0
  251. package/dist/plugins/store-backend.d.ts +1 -1
  252. package/dist/plugins/store-backend.js +26 -14
  253. package/dist/plugins/store-contracts.d.ts +25 -5
  254. package/dist/plugins/tidb-pool.d.ts +12 -7
  255. package/dist/plugins/tidb-pool.js +15 -3
  256. package/dist/plugins/tidb-session-store.d.ts +74 -28
  257. package/dist/plugins/tidb-session-store.js +132 -53
  258. package/dist/plugins/tool-result-store-sql.d.ts +16 -8
  259. package/dist/plugins/tool-result-store-sql.js +26 -17
  260. package/dist/plugins/worktree-isolation.d.ts +3 -2
  261. package/dist/plugins/worktree-isolation.js +1 -7
  262. package/dist/project-identity.d.ts +1 -14
  263. package/dist/project-identity.js +0 -4
  264. package/dist/project-memory.d.ts +9 -15
  265. package/dist/project-memory.js +5 -4
  266. package/dist/request-key-closure.js +1 -1
  267. package/dist/request-value-closure.d.ts +6 -7
  268. package/dist/request-value-closure.js +1 -3
  269. package/dist/rules-consent.js +6 -4
  270. package/dist/run-local.d.ts +6 -0
  271. package/dist/run-local.js +36 -26
  272. package/dist/runs.d.ts +9 -2
  273. package/dist/runs.js +9 -2
  274. package/dist/sandbox-git-plane.d.ts +2 -2
  275. package/dist/security.d.ts +38 -12
  276. package/dist/security.js +8 -2
  277. package/dist/server-secret-env.d.ts +15 -8
  278. package/dist/server-secret-env.js +4 -2
  279. package/dist/session-children-retire.d.ts +27 -9
  280. package/dist/session-children-retire.js +9 -5
  281. package/dist/session-incarnation.d.ts +19 -10
  282. package/dist/session-incarnation.js +5 -3
  283. package/dist/session-row.d.ts +114 -5
  284. package/dist/session-row.js +55 -5
  285. package/dist/session-titler.d.ts +2 -2
  286. package/dist/session-workspace.d.ts +130 -0
  287. package/dist/session-workspace.js +83 -0
  288. package/dist/spec-fields.d.ts +1 -1
  289. package/dist/standing-answer-grant.d.ts +2 -13
  290. package/dist/standing-answer-grant.js +0 -7
  291. package/dist/task-a2a.d.ts +44 -14
  292. package/dist/task-a2a.js +46 -20
  293. package/dist/task-cwd.d.ts +6 -1
  294. package/dist/task-mcp.d.ts +33 -20
  295. package/dist/task-mcp.js +46 -22
  296. package/dist/tool-approval.d.ts +38 -8
  297. package/dist/tool-approval.js +57 -13
  298. package/dist/trace/core-keyset-guard.d.ts +33 -18
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +2 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +3 -0
  303. package/dist/trace/extension-call-face.d.ts +33 -0
  304. package/dist/trace/extension-call-face.js +39 -0
  305. package/dist/trace/project.d.ts +26 -7
  306. package/dist/trace/project.js +30 -3
  307. package/dist/trace/redact.js +42 -13
  308. package/dist/trace/self-minted-wire.d.ts +240 -12
  309. package/dist/trace/self-minted-wire.js +100 -4
  310. package/dist/trace/wire-projection-faces.d.ts +6 -6
  311. package/dist/trace/wire-projection-faces.js +6 -5
  312. package/dist/url-refusal.d.ts +1 -1
  313. package/dist/user-authored-text.d.ts +2 -2
  314. package/dist/user-authored-text.js +16 -1
  315. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  316. package/dist/web-admin-client/apply-effective.js +20 -10
  317. package/dist/web-admin-client/center-request.d.ts +3 -2
  318. package/dist/web-admin-client/center-request.js +2 -3
  319. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  320. package/dist/web-admin-client/skills-mcp.js +32 -18
  321. package/dist/web-admin-client/types.d.ts +11 -0
  322. package/package.json +3 -3
  323. package/wire-changes/server@7.138.0.json +446 -0
  324. package/wire-changes/server@7.139.0.json +348 -0
  325. package/dist/http/device-placement.d.ts +0 -175
  326. package/dist/http/device-placement.js +0 -145
  327. package/dist/http/mcp-remote-text.d.ts +0 -5
  328. package/dist/http/mcp-remote-text.js +0 -7
@@ -1,6 +1,7 @@
1
1
  import { ExecutionError, FileError, RemoteExecutionError, type ExecutionEnv, type ExecutionEnvFactory, type ExecStreamOptions, type FileInfo, type OutputChunk, type RemoteConnectConfig, type RemoteExecutionEnv, type Result, type SessionToken, type SnapshotId, type VmLifecycleOptions, type WorkspaceHandle, type WriteExpectation, type WriteReceipt } from "@sema-agent/core";
2
2
  import { type DeviceOwner, type DeviceRejectCode, type DeviceRow, type DeviceStore } from "../device-store.js";
3
3
  import type { DeviceEnrollment } from "../device-enrollment.js";
4
+ import { type ExecutionPlacementSeat } from "../execution-placement.js";
4
5
  import type { DeviceWsHub } from "../device-ws-hub.js";
5
6
  import type { DeviceInstructionKind, DeviceOutcomeErrorCode } from "../device-ws-protocol.js";
6
7
  /** `WorkspaceHandle.provider` 与 `REMOTE_EXEC` 词表的那一个词,单点。 */
@@ -57,6 +58,8 @@ export interface DeviceEnvFactoryDeps {
57
58
  homeDir?: string;
58
59
  hub: DeviceWsHub;
59
60
  store: DeviceStore;
61
+ /** 会话店的执行席(绑定的唯一载体与唯一读;装配点交会话店的 `executionPlacement`,与准入链同一只)。工厂只调它的 `read(root)`。 */
62
+ seat: Pick<ExecutionPlacementSeat, "read">;
60
63
  enrollment: DeviceEnrollment;
61
64
  /** 指令级默认超时(ms;`DEVICE_EXEC_TIMEOUT_MS`,§4.7)。core 的 per-command `timeout` 可收紧它。 */
62
65
  execTimeoutMs?: number;
@@ -104,18 +107,22 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
104
107
  * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
105
108
  * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
106
109
  * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
107
- * 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
110
+ * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
108
111
  * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
109
112
  * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
110
113
  */
114
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在注册设备上(设备执行器),不在本进程所在的进程表。 */
115
+ /** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
111
116
  readonly capabilities: {
112
- isolation: boolean;
113
- suspendable: boolean;
114
- canonicalPathAuthoritative: boolean;
117
+ isolation: false;
118
+ suspendable: false;
119
+ canonicalPathAuthoritative: true;
120
+ sharesProcessTable: false;
121
+ hostLocalPaths: false;
122
+ externalContentTarget: true;
123
+ io: true;
124
+ keepsReadFloor: false;
115
125
  };
116
- readonly externalContentTarget = true;
117
- /** [ref]:设备路径不在 worker 宿主文件系统上 —— host-path 回执消费方据此拒绝把它当宿主路径呈现。 */
118
- readonly hostLocalPaths = false;
119
126
  cwd: string;
120
127
  /** 可选面(presence-typed,见文件头「铁律」段):构造期按设备声明装配,缺席就是缺席。 */
121
128
  readLink?: (path: string, abortSignal?: AbortSignal) => Promise<Result<string, FileError>>;
@@ -330,11 +337,24 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
330
337
  * `DEVICE_ERROR_CODES` 闭集(装配 / 数据错误取 {@link DEVICE_FACTORY_CONFIG_CODES}:placement 根为空 · 有效工作目录非绝对路径),句子里的 `[码]` 与 `.code` 同值。修前抛裸 `Error`,码只在句子里
331
338
  * (有效工作目录那一只直到 7.135.0 仍是裸 `Error`;钉:本工厂与 {@link effectiveDeviceCwd} 的函数体里没有 `new Error(`)。
332
339
  *
333
- * 🔴 **续跑腿的投递围栏**(core 7.54.0 [ref] r3 / [ref],`ExecutionEnvFactoryContext.resumeWorkspace`):行记下了设备(`resumeWorkspace.deviceId`)⇒
334
- * 本工厂**只**在那台设备上铸 —— placement 根此刻绑的不是它(换绑过)⇒ 抛 typed `device.identity_mismatch`(换绑后赎回的同一个词,零新码),
335
- * 句子点名两台设备;不连另一台、不改道(新设备没有那个工作区)。读当前绑定只为判出这一拒与取属主,铸的设备恒是行记下的那一台。
336
- * 「绑的就是它但现在不在线」与新跑同形:构造成功、`connect()` 回 typed `device.offline`(从不连别的设备)。
337
- * 行没记设备(非设备车道 / 早于该字段的行)⇒ 不声明要兑现的设备,按普通路由走。句柄(`workspaceHandle()`)恒报连接真到达的设备,不抄 `resumeWorkspace`。
340
+ * 🔴 **工厂认设备不认住客**(读口一只 = 会话店执行席 `seat.read(root)`,与解析器 / 换绑门 / 执行放置读面 / 准入链同一只读):按 placement 根读**当前**格,
341
+ * 与这条腿带来的设备句柄 `honor` 比,不同即 typed 拒。判序(一次行读):
342
+ * root = ctx.placementRootSessionId // 空 ⇒ config.placement_root_invalid
343
+ * honor = ctx.resumeWorkspace?.deviceId
344
+ * cell = seat.read(root).cell // 行不在 / 格缺 / mirror / writer(device, ∅) ⇒ device.not_bound(唯一读法 `boundDeviceOf`)
345
+ * honor ≠ undefined ∧ 格上设备 ≠ honor ⇒ device.identity_mismatch(句点名两台,零连接)
346
+ * readDeviceForAdmission(格上设备, 格上属主)
347
+ * 工厂**不**读 `task_run`、不读子代分区行、不按 `ctx.taskId`、不比化身、不比属主(住客谓词只用于写与 judged 复核,不是工厂的读者)。
348
+ * 续跑腿(core 7.54.0 [ref] r3 / [ref],`ExecutionEnvFactoryContext.resumeWorkspace`):行记下了设备 ⇒ 本工厂**只**在那台设备上铸 —— 根此刻绑的不是它(换绑过)
349
+ * ⇒ `device.identity_mismatch`(换绑后赎回的同一个词,零新码);不连另一台、不改道(新设备没有那个工作区)。「绑的就是它但现在不在线」与新跑同形:
350
+ * 构造成功、`connect()` 回 typed `device.offline`(从不连别的设备)。行没记设备(早于该字段的行)⇒ 引擎已命名的「行未记设备 ⇒ 普通路由」臂,按根读当前格。
351
+ * 句柄(`workspaceHandle()`)恒报连接真到达的设备,不抄 `resumeWorkspace`。
352
+ *
353
+ * 🔴 **过渡期(条件句)**:新派后代腿的放置句柄(引擎在 `ExecutionEnvFactoryContext` 上的放置工作区字段,`honor` 的第二来源)**只在本仓所基的引擎版本带它时**
354
+ * 进判序;本仓今天所基的引擎上下文没有这个字段 ⇒ `honor` 只取续跑腿的 `resumeWorkspace`,新派后代腿按根读当前格。本仓**不**注入任何替代见证、不造任何行
355
+ * (修在源头)。敞口(在那个字段到货之前成立):活过父 turn 的后台子代,在根换绑之后新派的腿会落到根当前设备 —— 换绑半边由换绑门的子代活性判关闭(归换绑门那一车),
356
+ * 删除半边另有票。字段到货后的形:`honor = ctx.resumeWorkspace?.deviceId ?? <放置句柄>.deviceId`,放置句柄的 provider 不是本车道 ⇒ 装配拒(`config.placement_root_invalid` 同级)。
357
+ * 本仓任何文本不得宣称工厂「一律按住客定址」。
338
358
  */
339
359
  export declare function deviceExecutionEnvFactory(deps: DeviceEnvFactoryDeps): ExecutionEnvFactory;
340
360
  /**
@@ -1,6 +1,8 @@
1
1
  import { posix } from "node:path";
2
2
  import { ExecutionError, FileError, RemoteExecutionError, } from "@sema-agent/core";
3
3
  import { DEVICE_ERROR_CODES, isDeviceAbsolutePath, } from "../device-store.js";
4
+ import { boundDeviceOf } from "../execution-placement.js";
5
+ import { createCutError, createExecFailure } from "../exec-cut.js";
4
6
  export const DEVICE_PROVIDER = "device";
5
7
  const ok = (value) => ({ ok: true, value });
6
8
  const STREAM_PENDING_MAX_BYTES = 2 * 1024 * 1024;
@@ -126,9 +128,7 @@ export class RemoteDeviceExecutionEnv {
126
128
  homeDir;
127
129
  cdPath = "unknown";
128
130
  withheldEnv = [];
129
- capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
130
- externalContentTarget = true;
131
- hostLocalPaths = false;
131
+ capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: true, io: true, keepsReadFloor: false };
132
132
  cwd;
133
133
  readLink;
134
134
  writeFileExclusive;
@@ -227,7 +227,8 @@ export class RemoteDeviceExecutionEnv {
227
227
  }
228
228
  let admitted;
229
229
  try {
230
- admitted = await this.cfg.enrollment.admitSession({ rootSessionId: this.cfg.rootSessionId, requester: this.cfg.owner });
230
+ const placement = await this.cfg.enrollment.readPlacement(this.cfg.rootSessionId);
231
+ admitted = await this.cfg.enrollment.admitSession({ rootSessionId: this.cfg.rootSessionId, placement, requester: this.cfg.owner });
231
232
  }
232
233
  catch (err) {
233
234
  return {
@@ -302,7 +303,7 @@ export class RemoteDeviceExecutionEnv {
302
303
  return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
303
304
  const failure = await this.ensureConnected();
304
305
  if (failure)
305
- return { ok: false, error: new ExecutionError(failure.exec, failure.message) };
306
+ return { ok: false, error: createExecFailure(failure.exec, failure.message) };
306
307
  return undefined;
307
308
  }
308
309
  async fsGate(p) {
@@ -381,15 +382,14 @@ export class RemoteDeviceExecutionEnv {
381
382
  case "env_terminal":
382
383
  return { ok: false, error: new ExecutionError("shell_unavailable", ENV_TERMINAL_MESSAGE) };
383
384
  case "rejected":
384
- return { ok: false, error: new ExecutionError(DEVICE_REJECT_TO_EXEC_ERROR[res.reject.code], rejectText(res.reject)) };
385
+ return { ok: false, error: createExecFailure(DEVICE_REJECT_TO_EXEC_ERROR[res.reject.code], rejectText(res.reject)) };
385
386
  case "never_started":
386
387
  case "settled": {
387
388
  const o = res.outcome;
388
- if (!o.ok) {
389
- return { ok: false, error: new ExecutionError(DEVICE_OUTCOME_TO_EXEC_ERROR[o.errorCode], o.message, undefined, { stdout, stderr }) };
390
- }
389
+ if (!o.ok)
390
+ return { ok: false, error: createExecFailure(DEVICE_OUTCOME_TO_EXEC_ERROR[o.errorCode], o.message, res.kind === "settled" ? { stdout, stderr } : undefined) };
391
391
  if (o.kind === "cancelled") {
392
- return { ok: false, error: new ExecutionError("aborted", `the device reported the command as ${o.state} after a cancel`, undefined, { stdout, stderr }) };
392
+ return { ok: false, error: createCutError({ code: "aborted", message: `the device reported the command as ${o.state} after a cancel` }, { stdout, stderr }) };
393
393
  }
394
394
  if (o.kind !== "exec")
395
395
  return { ok: false, error: new ExecutionError("unknown", `device returned a "${o.kind}" outcome for an exec — protocol violation`) };
@@ -799,17 +799,18 @@ export function deviceExecutionEnvFactory(deps) {
799
799
  throw deviceFactoryRefusal(DEVICE_FACTORY_CONFIG_CODES.PLACEMENT_ROOT_INVALID, `device lane: ExecutionEnvFactoryContext.placementRootSessionId is empty [${DEVICE_FACTORY_CONFIG_CODES.PLACEMENT_ROOT_INVALID}] — ` +
800
800
  "the device placement is keyed on the run tree's placement root; defaulting to the child sessionId would read as a NEW placement (v2 §4.3.2 / core design/380 O1①)");
801
801
  }
802
- const binding = await deps.store.getSessionBinding(rootSessionId);
802
+ const placement = await deps.seat.read(rootSessionId);
803
+ const binding = placement === undefined ? undefined : boundDeviceOf(placement.cell);
803
804
  if (!binding) {
804
805
  throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_BOUND, `device lane: no device binding for placement root ${rootSessionId} [${DEVICE_ERROR_CODES.NOT_BOUND}] — ` +
805
806
  "the submit-time admission chain must bind the root session to a device before a run reaches the factory (v2 §4.3.2)");
806
807
  }
807
- const recordedDevice = ctx.resumeWorkspace?.deviceId;
808
- if (recordedDevice !== undefined && recordedDevice !== binding.deviceId) {
809
- throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this resume rebuilds the workspace on device ${recordedDevice}, but placement root ${rootSessionId} is now bound to ${binding.deviceId} [${DEVICE_ERROR_CODES.IDENTITY_MISMATCH}] — ` +
808
+ const honor = ctx.resumeWorkspace?.deviceId;
809
+ if (honor !== undefined && honor !== binding.id) {
810
+ throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this resume rebuilds the workspace on device ${honor}, but placement root ${rootSessionId} is now bound to ${binding.id} [${DEVICE_ERROR_CODES.IDENTITY_MISMATCH}] — ` +
810
811
  "a parked call does not follow a rebind (the new device has no copy of that workspace); finish or cancel the parked task, then start it again on the device now bound");
811
812
  }
812
- const view = await deps.store.readDeviceForAdmission(binding.deviceId, binding.owner);
813
+ const view = await deps.store.readDeviceForAdmission(binding.id, binding.owner);
813
814
  if (!view) {
814
815
  throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_FOUND, `device lane: the bound device is gone or not owned by the binding's principal [${DEVICE_ERROR_CODES.NOT_FOUND}]`);
815
816
  }
@@ -821,13 +822,13 @@ export function deviceExecutionEnvFactory(deps) {
821
822
  hub: deps.hub,
822
823
  enrollment: deps.enrollment,
823
824
  rootSessionId,
824
- deviceId: binding.deviceId,
825
+ deviceId: binding.id,
825
826
  owner: binding.owner,
826
827
  cwd: effectiveDeviceCwd(deps.perSessionCwd?.get(ctx.sessionId), ctx.parentCwd, view.row),
827
828
  ...(deps.homeDir === undefined ? {} : { homeDir: deps.homeDir }),
828
829
  ...(deps.perSessionShellEnv?.get(ctx.sessionId) === undefined ? {} : { shellEnv: deps.perSessionShellEnv.get(ctx.sessionId) }),
829
830
  execTimeoutMs,
830
- supports: deps.hub.declaredSupports(binding.deviceId),
831
+ supports: deps.hub.declaredSupports(binding.id),
831
832
  ...(deps.logger === undefined ? {} : { logger: deps.logger }),
832
833
  });
833
834
  };
@@ -135,10 +135,17 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
135
135
  constructor(config: RemoteContainerEnvConfig, deps?: RemoteContainerEnvDeps);
136
136
  /** [ref] §9: E2B is a FULL RemoteExecutionEnv — an isolated, snapshot-suspendable microVM. (SSH/ADB
137
137
  * adapters declare {isolation:false, suspendable:false} — real machines/devices, not snapshotable.) */
138
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在云沙箱里,不在本进程所在的进程表。 */
139
+ /** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
138
140
  readonly capabilities: {
139
- isolation: boolean;
140
- suspendable: boolean;
141
- canonicalPathAuthoritative: boolean;
141
+ isolation: true;
142
+ suspendable: true;
143
+ canonicalPathAuthoritative: true;
144
+ sharesProcessTable: false;
145
+ hostLocalPaths: false;
146
+ externalContentTarget: false;
147
+ io: true;
148
+ keepsReadFloor: false;
142
149
  };
143
150
  workspaceHandle(): WorkspaceHandle;
144
151
  /**
@@ -252,7 +259,14 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
252
259
  abortSignal?: AbortSignal;
253
260
  }): Promise<Result<string[], FileError>>;
254
261
  readBinaryFile(p: string, abortSignal?: AbortSignal): Promise<Result<Uint8Array, FileError>>;
262
+ /** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts` `writeFileStaged` / `appendFileStaged`;本腿是原生 SDK 腿,与四条
263
+ * shell 腿共用的是这只编排,不是 `posix-shell-fs` 基座;父目录与私有暂存目录由编排的准备那一步建):上传 = `files.write` 整份写到私有目录里的
264
+ * 暂存件;提交 = 一次 `exec`。两者同一身份(`files.write` 与 `commands.run` 都不带 `user`,沙箱侧取同一个缺省用户)。 */
265
+ private stagedLane;
266
+ /** 整旧或整新(暂存 → 一次 `mv -f`)。修前 `files.write` 直落目标:上传中途断 / 取消时目标剩什么取决于沙箱侧的落地实现,本适配器不依赖它。 */
255
267
  writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
268
+ /** 追加(暂存要加的字节 → 一次 `cat >>`;inode 不换、已有字节不重写)。修前 = 读旧 + 拼 + 整份 `files.write`(O(文件);中途断 ⇒
269
+ * 半份)。目录不收新名字 / 私有目录收不成私有时的退路 = 旧形(`rewriteAppend`:读旧 + 拼 + 整份 `files.write` 到目标;只有写那一步经 `ctx.inPlace`)。 */
256
270
  appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
257
271
  fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
258
272
  listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
@@ -338,6 +352,8 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
338
352
  private tempResult;
339
353
  private toFileInfo;
340
354
  private fileError;
355
+ /** A thrown provider error → this exec's error. The two cuts (aborted, the provider's own command deadline) are a command that RAN: they carry what it had
356
+ * written (`captured`, `createCutError`); an unclassified fault does not (its outcome is unknown, the engine reads no partial output from it). */
341
357
  private execError;
342
358
  private remoteError;
343
359
  }
@@ -2,8 +2,9 @@ import path from "node:path";
2
2
  import { Sandbox, CommandExitError, FileType } from "e2b";
3
3
  import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
4
4
  import { fileErrorFromExec } from "./remote-env-file-error.js";
5
- import { numEnvOr, runSymlinkChain, shellQuote } from "./remote-shell.js";
5
+ import { appendFileStaged, rewriteAppend, numEnvOr, runSymlinkChain, shellQuote, writeFileStaged } from "./remote-shell.js";
6
6
  import { buildLivenessCut, commandBoundsOf, firstBoundOf, laneLivenessOf, LIVENESS_MS_DEFAULT } from "../exec-bounds.js";
7
+ import { createCutError } from "../exec-cut.js";
7
8
  import { BackgroundShellManager } from "./background-shell-support.js";
8
9
  const PROVIDER = "e2b";
9
10
  const DEFAULT_MOUNT_PATH = "/home/user";
@@ -46,7 +47,7 @@ export class RemoteContainerExecutionEnv {
46
47
  this.homeDir = config.homeDir;
47
48
  this.sandboxApi = deps.sandboxApi ?? Sandbox;
48
49
  }
49
- capabilities = { isolation: true, suspendable: true, canonicalPathAuthoritative: true };
50
+ capabilities = { isolation: true, suspendable: true, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
50
51
  workspaceHandle() {
51
52
  if (!this.handle) {
52
53
  throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
@@ -251,6 +252,8 @@ export class RemoteContainerExecutionEnv {
251
252
  stderr: { tail: new RollingTailBuffer(), total: 0, cursor: 0 },
252
253
  }
253
254
  : undefined;
255
+ let started;
256
+ const captured = () => ({ stdout: started?.stdout ?? "", stderr: started?.stderr ?? "" });
254
257
  let detached = false;
255
258
  const teePush = (s, d) => {
256
259
  const b = Buffer.from(d, "utf8");
@@ -299,10 +302,10 @@ export class RemoteContainerExecutionEnv {
299
302
  this.settleAbandonedStart(starting);
300
303
  const cut = e instanceof RemoteExecutionError && e.code === "timeout" && !options?.abortSignal?.aborted;
301
304
  if (!cut)
302
- return { ok: false, error: this.execError(e, options?.abortSignal) };
303
- const bc = boundCut(first);
304
- return { ok: false, error: new ExecutionError(bc.code, bc.message, undefined, undefined, bc.noOutputSec) };
305
+ return { ok: false, error: this.execError(e, options?.abortSignal, captured()) };
306
+ return { ok: false, error: createCutError(boundCut(first), captured()) };
305
307
  }
308
+ started = handle;
306
309
  const onAbort = () => { if (!detached)
307
310
  void handle.kill().catch(() => { }); };
308
311
  if (options?.abortSignal) {
@@ -352,6 +355,7 @@ export class RemoteContainerExecutionEnv {
352
355
  disarmed: () => detached,
353
356
  ...(tee ? { wall: { totalMs: this.commandTimeoutMs(options?.timeout), leftMs: Math.max(0, this.commandTimeoutMs(options?.timeout) - (Date.now() - startedAt)) } } : {}),
354
357
  liveness: bounds.liveness,
358
+ captured,
355
359
  });
356
360
  const winner = await (detachedP ? Promise.race([fg, detachedP]) : fg);
357
361
  if (winner === "detached") {
@@ -365,18 +369,18 @@ export class RemoteContainerExecutionEnv {
365
369
  });
366
370
  }
367
371
  if (options?.abortSignal?.aborted && !detached) {
368
- return { ok: false, error: new ExecutionError("aborted", "exec aborted (remote process killed)") };
372
+ return { ok: false, error: createCutError({ code: "aborted", message: "exec aborted (remote process killed)" }, captured()) };
369
373
  }
370
374
  return ok({ stdout: winner.stdout, stderr: winner.stderr, exitCode: winner.exitCode });
371
375
  }
372
376
  catch (e) {
373
377
  if (options?.abortSignal?.aborted && !detached) {
374
- return { ok: false, error: this.execError(e, options.abortSignal) };
378
+ return { ok: false, error: this.execError(e, options.abortSignal, captured()) };
375
379
  }
376
380
  if (e instanceof CommandExitError) {
377
381
  return ok({ stdout: e.stdout, stderr: e.stderr, exitCode: e.exitCode });
378
382
  }
379
- return { ok: false, error: this.execError(e, options?.abortSignal) };
383
+ return { ok: false, error: this.execError(e, options?.abortSignal, captured()) };
380
384
  }
381
385
  finally {
382
386
  fgSettled = true;
@@ -659,37 +663,33 @@ export class RemoteContainerExecutionEnv {
659
663
  return { ok: false, error: this.fileError(e, this.resolve(p), abortSignal) };
660
664
  }
661
665
  }
666
+ stagedLane(sbx, content) {
667
+ const data = typeof content === "string" ? content : toArrayBuffer(content);
668
+ return {
669
+ upload: async (dest, signal) => {
670
+ try {
671
+ await this.rpc("files.write", (s) => sbx.files.write(dest, data, { signal: s }), signal, this.cfg.dataTimeoutMs);
672
+ return ok(undefined);
673
+ }
674
+ catch (e) {
675
+ return { ok: false, error: this.fileError(e, dest, signal) };
676
+ }
677
+ },
678
+ run: (cmd, signal) => this.exec(cmd, { cwd: "/", ...(signal !== undefined ? { abortSignal: signal } : {}) }),
679
+ };
680
+ }
662
681
  async writeFile(p, content, abortSignal) {
663
682
  const sbx = await this.fsReady(p);
664
683
  if (!sbx.ok)
665
684
  return sbx;
666
- try {
667
- const data = typeof content === "string" ? content : toArrayBuffer(content);
668
- await this.rpc("files.write", (signal) => sbx.value.files.write(this.resolve(p), data, { signal }), abortSignal, this.cfg.dataTimeoutMs);
669
- return ok(undefined);
670
- }
671
- catch (e) {
672
- return { ok: false, error: this.fileError(e, this.resolve(p), abortSignal) };
673
- }
685
+ return writeFileStaged(this.stagedLane(sbx.value, content), this.resolve(p), abortSignal);
674
686
  }
675
687
  async appendFile(p, content, abortSignal) {
688
+ const sbx = await this.fsReady(p);
689
+ if (!sbx.ok)
690
+ return sbx;
676
691
  const abs = this.resolve(p);
677
- const existing = await this.readBinaryFile(abs, abortSignal);
678
- let base;
679
- if (existing.ok) {
680
- base = existing.value;
681
- }
682
- else if (existing.error.code === "not_found") {
683
- base = new Uint8Array(0);
684
- }
685
- else {
686
- return existing;
687
- }
688
- const add = typeof content === "string" ? new TextEncoder().encode(content) : content;
689
- const merged = new Uint8Array(base.length + add.length);
690
- merged.set(base, 0);
691
- merged.set(add, base.length);
692
- return this.writeFile(abs, merged, abortSignal);
692
+ return appendFileStaged(this.stagedLane(sbx.value, content), abs, abortSignal, (ctx) => rewriteAppend((signal) => this.readBinaryFile(abs, signal), (bytes, signal) => this.stagedLane(sbx.value, bytes).upload(abs, signal), content, ctx));
693
693
  }
694
694
  async fileInfo(p, abortSignal) {
695
695
  const sbx = await this.fsReady(p);
@@ -851,16 +851,15 @@ export class RemoteContainerExecutionEnv {
851
851
  async waitWithLiveness(handle, lastProgress, opts) {
852
852
  let timer;
853
853
  let wallTimer;
854
- const liveness = opts?.liveness;
854
+ const liveness = opts.liveness;
855
855
  const idle = new Promise((_, reject) => {
856
856
  const arm = (bound) => {
857
857
  timer = setTimeout(() => {
858
- if (opts?.disarmed?.())
858
+ if (opts.disarmed?.())
859
859
  return;
860
860
  const idleFor = Date.now() - lastProgress();
861
861
  if (idleFor >= bound.ms) {
862
- const cut = buildLivenessCut(bound);
863
- reject(new ExecutionError(cut.code, cut.message, undefined, undefined, cut.noOutputSec));
862
+ reject(createCutError(buildLivenessCut(bound), opts.captured()));
864
863
  void handle.kill().catch(() => { });
865
864
  this.cfg.metrics?.inc("remote_env_exec_idle_timeout_total", { provider: PROVIDER });
866
865
  }
@@ -871,18 +870,18 @@ export class RemoteContainerExecutionEnv {
871
870
  };
872
871
  if (liveness !== undefined)
873
872
  arm(liveness);
874
- if (opts?.wall !== undefined) {
873
+ if (opts.wall !== undefined) {
875
874
  const wall = opts.wall;
876
875
  wallTimer = setTimeout(() => {
877
876
  if (opts.disarmed?.())
878
877
  return;
879
- reject(new ExecutionError("timeout", wallCutMessage(wall.totalMs)));
878
+ reject(createCutError({ code: "timeout", message: wallCutMessage(wall.totalMs) }, opts.captured()));
880
879
  void handle.kill().catch(() => { });
881
880
  }, wall.leftMs);
882
881
  }
883
882
  });
884
883
  try {
885
- return await Promise.race([opts?.waitP ?? handle.wait(), idle]);
884
+ return await Promise.race([opts.waitP ?? handle.wait(), idle]);
886
885
  }
887
886
  finally {
888
887
  if (timer)
@@ -953,15 +952,15 @@ export class RemoteContainerExecutionEnv {
953
952
  return new FileError("is_directory", msg, p);
954
953
  return new FileError("unknown", msg, p);
955
954
  }
956
- execError(e, abortSignal) {
955
+ execError(e, abortSignal, captured) {
957
956
  if (e instanceof ExecutionError)
958
957
  return e;
959
958
  const msg = errMsg(e);
960
959
  const name = e?.name;
961
960
  if (abortSignal?.aborted || name === "AbortError")
962
- return new ExecutionError("aborted", msg);
961
+ return createCutError({ code: "aborted", message: msg }, captured);
963
962
  if (providerTimedOut(e))
964
- return new ExecutionError("timeout", msg);
963
+ return createCutError({ code: "timeout", message: msg }, captured);
965
964
  return new ExecutionError("unknown", msg, e instanceof Error ? e : undefined);
966
965
  }
967
966
  remoteError(e, code = "unknown") {
@@ -18,6 +18,8 @@
18
18
  import { FileError, type ExecutionError, type RemoteExecutionError } from "@sema-agent/core";
19
19
  /** exec 层的错误码 → 文件层的码。只映射**有对应语义**的那些,其余落 `unknown`(诚实降级)。 */
20
20
  export declare function fileErrorFromExec(e: ExecutionError | RemoteExecutionError, path?: string): FileError;
21
+ /** POSIX 的一个名字段上限(NAME_MAX,字节;Linux / macOS / 常见文件系统同值)。也是远端写的私有暂存目录名的上限(`remote-shell.ts` `buildStagingPaths`)。 */
22
+ export declare const NAME_MAX_BYTES = 255;
21
23
  /**
22
24
  * core 7.56.0 [ref]:`ENAMETOOLONG` 的**一只读法**(本文件的 stderr 分类器与 host 车道的 errno 映射同调)—— 路径有一段超过 NAME_MAX(按 UTF-8 字节量)⇒
23
25
  * `name_too_long`:没有目录装得下这个名字,引擎读站把它读作缺席(与 `not_found` 同读);段都短、只是整条超 PATH_MAX ⇒ `unknown`(那种文件可能存在,从下层目录够得着)。
@@ -3,7 +3,7 @@ export function fileErrorFromExec(e, path) {
3
3
  const code = e.code === "aborted" ? "aborted" : "unknown";
4
4
  return new FileError(code, e.message, path, e);
5
5
  }
6
- const NAME_MAX_BYTES = 255;
6
+ export const NAME_MAX_BYTES = 255;
7
7
  export function nameTooLongCode(p) {
8
8
  return p !== undefined && p.split(/[\\/]/).some((part) => Buffer.byteLength(part, "utf8") > NAME_MAX_BYTES) ? "name_too_long" : "unknown";
9
9
  }
@@ -71,14 +71,21 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
71
71
  * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
72
72
  * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
73
73
  * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
74
- * 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
74
+ * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
75
75
  * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
76
76
  * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
77
77
  */
78
+ /** `sharesProcessTable`(core 7.57.0,必填):命令是本进程 spawn 的子进程(同一张进程表),引擎的模式杀守卫对它照 NodeExecutionEnv 一样问 `pgrep`。 */
79
+ /** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
78
80
  readonly capabilities: {
79
- isolation: boolean;
80
- suspendable: boolean;
81
- canonicalPathAuthoritative: boolean;
81
+ isolation: false;
82
+ suspendable: false;
83
+ canonicalPathAuthoritative: true;
84
+ sharesProcessTable: true;
85
+ hostLocalPaths: true;
86
+ externalContentTarget: false;
87
+ io: true;
88
+ keepsReadFloor: false;
82
89
  };
83
90
  /** R7 自唤醒: structural delegation to the injected scheduler backend. `hasScheduler(env)` checks BOTH the method
84
91
  * presence (always, since we implement the interface) AND `schedulerCapabilities.supported===true` — which is only
@@ -297,8 +304,9 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
297
304
  maxLines?: number;
298
305
  abortSignal?: AbortSignal;
299
306
  }): Promise<Result<string[], FileError>>;
300
- /** core `NodeExecutionEnv` 的绑定读器 —— 首次 {@link openBound} 时建(构造无副作用,只记 cwd)。 */
301
- private boundReader;
307
+ /** core `NodeExecutionEnv` 的一只实例 —— 本车道把**落盘那一步**委托给它的两只面({@link openBound} 的持有读、{@link writeFile} 的整文件替换);
308
+ * 首次用时建(构造无副作用,只记 cwd)。门(`fsReady`)、在飞登记(`withInflightFs`)与工作区相对解析留在本适配器。 */
309
+ private coreFs;
302
310
  /**
303
311
  * core 7.50.0 `FileSystem.openBound`:开着并**持有**一只对象(叶链接不跟,开后身份对拍),调用方判的与读的是同一只对象 ——
304
312
  * LSP 会话的读口与路径附件只经它读,不实现 ⇒ 两者在本车道一律拒。
@@ -308,8 +316,15 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
308
316
  */
309
317
  openBound(p: string, abortSignal?: AbortSignal): Promise<Result<BoundFileObject, FileError>>;
310
318
  readBinaryFile(p: string, abortSignal?: AbortSignal): Promise<Result<Uint8Array, FileError>>;
311
- /** [ref]:整只方法体(**含** `fsReady` 那道门)走 `withInflightFs` —— 登记必须与调用方同一同步段,
312
- * 否则 destroy 的快照可能刚好在 `await this.fsReady(...)` 的让出点上取完。见那处头注。 */
319
+ /**
320
+ * 整文件替换:**整旧或整新;`aborted` ⇒ 目标未碰**。
321
+ * 🔴 **不重写**:落盘那一步委托 core `NodeExecutionEnv.writeFile`(同目录暂存 + fsync + rename;父目录自建;单跳链接改写它指的
322
+ * 常规文件、链接链 / 非常规文件 / rename 不可用的文件系统回落就地写;已有文件的权限位带过去)—— 该原语的唯一属主在 core。
323
+ * 本适配器只给它与其余写面同一的入口纪律:[ref] 在飞登记(整只方法体**含** `fsReady` 那道门走 `withInflightFs` —— 登记必须与
324
+ * 调用方同一同步段,否则 destroy 的快照可能刚好在 `await this.fsReady(...)` 的让出点上取完;见那处头注)、销毁后拒、工作区相对解析。
325
+ * 修前这里是裸 `fs.writeFile(abs, data, { signal })`:open 即 O_TRUNC,中途取消 / 出错 ⇒ 目标 = 新内容的前缀,原件不可恢复。
326
+ * 已知语义变化(与 core / CC 同形):rename 断开硬链接 —— 另一条链接留旧内容。
327
+ */
313
328
  writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
314
329
  /** [ref](写面一律 `withInflightFs`,见那处头注)。本面是唯一能在 `destroyed` **之后**过门的写面
315
330
  * (`lingeringBgWrite`):那一族登记在 destroy 的快照**之后**,所以 destroy 不会去等它们 —— 正确,