@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
@@ -7,6 +7,7 @@ import { openSync, closeSync, readSync, statSync, truncateSync, unlinkSync, mkdi
7
7
  import { StringDecoder } from "node:string_decoder";
8
8
  import { armPipeDestroyGrace, numEnvOr } from "./remote-shell.js";
9
9
  import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
10
+ import { buildCapturedOutput, createCutError } from "../exec-cut.js";
10
11
  import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
11
12
  import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
12
13
  import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
@@ -37,7 +38,7 @@ const UNSUPPORTED_SCHEDULER_CAPS = {
37
38
  maxScheduledHorizonSec: 0,
38
39
  };
39
40
  export class RemoteHostExecutionEnv {
40
- capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
41
+ capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: true, hostLocalPaths: true, externalContentTarget: false, io: true, keepsReadFloor: false };
41
42
  get schedulerCapabilities() {
42
43
  return this.cfg.scheduler?.schedulerCapabilities ?? UNSUPPORTED_SCHEDULER_CAPS;
43
44
  }
@@ -454,11 +455,11 @@ export class RemoteHostExecutionEnv {
454
455
  };
455
456
  const timer = setTimeout(() => {
456
457
  kill();
457
- finish({ ok: false, error: new ExecutionError("timeout", `host exec exceeded ${timeoutMs}ms`) });
458
+ finish({ ok: false, error: createCutError({ code: "timeout", message: `host exec exceeded ${timeoutMs}ms` }, buildCapturedOutput(outBuf, errBuf)) });
458
459
  }, timeoutMs);
459
460
  const onAbort = () => {
460
461
  kill();
461
- finish({ ok: false, error: new ExecutionError("aborted", "host exec aborted") });
462
+ finish({ ok: false, error: createCutError({ code: "aborted", message: "host exec aborted" }, buildCapturedOutput(outBuf, errBuf)) });
462
463
  };
463
464
  if (options?.abortSignal)
464
465
  options.abortSignal.addEventListener("abort", onAbort, { once: true });
@@ -607,32 +608,36 @@ export class RemoteHostExecutionEnv {
607
608
  }
608
609
  catch { }
609
610
  };
610
- const feed = (lane, inc) => {
611
+ const record = (lane, inc) => {
611
612
  lane.buf.push(inc);
612
- if (!settled) {
613
- try {
614
- lane.cb?.(inc.toString("utf8"));
615
- }
616
- catch { }
613
+ };
614
+ const deliver = (lane, inc) => {
615
+ try {
616
+ lane.cb?.(inc.toString("utf8"));
617
617
  }
618
+ catch { }
618
619
  };
619
- const pumpOnce = () => {
620
+ const feed = (lane, inc) => {
621
+ record(lane, inc);
622
+ deliver(lane, inc);
623
+ };
624
+ const pumpOnce = (take) => {
620
625
  for (const lane of lanes) {
621
626
  skipAhead(lane, ROTATE_BYTES);
622
627
  const inc = readIncrement(lane.path, lane.key);
623
628
  if (inc)
624
- feed(lane, inc);
629
+ take(lane, inc);
625
630
  reclaim(lane);
626
631
  }
627
632
  };
628
- const drainFinal = () => {
633
+ const drainFinal = (take) => {
629
634
  for (const lane of lanes) {
630
635
  skipAhead(lane, FINAL_TAIL_BYTES);
631
636
  for (let i = 0; i < 4; i++) {
632
637
  const inc = readIncrement(lane.path, lane.key);
633
638
  if (!inc)
634
639
  break;
635
- feed(lane, inc);
640
+ take(lane, inc);
636
641
  }
637
642
  }
638
643
  };
@@ -661,15 +666,17 @@ export class RemoteHostExecutionEnv {
661
666
  }
662
667
  };
663
668
  let removeDetachListener;
664
- const finish = (r) => {
669
+ const finish = (outcome) => {
665
670
  if (settled)
666
- return;
671
+ return false;
667
672
  settled = true;
673
+ const r = outcome();
668
674
  clearTimeout(timer);
669
675
  options?.abortSignal?.removeEventListener("abort", onAbort);
670
676
  removeDetachListener?.();
671
677
  cleanupSpool();
672
678
  resolve(r);
679
+ return true;
673
680
  };
674
681
  const closeSpoolFds = () => {
675
682
  for (const fd of spool.fds) {
@@ -697,66 +704,56 @@ export class RemoteHostExecutionEnv {
697
704
  ticket = adoptHostForegroundChild(child);
698
705
  closeSpoolFds();
699
706
  child.on("error", (e) => {
700
- finish({ ok: false, error: new ExecutionError("spawn_error", `host spawn failed: ${e.message}`) });
707
+ finish(() => ({ ok: false, error: new ExecutionError("spawn_error", `host spawn failed: ${e.message}`) }));
701
708
  });
702
- const partial = () => {
703
- pumpOnce();
704
- const o = outBuf.result();
705
- const e = errBuf.result();
706
- return { stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes) };
709
+ const captured = () => {
710
+ pumpOnce(record);
711
+ return buildCapturedOutput(outBuf, errBuf);
707
712
  };
708
713
  let exited = false;
709
- const timer = setTimeout(() => {
714
+ const cut = (c) => {
710
715
  if (exited)
711
716
  return;
712
- try {
713
- if (child.pid != null)
714
- killTreeHard(child.pid);
715
- }
716
- catch {
717
+ finish(() => {
717
718
  try {
718
- child.kill("SIGKILL");
719
+ if (child.pid != null)
720
+ killTreeHard(child.pid);
719
721
  }
720
- catch { }
721
- }
722
- const p = partial();
723
- finish({ ok: false, error: new ExecutionError("timeout", `host exec exceeded ${timeoutMs}ms; partial stdout: ${p.stdout.slice(-2048)}`) });
724
- }, timeoutMs);
725
- const onAbort = () => {
726
- if (exited)
727
- return;
728
- try {
729
- if (child.pid != null)
730
- killTreeHard(child.pid);
731
- }
732
- catch {
733
- try {
734
- child.kill("SIGKILL");
722
+ catch {
723
+ try {
724
+ child.kill("SIGKILL");
725
+ }
726
+ catch { }
735
727
  }
736
- catch { }
737
- }
738
- finish({ ok: false, error: new ExecutionError("aborted", "host exec aborted") });
728
+ return { ok: false, error: createCutError(c, captured()) };
729
+ });
739
730
  };
731
+ const timer = setTimeout(() => cut({ code: "timeout", message: `host exec exceeded ${timeoutMs}ms` }), timeoutMs);
732
+ const onAbort = () => cut({ code: "aborted", message: "host exec aborted" });
740
733
  if (options?.abortSignal?.aborted) {
741
734
  onAbort();
742
735
  return;
743
736
  }
744
737
  if (options?.abortSignal)
745
738
  options.abortSignal.addEventListener("abort", onAbort, { once: true });
746
- pump = setInterval(pumpOnce, PUMP_MS);
739
+ pump = setInterval(() => pumpOnce(feed), PUMP_MS);
747
740
  pump.unref?.();
748
741
  const onDetach = () => {
749
742
  if (settled || exited)
750
743
  return;
751
- pumpOnce();
744
+ pumpOnce(feed);
745
+ if (settled)
746
+ return;
752
747
  const fgDropped = spoolRemoved.out + spoolRemoved.err;
753
748
  const shellId = this.adoptRunningExecSpool(child, spool, { out: 0, err: 0 }, fgDropped);
754
749
  if (shellId === undefined)
755
750
  return;
756
751
  handedOver = true;
757
- const o = outBuf.result();
758
- const e = errBuf.result();
759
- finish(ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode: 0, detached: { shellId } }));
752
+ finish(() => {
753
+ const o = outBuf.result();
754
+ const e = errBuf.result();
755
+ return ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode: 0, detached: { shellId } });
756
+ });
760
757
  };
761
758
  if (detachSignal) {
762
759
  if (detachSignal.aborted)
@@ -767,13 +764,20 @@ export class RemoteHostExecutionEnv {
767
764
  }
768
765
  }
769
766
  const settleFromTermination = (code, signal) => {
770
- if (settled)
771
- return;
772
- drainFinal();
773
- const o = outBuf.result();
774
- const e = errBuf.result();
775
767
  const exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 0) : 1);
776
- finish(ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode }));
768
+ const drained = [];
769
+ const won = finish(() => {
770
+ drainFinal((lane, inc) => {
771
+ record(lane, inc);
772
+ drained.push({ lane, inc });
773
+ });
774
+ const o = outBuf.result();
775
+ const e = errBuf.result();
776
+ return ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode });
777
+ });
778
+ if (won)
779
+ for (const d of drained)
780
+ deliver(d.lane, d.inc);
777
781
  };
778
782
  child.on("close", (code, signal) => {
779
783
  if (drainTimer) {
@@ -972,13 +976,13 @@ export class RemoteHostExecutionEnv {
972
976
  lines = lines.slice(0, options.maxLines);
973
977
  return ok(lines);
974
978
  }
975
- boundReader;
979
+ coreFs;
976
980
  async openBound(p, abortSignal) {
977
981
  const ready = await this.fsReady(p);
978
982
  if (!ready.ok)
979
983
  return ready;
980
- this.boundReader ??= new NodeExecutionEnv({ cwd: this.workspaceDir });
981
- return this.boundReader.openBound(this.resolve(p), abortSignal);
984
+ this.coreFs ??= new NodeExecutionEnv({ cwd: this.workspaceDir });
985
+ return this.coreFs.openBound(this.resolve(p), abortSignal);
982
986
  }
983
987
  async readBinaryFile(p, abortSignal) {
984
988
  const ready = await this.fsReady(p);
@@ -998,16 +1002,8 @@ export class RemoteHostExecutionEnv {
998
1002
  const ready = await this.fsReady(p);
999
1003
  if (!ready.ok)
1000
1004
  return ready;
1001
- const abs = this.resolve(p);
1002
- try {
1003
- await fs.mkdir(path.dirname(abs), { recursive: true });
1004
- const data = typeof content === "string" ? content : Buffer.from(content);
1005
- await fs.writeFile(abs, data, { signal: abortSignal });
1006
- return ok(undefined);
1007
- }
1008
- catch (e) {
1009
- return { ok: false, error: this.fileError(e, abs, abortSignal) };
1010
- }
1005
+ this.coreFs ??= new NodeExecutionEnv({ cwd: this.workspaceDir });
1006
+ return this.coreFs.writeFile(this.resolve(p), content, abortSignal);
1011
1007
  });
1012
1008
  }
1013
1009
  appendFile(p, content, abortSignal) {
@@ -2,7 +2,7 @@ import { type SpaceReclaimer, type WorkspaceSpacePolicy, type CacheReclaimReason
2
2
  import { type SpaceCommitFence } from "../workspace-space-commit.js";
3
3
  import { type CacheMissMemory, type WorkspaceCacheCapability } from "../workspace-cache.js";
4
4
  import { type SandboxEgressGap, type SandboxIpv6 } from "../sandbox-egress-policy.js";
5
- 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 BackgroundShellCapability, type BackgroundShellId, type BackgroundShellError, type BackgroundSpawnOptions, type BackgroundPoll } from "@sema-agent/core";
5
+ 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 BackgroundShellCapability, type BackgroundShellId, type BackgroundShellError, type BackgroundSpawnOptions, type BackgroundPoll, type ExecutionEnvCapabilities } from "@sema-agent/core";
6
6
  import type { PodSpecPatch } from "./k8s-exec-protocol.js";
7
7
  import { type LaneHomeConfig } from "../lane-home-probe.js";
8
8
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
@@ -16,7 +16,9 @@ export interface K8sEnvConfig extends LaneHomeConfig {
16
16
  caCert?: string;
17
17
  /** Skip TLS verification (lab only — never production). */
18
18
  insecureTls?: boolean;
19
- namespace?: string;
19
+ /** The namespace the sandbox pods live in (env `K8S_NAMESPACE`). REQUIRED, no default: the deployment declares it, and every reader
20
+ * (the adapter's API paths, the reaper's pod face, the boot's egress reading) reads this one field. */
21
+ namespace: string;
20
22
  /** Sandbox pod image (a plain docker image present in the cluster). REQUIRED. */
21
23
  image: string;
22
24
  /** RuntimeClass for the VM boundary. Default "kata-qemu" (set "" to run plain runc — tests/dev only). */
@@ -258,8 +260,6 @@ export { execSocketTlsOptions } from "./k8s-exec-protocol.js";
258
260
  * String() is "[object ErrorEvent]" — that masked the real TLS failure for a whole drill. */
259
261
  export declare function errorMessageOf(e: unknown): string;
260
262
  export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
261
- /** The sandbox namespace when `K8S_NAMESPACE` is unset (the one spelling: the adapter and the boot's egress line read it). */
262
- export declare const K8S_SANDBOX_NAMESPACE_DEFAULT = "sandboxes";
263
263
  /**
264
264
  * The boot reading of the sandbox Secret seat, a closed set: `granted` — every verb allowed · `denied` — the API answered "not allowed" for these
265
265
  * verbs (a definite answer; it wins over a verb that could not be asked) · `unverified` — some verb could not be asked (the request failed, the
@@ -296,15 +296,13 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
296
296
  * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
297
297
  * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
298
298
  * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
299
- * 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
299
+ * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
300
300
  * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
301
301
  * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
302
302
  */
303
- readonly capabilities: {
304
- isolation: boolean;
305
- suspendable: boolean;
306
- canonicalPathAuthoritative: true;
307
- };
303
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在沙箱 pod 自己的 PID 命名空间里(不开 hostPID),不在本进程所在的进程表。 */
304
+ /** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
305
+ readonly capabilities: ExecutionEnvCapabilities;
308
306
  cwd: string;
309
307
  private podName?;
310
308
  /** Cluster-internal pod address, captured when the pod reaches Running — the LSP `getHost` surface.
@@ -811,19 +809,18 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
811
809
  maxLines?: number;
812
810
  abortSignal?: AbortSignal;
813
811
  }): Promise<Result<string[], FileError>>;
814
- /** 父目录自建(与 host/local-docker/adb/ssh 同口径)。抽成共用是因为 `appendFile` 曾**跳过**它:
815
- * 🔴 2026-07-25 实测的跨腿分叉 —— `appendFile` 直接调 `writeChunked`,整条路上没有 `mkdir -p`,于是
816
- * 「直接 append 建一个新日志路径」在 k8s 腿上返 `not_found`,而其余 5 腿都成功(host/local-docker/adb 实测,
817
- * ssh/e2b 由方法体判据)。core 的后台输出镜像正是"每拍 append 一次"的形,这条分叉会让它在 k8s 腿上从第一拍
818
- * 就失败,而 `mirrorFailed` 只会表现成一句「output file is INCOMPLETE」。 */
819
- private ensureParentDir;
820
812
  writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
813
+ /** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts`;父目录与私有暂存目录由编排的准备那一步建):上传 = 分块整份写进私有目录里的暂存件;
814
+ * 提交 = 文件面探针 exec。两者同一身份(同一只容器里的 exec)。 */
815
+ private stagedLane;
821
816
  appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
822
817
  /**
823
818
  * Write `content` to `abs` via argv-embedded base64, chunked at WRITE_CHUNK_BYTES (no stdin on the exec
824
819
  * channel we use; v4 has no per-channel close). `truncateFirst` → the FIRST chunk uses `>` (overwrite),
825
820
  * the rest `>>` (append); `false` → all chunks `>>`. Empty content still runs exactly one command so an
826
821
  * empty file is created (write) / left intact (append). Shared by writeFile/appendFile (council DESIGN#2).
822
+ * Both file faces point this at a STAGING name (`writeFileStaged` / `appendFileStaged`), so a cut mid-way never reaches
823
+ * the target; the direct uses are the fallbacks of both faces (writeFile passes truncateFirst, appendFile does not) when the target's directory refuses a new name or the private staging directory could not be made private.
827
824
  */
828
825
  private writeChunked;
829
826
  private posixFsInst?;
@@ -4,7 +4,7 @@ import https from "node:https";
4
4
  import http from "node:http";
5
5
  import { StringDecoder } from "node:string_decoder";
6
6
  import WebSocket from "ws";
7
- import { buildCwdEnvCommand, createCwdGuardedCall, shellQuote, kindFromMode, numEnvOr } from "./remote-shell.js";
7
+ import { appendFileStaged, buildCwdEnvCommand, createCwdGuardedCall, shellQuote, kindFromMode, numEnvOr, writeFileStaged } from "./remote-shell.js";
8
8
  import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
9
9
  import { WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "../object-store-buckets.js";
10
10
  import { SNAPSHOT_BUCKET_MISSING, isNoSuchBucketAnswer } from "./s3-answer.js";
@@ -15,6 +15,7 @@ import { WORKSPACE_CACHE_SPACE_LABEL, workspaceCacheClaimOf, workspaceCacheEnv }
15
15
  import { recordFailOpen } from "../observability/fail-open.js";
16
16
  import { PodDeleteRefused, reportPodDeleteFailed } from "./k8s-pod-delete-failure.js";
17
17
  import { buildLivenessCut, commandBoundsOf, laneLivenessOf, LIVENESS_MS_DEFAULT } from "../exec-bounds.js";
18
+ import { buildCapturedOutput, createCutError } from "../exec-cut.js";
18
19
  import { SANDBOX_IPV6_DEFAULT, sandboxEgressGaps, sandboxPodSysctls } from "../sandbox-egress-policy.js";
19
20
  import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
20
21
  import { fileErrorFromExec, classifyFsStderr } from "./remote-env-file-error.js";
@@ -171,7 +172,6 @@ export function errorMessageOf(e) {
171
172
  return typeof m === "string" && m ? m : String(e);
172
173
  }
173
174
  export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
174
- export const K8S_SANDBOX_NAMESPACE_DEFAULT = "sandboxes";
175
175
  const SANDBOX_SECRET_NAME_PREFIX = "sbx-env-";
176
176
  const SANDBOX_SECRET_LABEL = "sema.io/sandbox-secret";
177
177
  const secretEnvKey = (name) => `env.${name}`;
@@ -235,7 +235,7 @@ export class RemoteK8sExecutionEnv {
235
235
  token: config.token,
236
236
  caCert: config.caCert,
237
237
  insecureTls: config.insecureTls ?? false,
238
- namespace: config.namespace ?? K8S_SANDBOX_NAMESPACE_DEFAULT,
238
+ namespace: config.namespace,
239
239
  image: config.image,
240
240
  runtimeClass: config.runtimeClass ?? "kata-qemu",
241
241
  mountPath: config.mountPath ?? K8S_WORKSPACE_MOUNT_DEFAULT,
@@ -268,7 +268,7 @@ export class RemoteK8sExecutionEnv {
268
268
  this.logger = config.logger;
269
269
  this.onWorkspaceSnapshot = config.onWorkspaceSnapshot;
270
270
  this.cacheMisses = config.cacheMisses;
271
- this.capabilities = { isolation: true, suspendable: !!this.cfg.s3Snapshot, canonicalPathAuthoritative: true };
271
+ this.capabilities = { isolation: true, suspendable: !!this.cfg.s3Snapshot, canonicalPathAuthoritative: true, sharesProcessTable: false, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false };
272
272
  const bgMax = Math.max(1, Math.min(K8S_BG_MAX_TIMEOUT_SEC, Math.floor(this.cfg.timeoutMs / 1000)));
273
273
  this.backgroundCapabilities = {
274
274
  supported: true,
@@ -1498,10 +1498,11 @@ export class RemoteK8sExecutionEnv {
1498
1498
  let sinkClosed = false;
1499
1499
  let removeDetachListener;
1500
1500
  const detachTee = opts?.detach?.tee;
1501
- const finish = (r) => {
1501
+ const finish = (outcome) => {
1502
1502
  if (settled)
1503
- return;
1503
+ return false;
1504
1504
  settled = true;
1505
+ const r = outcome();
1505
1506
  clearTimeout(wallTimer);
1506
1507
  clearTimeout(livenessTimer);
1507
1508
  opts?.signal?.removeEventListener("abort", onAbort);
@@ -1512,11 +1513,37 @@ export class RemoteK8sExecutionEnv {
1512
1513
  catch {
1513
1514
  }
1514
1515
  resolvePromise(r);
1516
+ return true;
1515
1517
  };
1516
1518
  const bounds = commandBoundsOf({ timeoutMs: opts?.timeoutMs, readTimeoutMs: opts?.readTimeoutMs }, this.laneLiveness);
1517
1519
  const capMs = opts?.capMs;
1518
1520
  const wallMs = bounds.wallMs === undefined ? capMs : capMs === undefined ? bounds.wallMs : Math.min(bounds.wallMs, capMs);
1519
- const wallTimer = wallMs !== undefined ? setTimeout(() => finish({ ok: false, error: new ExecutionError("timeout", `command exceeded ${wallMs}ms`) }), wallMs) : undefined;
1521
+ const recordStderr = (bytes) => {
1522
+ if (bytes.length === 0)
1523
+ return;
1524
+ errBuf.push(bytes);
1525
+ if (detachTee)
1526
+ feedMemStream(detachTee.err, bytes, K8S_BG_MEM_CAP);
1527
+ };
1528
+ const emitStderr = (bytes) => {
1529
+ if (bytes.length > 0 && errDecoder)
1530
+ opts.onStderr(errDecoder.write(bytes));
1531
+ };
1532
+ const takeStderr = (bytes) => {
1533
+ recordStderr(bytes);
1534
+ emitStderr(bytes);
1535
+ };
1536
+ const heldStderr = () => opts?.stderrFilter?.endStderr();
1537
+ const captured = () => {
1538
+ const held = heldStderr();
1539
+ if (held)
1540
+ recordStderr(held);
1541
+ return buildCapturedOutput(outTail, errBuf);
1542
+ };
1543
+ const cut = (c) => {
1544
+ finish(() => ({ ok: false, error: createCutError(c, captured()) }));
1545
+ };
1546
+ const wallTimer = wallMs !== undefined ? setTimeout(() => cut({ code: "timeout", message: `command exceeded ${wallMs}ms` }), wallMs) : undefined;
1520
1547
  let livenessTimer;
1521
1548
  const liveness = bounds.liveness;
1522
1549
  const armLiveness = () => {
@@ -1525,28 +1552,18 @@ export class RemoteK8sExecutionEnv {
1525
1552
  clearTimeout(livenessTimer);
1526
1553
  const idle = Date.now() - lastProgress;
1527
1554
  if (idle >= liveness.ms) {
1528
- const cut = buildLivenessCut(liveness);
1529
- finish({ ok: false, error: new ExecutionError(cut.code, cut.message, undefined, undefined, cut.noOutputSec) });
1555
+ cut(buildLivenessCut(liveness));
1530
1556
  return;
1531
1557
  }
1532
1558
  livenessTimer = setTimeout(armLiveness, liveness.ms - idle);
1533
1559
  };
1534
1560
  armLiveness();
1535
- const onAbort = () => finish({ ok: false, error: new ExecutionError("aborted", "command aborted") });
1561
+ const onAbort = () => cut({ code: "aborted", message: "command aborted" });
1536
1562
  if (opts?.signal) {
1537
1563
  if (opts.signal.aborted)
1538
1564
  return onAbort();
1539
1565
  opts.signal.addEventListener("abort", onAbort, { once: true });
1540
1566
  }
1541
- const takeStderr = (bytes) => {
1542
- if (bytes.length === 0)
1543
- return;
1544
- errBuf.push(bytes);
1545
- if (detachTee)
1546
- feedMemStream(detachTee.err, bytes, K8S_BG_MEM_CAP);
1547
- if (errDecoder)
1548
- opts.onStderr(errDecoder.write(bytes));
1549
- };
1550
1567
  sock.on("message", (...args) => {
1551
1568
  const data = args[0];
1552
1569
  const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
@@ -1588,7 +1605,7 @@ export class RemoteK8sExecutionEnv {
1588
1605
  }
1589
1606
  return;
1590
1607
  }
1591
- finish({ ok: false, error: new ExecutionError("transport_lost", `exec socket error: ${errorMessageOf(e)}`, e instanceof Error ? e : undefined) });
1608
+ finish(() => ({ ok: false, error: new ExecutionError("transport_lost", `exec socket error: ${errorMessageOf(e)}`, e instanceof Error ? e : undefined) }));
1592
1609
  });
1593
1610
  sock.on("close", () => {
1594
1611
  if (spliceSink) {
@@ -1601,25 +1618,30 @@ export class RemoteK8sExecutionEnv {
1601
1618
  return spliceSink.closed("error" in parsed ? { failed: true } : { failed: false, exitCode: parsed.exitCode });
1602
1619
  }
1603
1620
  if (statusFrame === undefined) {
1604
- finish({ ok: false, error: new ExecutionError("transport_lost", "exec socket closed without status — k8s transport lost mid-command") });
1621
+ finish(() => ({ ok: false, error: new ExecutionError("transport_lost", "exec socket closed without status — k8s transport lost mid-command") }));
1605
1622
  return;
1606
1623
  }
1607
1624
  const parsed = exitCodeFromStatus(statusFrame);
1608
1625
  if ("error" in parsed) {
1609
- finish({ ok: false, error: new ExecutionError("spawn_error", parsed.error) });
1626
+ const said = parsed.error;
1627
+ finish(() => ({ ok: false, error: new ExecutionError("spawn_error", said) }));
1610
1628
  return;
1611
1629
  }
1612
- if (opts?.stderrFilter)
1613
- takeStderr(opts.stderrFilter.endStderr());
1614
- const err = errBuf.result();
1615
- const stderr = markTruncated(err.text, err.droppedBytes);
1616
- if (boundOutput) {
1617
- const out = outTail.result();
1618
- finish(ok({ stdout: markTruncated(out.text, out.droppedBytes), stderr, exitCode: parsed.exitCode }));
1619
- }
1620
- else {
1621
- finish(ok({ stdout: Buffer.concat(outBufs).toString("utf8"), stderr, exitCode: parsed.exitCode }));
1622
- }
1630
+ const exitCode = parsed.exitCode;
1631
+ const held = heldStderr();
1632
+ const won = finish(() => {
1633
+ if (held)
1634
+ recordStderr(held);
1635
+ const err = errBuf.result();
1636
+ const stderr = markTruncated(err.text, err.droppedBytes);
1637
+ if (boundOutput) {
1638
+ const out = outTail.result();
1639
+ return ok({ stdout: markTruncated(out.text, out.droppedBytes), stderr, exitCode });
1640
+ }
1641
+ return ok({ stdout: Buffer.concat(outBufs).toString("utf8"), stderr, exitCode });
1642
+ });
1643
+ if (won && held)
1644
+ emitStderr(held);
1623
1645
  });
1624
1646
  const detach = opts?.detach;
1625
1647
  if (detach) {
@@ -1925,30 +1947,15 @@ export class RemoteK8sExecutionEnv {
1925
1947
  lines = lines.slice(0, options.maxLines);
1926
1948
  return ok(lines);
1927
1949
  }
1928
- async ensureParentDir(abs, abortSignal) {
1929
- const dir = path.posix.dirname(abs);
1930
- if (!dir || dir === "." || dir === "/")
1931
- return undefined;
1932
- const mk = await this.fsExec(`mkdir -p ${shellQuote(dir)}`, abortSignal);
1933
- if (!mk.ok)
1934
- return { ok: false, error: fileErrorFromExec(mk.error, dir) };
1935
- if (mk.value.exitCode !== 0)
1936
- return { ok: false, error: classifyFsStderr(mk.value.stderr, dir) };
1937
- return undefined;
1938
- }
1939
1950
  async writeFile(p, content, abortSignal) {
1940
- const abs = this.resolve(p);
1941
- const bad = await this.ensureParentDir(abs, abortSignal);
1942
- if (bad)
1943
- return bad;
1944
- return this.writeChunked(abs, content, true, abortSignal);
1951
+ return writeFileStaged(this.stagedLane(content), this.resolve(p), abortSignal);
1952
+ }
1953
+ stagedLane(content) {
1954
+ return { upload: (dest, signal) => this.writeChunked(dest, content, true, signal), run: (cmd, signal) => this.fsExec(cmd, signal) };
1945
1955
  }
1946
1956
  async appendFile(p, content, abortSignal) {
1947
1957
  const abs = this.resolve(p);
1948
- const bad = await this.ensureParentDir(abs, abortSignal);
1949
- if (bad)
1950
- return bad;
1951
- return this.writeChunked(abs, content, false, abortSignal);
1958
+ return appendFileStaged(this.stagedLane(content), abs, abortSignal, (ctx) => ctx.inPlace((signal) => this.writeChunked(abs, content, false, signal)));
1952
1959
  }
1953
1960
  async writeChunked(abs, content, truncateFirst, abortSignal) {
1954
1961
  const bytes = typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
@@ -2119,7 +2126,7 @@ export function createSandboxPodApi(config, deps) {
2119
2126
  let env;
2120
2127
  const control = () => (env ??= new RemoteK8sExecutionEnv(config, deps));
2121
2128
  return {
2122
- namespace: config.namespace ?? K8S_SANDBOX_NAMESPACE_DEFAULT,
2129
+ namespace: config.namespace,
2123
2130
  list: () => control().listSandboxPods(),
2124
2131
  delete: (name) => control().deletePod(name),
2125
2132
  };
@@ -23,7 +23,7 @@ export interface LocalDockerEnvConfig extends LaneHomeConfig {
23
23
  * lane stays `local-docker` (`REMOTE_EXEC=local-docker` + `DOCKER_HOST`; the settings-schema lane table lists
24
24
  * `DOCKER_HOST` as this lane's optional coordinate, and there is no separate `remote-docker` lane since 7.0.0).
25
25
  * Nothing about the class is host-local: the workspace is `mkdir`'d INSIDE the container (never a bind-mount of a
26
- * host path) and bytes move by `docker cp` / `docker exec cat`, which stream over the daemon API.
26
+ * host path) and bytes move by `docker exec -i … cat >` / `docker exec cat`, which stream over the daemon API.
27
27
  */
28
28
  dockerHost?: string;
29
29
  /** Container memory limit (docker `--memory`, e.g. "2g"). Absent = no limit. */
@@ -62,10 +62,17 @@ export interface LocalDockerEnvDeps {
62
62
  }
63
63
  export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv {
64
64
  /** DUAL-MODE-DESIGN §5: a container is a real isolation boundary; v1 has no managed memory snapshot. */
65
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在本机容器自己的 PID 命名空间里(不传 `--pid=host`),不在本进程所在的进程表。 */
66
+ /** 能力块(引擎 [ref],八键必填、各键只经引擎读器读):`hostLocalPaths` = 这条车道答出的路径是不是本进程所在机器上的文件;`externalContentTarget` = 产出是否来自部署信任边界之外;`io` = 文件与 shell 两面真到达目标;`keepsReadFloor` = 是否守住读拒表的字节地板(本车道不守 ⇒ 读拒表非空的 run 被告知,部署要求地板时准备期拒)。 */
65
67
  readonly capabilities: {
66
- isolation: boolean;
67
- suspendable: boolean;
68
- canonicalPathAuthoritative: boolean;
68
+ isolation: true;
69
+ suspendable: false;
70
+ canonicalPathAuthoritative: true;
71
+ sharesProcessTable: false;
72
+ hostLocalPaths: false;
73
+ externalContentTarget: false;
74
+ io: true;
75
+ keepsReadFloor: false;
69
76
  };
70
77
  /** Working directory inside the container; relative paths resolve against it (ExecutionEnv contract). */
71
78
  cwd: string;
@@ -161,10 +168,27 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
161
168
  maxLines?: number;
162
169
  abortSignal?: AbortSignal;
163
170
  }): Promise<Result<string[], FileError>>;
171
+ /** 暂存写的两只面(规则与提交脚本的唯一属主 = `remote-shell.ts` `writeFileStaged` / `appendFileStaged`;父目录与私有暂存目录由编排的准备那一步建):
172
+ * 上传 = 容器内 `cat` 从 stdin 收整份字节落到私有目录里的暂存件({@link pipeIn});提交 = 容器内一次 shell 往返。两者同一身份 ——
173
+ * 同一只 argv 构造({@link execArgv}),都是镜像的用户。不用 `docker cp`:它由守护进程以 root 落盘,镜像用户 ≠ root 时暂存件属 root、
174
+ * 提交方拿不走也清不掉,「目录不收新名字」也探不出来(root 哪里都写得进)。 */
175
+ private stagedLane;
176
+ /** 整旧或整新(暂存 → 一次 `mv -f`)。修前 `docker cp` 直落目标(以守护进程的 root 身份):传输中途断 / 取消时目标剩什么取决于守护进程的解包实现,本适配器不依赖它。 */
164
177
  writeFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
178
+ /** 追加(暂存要加的字节 → 一次 `cat >>`;inode 不换、已有字节不重写)。修前 = 读旧 + 拼 + 整份 cp(O(文件);中途断 ⇒ 目标没了或半份)。
179
+ * 目录不收新名字 / 私有目录收不成私有时的退路 = 容器内 `cat >>` 从 stdin 直接追加到目标(同一身份)。 */
165
180
  appendFile(p: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
181
+ /**
182
+ * 把整份内容经 stdin 交给容器里的 `cat`,落到 `dest`(`>` 建或覆盖;`>>` 追加)。走与提交 / 文件面探针同一只往返({@link fsExec},argv 只多一个 `-i`)
183
+ * ⇒ 同一身份、同一工作目录、同一道守卫;二进制安全(载荷不经 shell 转义,也不在宿主落临时件)。stdin 没写完而进程退 0 ⇒ 按失败答(载荷未必整份到了)。
184
+ */
185
+ private pipeIn;
166
186
  private posixFsInst?;
167
187
  private get posixFs();
188
+ /** 文件面的一次容器内 shell 往返(基座、暂存写的准备 / 上传 / 提交 / 清理共用)。承 fsReady 契约(其余三腿走公有 exec、守卫天然在;本腿直用私有
189
+ * docker,必须自带):destroyed 守卫 + 懒连接,fail closed 不抛 —— 消息保留 "destroyed" 字样(测试钉的就是它)。`input` 在场 ⇒ 同一只 argv 多一个
190
+ * `-i`、载荷走 stdin({@link pipeIn});身份与工作目录与不带载荷的往返逐字相同。 */
191
+ private fsExec;
168
192
  fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
169
193
  listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
170
194
  readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
@@ -198,7 +222,8 @@ export declare class RemoteLocalDockerExecutionEnv implements RemoteExecutionEnv
198
222
  /** Prepend `-H <dockerHost>` to a docker argv when configured (per-invocation, not the process env). */
199
223
  private withHostFlag;
200
224
  /** Build the `docker exec` argv for a command, applying cwd (`-w`) and per-command env (`-e K=V`).
201
- * argv elements are SEPARATE (no host-side shell join) — the command runs in the CONTAINER's `/bin/sh -c`. */
225
+ * argv elements are SEPARATE (no host-side shell join) — the command runs in the CONTAINER's `/bin/sh -c`.
226
+ * `stdin` ⇒ `-i` (the payload of {@link pipeIn}); nothing else differs, so a piped upload runs as the same user as every other exec. */
202
227
  private execArgv;
203
228
  /** Like {@link execArgv} but for an internal FileSystem helper command (uses the workspace cwd + adapter env). */
204
229
  private execRaw;