@sema-agent/server 7.136.0 → 7.138.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 (327) hide show
  1. package/MIGRATION.md +501 -139
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +149 -34
  5. package/deploy/sema-up/README.md +14 -0
  6. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  7. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  8. package/dist/approval-card.d.ts +21 -14
  9. package/dist/approval-card.js +1 -0
  10. package/dist/approval-content-kind.d.ts +6 -0
  11. package/dist/approval-content-kind.js +7 -0
  12. package/dist/approval-gate-currency.d.ts +48 -6
  13. package/dist/approval-gate-currency.js +16 -9
  14. package/dist/approval-hmac.d.ts +13 -1
  15. package/dist/approval-hmac.js +1 -1
  16. package/dist/approval.js +3 -3
  17. package/dist/audit.js +1 -0
  18. package/dist/auth-bridge.js +2 -2
  19. package/dist/background-agent-name.d.ts +5 -0
  20. package/dist/background-agent-name.js +5 -0
  21. package/dist/boot/budget-tracing.d.ts +1 -12
  22. package/dist/boot/deployment-mods.d.ts +46 -16
  23. package/dist/boot/deployment-mods.js +82 -6
  24. package/dist/boot/execution-env.d.ts +24 -1
  25. package/dist/boot/execution-env.js +69 -6
  26. package/dist/boot/governance-seams.d.ts +10 -17
  27. package/dist/boot/governance-seams.js +5 -17
  28. package/dist/boot/leader.d.ts +3 -0
  29. package/dist/boot/leader.js +1 -0
  30. package/dist/boot/org-delivery.js +15 -2
  31. package/dist/boot/reapers.d.ts +3 -0
  32. package/dist/boot/reapers.js +15 -1
  33. package/dist/boot/resolve-spec.d.ts +7 -0
  34. package/dist/boot/resolve-spec.js +36 -26
  35. package/dist/boot/retention-lane.d.ts +11 -5
  36. package/dist/boot/retention-lane.js +2 -2
  37. package/dist/boot/runner-deps.d.ts +7 -3
  38. package/dist/boot/runner-deps.js +1 -0
  39. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  40. package/dist/boot/stage-04-stores.d.ts +2 -13
  41. package/dist/boot/stage-05-execution-env.d.ts +6 -13
  42. package/dist/boot/stage-05-execution-env.js +17 -5
  43. package/dist/boot/stage-06-runners.d.ts +6 -13
  44. package/dist/boot/stage-06-runners.js +10 -3
  45. package/dist/boot/stage-07-capability-layer.d.ts +7 -13
  46. package/dist/boot/stage-07-capability-layer.js +3 -3
  47. package/dist/boot/stage-08-reapers.d.ts +7 -13
  48. package/dist/boot/stage-08-reapers.js +2 -1
  49. package/dist/boot/stage-09-leader.d.ts +7 -13
  50. package/dist/boot/stage-09-leader.js +2 -2
  51. package/dist/boot/stage-10-http-server.d.ts +7 -13
  52. package/dist/boot/stage-10-http-server.js +6 -3
  53. package/dist/boot/stores.d.ts +1 -1
  54. package/dist/boot/web-admin.d.ts +5 -2
  55. package/dist/boot/web-admin.js +3 -3
  56. package/dist/brain.js +5 -5
  57. package/dist/broker/broker.d.ts +89 -0
  58. package/dist/broker/broker.js +287 -0
  59. package/dist/broker/constants.d.ts +39 -0
  60. package/dist/broker/constants.js +15 -0
  61. package/dist/broker/http1.d.ts +111 -0
  62. package/dist/broker/http1.js +256 -0
  63. package/dist/broker/lease-book.d.ts +87 -0
  64. package/dist/broker/lease-book.js +142 -0
  65. package/dist/broker/lease-file.d.ts +60 -0
  66. package/dist/broker/lease-file.js +127 -0
  67. package/dist/broker/main.d.ts +2 -0
  68. package/dist/broker/main.js +20 -0
  69. package/dist/budget.d.ts +17 -15
  70. package/dist/budget.js +2 -2
  71. package/dist/capabilities/composite-members.d.ts +2 -1
  72. package/dist/capabilities/composite-members.js +2 -1
  73. package/dist/capabilities/engine-capabilities.js +1 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -11
  75. package/dist/capabilities/mcp-probe.js +44 -5
  76. package/dist/capabilities/scenarios.d.ts +23 -8
  77. package/dist/capabilities/scenarios.js +13 -8
  78. package/dist/capabilities/skills.d.ts +28 -5
  79. package/dist/capabilities/skills.js +8 -0
  80. package/dist/capability-facts.d.ts +6 -0
  81. package/dist/capability-facts.js +5 -0
  82. package/dist/center-credential-renewer.js +5 -1
  83. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  84. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  85. package/dist/checkpoint-identity.d.ts +13 -6
  86. package/dist/config-catalog.js +16 -7
  87. package/dist/config-types.d.ts +24 -7
  88. package/dist/config.d.ts +1 -1
  89. package/dist/config.js +42 -13
  90. package/dist/deployment-governance.d.ts +2 -2
  91. package/dist/deployment-governance.js +7 -9
  92. package/dist/deployment-shape.d.ts +6 -6
  93. package/dist/device-ws-protocol.d.ts +1 -0
  94. package/dist/device-ws-protocol.js +2 -1
  95. package/dist/file-history-disclosure.d.ts +4 -2
  96. package/dist/file-history-disclosure.js +2 -0
  97. package/dist/gate-settlement.d.ts +71 -0
  98. package/dist/gate-settlement.js +40 -0
  99. package/dist/hooks/classic-stations.d.ts +12 -54
  100. package/dist/hooks/classic-stations.js +1 -0
  101. package/dist/hooks/command-spawn.d.ts +2 -2
  102. package/dist/hooks/hook-runner.js +14 -14
  103. package/dist/http/admission.js +14 -9
  104. package/dist/http/approval-sweeps.js +4 -3
  105. package/dist/http/caller-trust.d.ts +2 -1
  106. package/dist/http/caller-trust.js +1 -3
  107. package/dist/http/checkpoint-ctx.d.ts +48 -7
  108. package/dist/http/checkpoint-ctx.js +40 -0
  109. package/dist/http/dispatch.d.ts +4 -0
  110. package/dist/http/dispatch.js +18 -10
  111. package/dist/http/durable-run-leg.d.ts +5 -1
  112. package/dist/http/durable-run-leg.js +5 -3
  113. package/dist/http/health-self-report.d.ts +2 -2
  114. package/dist/http/memory-compliance-failure-reply.js +3 -0
  115. package/dist/http/principal-gate.d.ts +15 -10
  116. package/dist/http/principal-gate.js +48 -9
  117. package/dist/http/resume-legs.d.ts +7 -31
  118. package/dist/http/resume-legs.js +185 -126
  119. package/dist/http/route-ctx.d.ts +38 -53
  120. package/dist/http/route-ctx.js +2 -5
  121. package/dist/http/routes/a2a-serve.js +10 -4
  122. package/dist/http/routes/agents-roster.d.ts +1 -1
  123. package/dist/http/routes/agents-roster.js +5 -1
  124. package/dist/http/routes/approvals-assistant.js +24 -10
  125. package/dist/http/routes/capabilities.js +23 -11
  126. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  127. package/dist/http/routes/mcp-reconnect.js +7 -7
  128. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/rules.js +3 -3
  131. package/dist/http/routes/runs.js +13 -8
  132. package/dist/http/routes/session-sync.js +42 -6
  133. package/dist/http/routes/sessions.js +6 -3
  134. package/dist/http/routes/side-query.js +2 -3
  135. package/dist/http/routes/tasks.js +794 -780
  136. package/dist/http/server.d.ts +35 -25
  137. package/dist/http/stored-body-replay.d.ts +3 -3
  138. package/dist/http/stored-body-replay.js +6 -1
  139. package/dist/http/wire-types.d.ts +66 -33
  140. package/dist/identity-source.d.ts +65 -0
  141. package/dist/identity-source.js +45 -0
  142. package/dist/index.d.ts +2 -0
  143. package/dist/index.js +1 -0
  144. package/dist/leader/wire.d.ts +7 -0
  145. package/dist/leader/wire.js +5 -4
  146. package/dist/mcp-assertion-header.d.ts +51 -40
  147. package/dist/mcp-assertion-header.js +10 -11
  148. package/dist/mcp-authorization-face.d.ts +27 -0
  149. package/dist/mcp-authorization-face.js +15 -0
  150. package/dist/memory-operator-faces.d.ts +13 -4
  151. package/dist/memory-operator-faces.js +1 -17
  152. package/dist/memory-store-refusal.d.ts +3 -3
  153. package/dist/memory-sync-client.js +72 -57
  154. package/dist/memory-sync.d.ts +1 -1
  155. package/dist/model-entry-refusal.d.ts +2 -0
  156. package/dist/model-entry-refusal.js +11 -0
  157. package/dist/model-route-gap.d.ts +6 -22
  158. package/dist/model-route-gap.js +7 -5
  159. package/dist/model-select.d.ts +6 -17
  160. package/dist/model-select.js +0 -3
  161. package/dist/mods-declaration.d.ts +110 -0
  162. package/dist/mods-declaration.js +109 -0
  163. package/dist/namespaced-rule-name.d.ts +1 -1
  164. package/dist/observability/err-text.d.ts +5 -0
  165. package/dist/observability/err-text.js +9 -0
  166. package/dist/observability/fail-open.d.ts +17 -1
  167. package/dist/observability/fail-open.js +17 -1
  168. package/dist/observability/metrics.js +1 -1
  169. package/dist/observability/run-terminal.js +1 -0
  170. package/dist/observability/secret-env-scrub.d.ts +1 -1
  171. package/dist/observability/secret-env-scrub.js +1 -1
  172. package/dist/operator-ask.d.ts +2 -2
  173. package/dist/org-delivery-gate.js +1 -1
  174. package/dist/org-memory-admission.d.ts +2 -2
  175. package/dist/org-memory-admission.js +39 -2
  176. package/dist/parked-decide.d.ts +15 -2
  177. package/dist/parked-decide.js +17 -10
  178. package/dist/peer-directory.js +2 -0
  179. package/dist/peer-seat.d.ts +150 -0
  180. package/dist/peer-seat.js +239 -0
  181. package/dist/permission-rule-list.d.ts +2 -2
  182. package/dist/permission-rule-vocab.d.ts +7 -7
  183. package/dist/permission-rule-vocab.js +3 -3
  184. package/dist/plain-string-record.d.ts +38 -0
  185. package/dist/plain-string-record.js +38 -0
  186. package/dist/plugins/background-agent-store-sql.js +6 -3
  187. package/dist/plugins/caching-session-store.d.ts +5 -0
  188. package/dist/plugins/caching-session-store.js +6 -0
  189. package/dist/plugins/checkpoint-store-sql.d.ts +34 -29
  190. package/dist/plugins/checkpoint-store-sql.js +14 -14
  191. package/dist/plugins/file-history-store-sql.js +10 -16
  192. package/dist/plugins/file-run-store.js +8 -2
  193. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  194. package/dist/plugins/k8s-exec-protocol.js +116 -28
  195. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  196. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  197. package/dist/plugins/k8s-pod-reaper.d.ts +106 -0
  198. package/dist/plugins/k8s-pod-reaper.js +122 -0
  199. package/dist/plugins/local-checkpoint-store.d.ts +4 -17
  200. package/dist/plugins/local-checkpoint-store.js +3 -4
  201. package/dist/plugins/local-session-store.d.ts +66 -27
  202. package/dist/plugins/local-session-store.js +60 -28
  203. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  204. package/dist/plugins/mailbox-store-sql.js +103 -15
  205. package/dist/plugins/memory-run-store.js +2 -0
  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 +34 -12
  209. package/dist/plugins/one-time-migrations.js +51 -9
  210. package/dist/plugins/permission-rule-store-file.js +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  212. package/dist/plugins/permission-rule-store-sql.js +37 -30
  213. package/dist/plugins/pg-pool.js +4 -0
  214. package/dist/plugins/pg-session-storage.d.ts +10 -2
  215. package/dist/plugins/pg-session-storage.js +20 -7
  216. package/dist/plugins/remote-env-adb.d.ts +6 -1
  217. package/dist/plugins/remote-env-adb.js +2 -1
  218. package/dist/plugins/remote-env-device.d.ts +6 -1
  219. package/dist/plugins/remote-env-device.js +5 -1
  220. package/dist/plugins/remote-env-e2b.d.ts +5 -0
  221. package/dist/plugins/remote-env-e2b.js +2 -1
  222. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  223. package/dist/plugins/remote-env-file-error.js +7 -1
  224. package/dist/plugins/remote-env-host.d.ts +10 -2
  225. package/dist/plugins/remote-env-host.js +8 -2
  226. package/dist/plugins/remote-env-k8s.d.ts +140 -11
  227. package/dist/plugins/remote-env-k8s.js +277 -15
  228. package/dist/plugins/remote-env-local-docker.d.ts +5 -0
  229. package/dist/plugins/remote-env-local-docker.js +2 -1
  230. package/dist/plugins/remote-env-ssh.d.ts +6 -1
  231. package/dist/plugins/remote-env-ssh.js +2 -1
  232. package/dist/plugins/retention-store-sql.d.ts +14 -2
  233. package/dist/plugins/retention-store-sql.js +6 -0
  234. package/dist/plugins/run-store-sql.js +10 -4
  235. package/dist/plugins/session-fork.d.ts +10 -13
  236. package/dist/plugins/session-fork.js +5 -25
  237. package/dist/plugins/session-ownership.d.ts +24 -3
  238. package/dist/plugins/session-ownership.js +24 -5
  239. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  240. package/dist/plugins/session-workspace-cell.js +38 -0
  241. package/dist/plugins/sql-errors.d.ts +23 -2
  242. package/dist/plugins/sql-errors.js +15 -0
  243. package/dist/plugins/store-backend.js +7 -7
  244. package/dist/plugins/store-contracts.d.ts +10 -1
  245. package/dist/plugins/store-fault.d.ts +2 -2
  246. package/dist/plugins/store-fault.js +4 -2
  247. package/dist/plugins/tidb-pool.js +13 -0
  248. package/dist/plugins/tidb-session-store.d.ts +17 -2
  249. package/dist/plugins/tidb-session-store.js +18 -7
  250. package/dist/plugins/tool-result-store-sql.d.ts +13 -6
  251. package/dist/plugins/tool-result-store-sql.js +8 -7
  252. package/dist/plugins/worktree-isolation.d.ts +3 -2
  253. package/dist/plugins/worktree-isolation.js +1 -7
  254. package/dist/project-identity.d.ts +1 -14
  255. package/dist/project-identity.js +0 -4
  256. package/dist/request-key-closure.js +1 -1
  257. package/dist/request-value-closure.d.ts +31 -2
  258. package/dist/request-value-closure.js +7 -1
  259. package/dist/rule-entry-list.d.ts +3 -2
  260. package/dist/rule-entry-list.js +2 -2
  261. package/dist/rules-consent.js +12 -7
  262. package/dist/run-local.d.ts +2 -2
  263. package/dist/run-local.js +12 -6
  264. package/dist/runs.d.ts +7 -1
  265. package/dist/runs.js +8 -2
  266. package/dist/runtime-governance.d.ts +5 -5
  267. package/dist/sandbox-git-lease.d.ts +49 -0
  268. package/dist/sandbox-git-lease.js +60 -0
  269. package/dist/sandbox-git-plane.d.ts +84 -0
  270. package/dist/sandbox-git-plane.js +105 -0
  271. package/dist/security.d.ts +16 -4
  272. package/dist/security.js +8 -3
  273. package/dist/server-secret-env.d.ts +12 -3
  274. package/dist/server-secret-env.js +18 -2
  275. package/dist/session-error-wire.d.ts +14 -6
  276. package/dist/session-error-wire.js +2 -1
  277. package/dist/session-incarnation.d.ts +4 -5
  278. package/dist/session-incarnation.js +2 -2
  279. package/dist/session-policy-wire.d.ts +1 -1
  280. package/dist/session-policy-wire.js +2 -2
  281. package/dist/session-sync.d.ts +1 -1
  282. package/dist/session-workspace.d.ts +130 -0
  283. package/dist/session-workspace.js +83 -0
  284. package/dist/spec-fields.d.ts +44 -14
  285. package/dist/spec-fields.js +33 -12
  286. package/dist/standing-answer-grant.d.ts +2 -13
  287. package/dist/standing-answer-grant.js +0 -7
  288. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  289. package/dist/stored-request-prompt-profile-m30.js +25 -0
  290. package/dist/task-a2a.d.ts +44 -14
  291. package/dist/task-a2a.js +46 -20
  292. package/dist/task-mcp.d.ts +34 -21
  293. package/dist/task-mcp.js +46 -22
  294. package/dist/task-settings.d.ts +2 -2
  295. package/dist/tool-approval.d.ts +43 -9
  296. package/dist/tool-approval.js +57 -15
  297. package/dist/tool-rule-names.d.ts +5 -0
  298. package/dist/trace/core-keyset-guard.d.ts +34 -19
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +4 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +5 -2
  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/ledger-sink.d.ts +4 -5
  306. package/dist/trace/ledger-sink.js +3 -7
  307. package/dist/trace/project.d.ts +62 -10
  308. package/dist/trace/project.js +49 -9
  309. package/dist/trace/redact.d.ts +18 -6
  310. package/dist/trace/redact.js +154 -60
  311. package/dist/trace/self-minted-wire.d.ts +500 -7
  312. package/dist/trace/self-minted-wire.js +182 -1
  313. package/dist/trace/wire-projection-faces.d.ts +7 -5
  314. package/dist/trace/wire-projection-faces.js +9 -4
  315. package/dist/user-authored-text.d.ts +2 -2
  316. package/dist/user-authored-text.js +16 -1
  317. package/dist/web-admin-client/apply-effective.js +0 -1
  318. package/dist/web-admin-client/center-request.d.ts +23 -4
  319. package/dist/web-admin-client/center-request.js +42 -8
  320. package/dist/web-admin-client/http-client.js +6 -6
  321. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  322. package/dist/web-admin-client/skills-mcp.js +31 -18
  323. package/dist/web-admin-client/types.d.ts +10 -5
  324. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  325. package/package.json +3 -3
  326. package/wire-changes/server@7.137.0.json +825 -0
  327. package/wire-changes/server@7.138.0.json +446 -0
@@ -14,11 +14,13 @@ import { createLogger } from "../observability/logger.js";
14
14
  import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
15
15
  import { isPgUniqueViolation } from "./sql-errors.js";
16
16
  import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSource } from "../instruction-source-notice.js";
17
- import { MANAGED_RETENTION } from "./retention-store-sql.js";
17
+ import { SESSION_META_WORKSPACE_COLUMN, decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
18
+ import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
18
19
  import { SESSION_META_INCARNATION_COLUMN, carriedIncarnation, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
19
20
  import { ensureIndexes } from "./ensure-index.js";
20
- import { assertFirstOwnerWritable, forkOwnerOf } from "./session-ownership.js";
21
- import { forkCopyOf, forkPointOf } from "./session-fork.js";
21
+ import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue } from "./session-ownership.js";
22
+ import { selectForkRows } from "@sema-agent/core";
23
+ import { FORK_LOG_ROW_SHAPE, forkPointOf } from "./session-fork.js";
22
24
  const isDupKey = isPgUniqueViolation;
23
25
  function baselineQuery(text, values) {
24
26
  return { text, values, query_timeout: BASELINE_IO_DEADLINE_MS };
@@ -39,6 +41,8 @@ export const PG_SESSION_SCHEMA = [
39
41
  pinned BOOLEAN NOT NULL DEFAULT FALSE,
40
42
  -- instruction_source(S-690):换源通告的基线 —— TiDB 孪生同名同宽,三态读法(NULL / '' / 链成员相对路径)见那里的列注。
41
43
  instruction_source VARCHAR(64) COLLATE "C",
44
+ -- workspace:本会话上一条腿自己的最后一帧 workspace_changed —— TiDB 孪生同名同义(列注与唯一属主见那里:src/plugins/session-workspace-cell.ts)。
45
+ ${SESSION_META_WORKSPACE_COLUMN.pg},
42
46
  -- #292 C18(core 5.48.0 design/323)subagent 转录持久化 —— placement 元组(TiDB 孪生同名同宽,
43
47
  -- 语义与六条义务见 src/plugins/session-placement.ts 顶注):创建时一次落行、first-write 不可变;
44
48
  -- placement_kind 在场 ⇒ 住在 placement 分区(不上宿主枚举 / claim 形 acquire 恒拒 / release=真删除 /
@@ -176,13 +180,14 @@ export class PgSessionStorage extends BaseSessionStorage {
176
180
  }
177
181
  export class PgSessionStore {
178
182
  pool;
179
- retention = MANAGED_RETENTION;
183
+ retention;
180
184
  placements = SQL_SESSION_PLACEMENTS;
181
185
  pending = new Map();
182
186
  log;
183
187
  constructor(pool, opts = {}) {
184
188
  this.pool = pool;
185
189
  this.log = opts.log ?? createLogger();
190
+ this.retention = opts.retention ?? UNMANAGED_RETENTION;
186
191
  }
187
192
  async acquire(sessionId, opts) {
188
193
  if (!sessionId) {
@@ -219,7 +224,7 @@ export class PgSessionStore {
219
224
  return { session: new StoredSession(storage), sessionId };
220
225
  }
221
226
  async register(sessionId, owner) {
222
- if (typeof owner !== "string" && owner !== null) {
227
+ if (!isSessionOwnerValue(owner)) {
223
228
  if ((await this.headOf(sessionId)) !== undefined)
224
229
  return;
225
230
  assertFirstOwnerWritable(sessionId, owner);
@@ -417,6 +422,14 @@ export class PgSessionStore {
417
422
  await this.pool.query(baselineQuery("UPDATE session_meta SET instruction_source = $1 WHERE session_id = $2 AND instruction_source IS NULL", [next, sessionId]));
418
423
  }
419
424
  }
425
+ async readSessionWorkspace(sessionId) {
426
+ const res = await this.pool.query(`SELECT ${jsonColumnText("pg", "workspace")} FROM session_meta WHERE session_id = $1`, [sessionId]);
427
+ const row = res.rows[0];
428
+ return row === undefined ? undefined : decodeSessionWorkspaceCell(row.workspace, sessionId);
429
+ }
430
+ async recordSessionWorkspace(sessionId, incarnation, workspace) {
431
+ await this.pool.query("UPDATE session_meta SET workspace = $1::jsonb WHERE session_id = $2 AND incarnation = $3", [encodeSessionWorkspaceCell(workspace, pgSafeJsonStringify, sessionId), sessionId, incarnation]);
432
+ }
420
433
  async fork(sourceId, owner) {
421
434
  const metaRes = await this.pool.query("SELECT leaf_id, owner FROM session_meta WHERE session_id = $1", [sourceId]);
422
435
  if (!metaRes.rows[0])
@@ -428,7 +441,7 @@ export class PgSessionStore {
428
441
  return null;
429
442
  const point = await forkPointOf(storage);
430
443
  const eventRes = await this.pool.query(`SELECT entry_id, parent_id, type, ${jsonColumnText("pg", "payload")}, ts FROM session_event WHERE session_id = $1 ORDER BY seq ASC`, [sourceId]);
431
- const { rows, leafId: forkLeafId } = forkCopyOf(eventRes.rows, leafId, point);
444
+ const { rows, leafId: forkLeafId } = selectForkRows(eventRes.rows, leafId, point, FORK_LOG_ROW_SHAPE);
432
445
  const newId = uuidv7();
433
446
  const now = new Date();
434
447
  const conn = await this.pool.connect();
@@ -523,7 +536,7 @@ export class PgSessionStore {
523
536
  }
524
537
  await conn.query("DELETE FROM session_event WHERE session_id = $1", [sessionId]);
525
538
  await conn.query("DELETE FROM session_meta WHERE session_id = $1 AND owner IS NOT DISTINCT FROM $2 AND placement_kind IS NULL", [sessionId, owner]);
526
- await this.insertValidatedLog(conn, sessionId, owner, validated, carriedIncarnation(sessionId, placedRes.rows[0], owner));
539
+ await this.insertValidatedLog(conn, sessionId, owner, validated, carriedIncarnation(sessionId, placedRes.rows[0]));
527
540
  await conn.query("COMMIT");
528
541
  }
529
542
  catch (err) {
@@ -58,14 +58,16 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
58
58
  * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
59
59
  * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
60
60
  * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
61
- * 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
61
+ * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
62
62
  * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
63
63
  * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
64
64
  */
65
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在设备上(adb shell),不在本进程所在的进程表。 */
65
66
  readonly capabilities: {
66
67
  isolation: boolean;
67
68
  suspendable: boolean;
68
69
  canonicalPathAuthoritative: boolean;
70
+ sharesProcessTable: boolean;
69
71
  };
70
72
  cwd: string;
71
73
  private connected;
@@ -85,6 +87,9 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
85
87
  /** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 Android 设备的 shell 里(设备自己的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
86
88
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
87
89
  readonly cdPath: "unknown";
90
+ /** core 7.56.0 [ref]:shell 子进程继承的环境里收不到的条目 —— 本车道 shell 的环境在 Android 设备上**构建**(本进程的 env 从不注入,只有引擎逐次交的 `env`
91
+ * 这条显式授予),没有一条是从本进程继承的 ⇒ `[]`(core 契约:「`[]` when its shell environment is built rather than inherited」)。 */
92
+ readonly withheldEnv: readonly [];
88
93
  private readonly cfg;
89
94
  private readonly spawnFn;
90
95
  private readonly injectedConnectOnce?;
@@ -27,7 +27,7 @@ export function isAdbTransportLost(stderr) {
27
27
  return /device offline|device '.*' not found|no devices\/emulators found|connection reset|protocol fault|device disconnected/i.test(stderr);
28
28
  }
29
29
  export class RemoteAdbExecutionEnv {
30
- capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
30
+ capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false };
31
31
  cwd;
32
32
  connected = false;
33
33
  destroyed = false;
@@ -37,6 +37,7 @@ export class RemoteAdbExecutionEnv {
37
37
  }
38
38
  homeSeat;
39
39
  cdPath = "unknown";
40
+ withheldEnv = [];
40
41
  cfg;
41
42
  spawnFn;
42
43
  injectedConnectOnce;
@@ -89,6 +89,9 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
89
89
  /** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在设备上(设备自己的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
90
90
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
91
91
  readonly cdPath: "unknown";
92
+ /** core 7.56.0 [ref]:shell 子进程继承的环境里收不到的条目 —— 本车道 shell 的环境在设备上**构建**(本进程的 env 从不注入,只有引擎逐次交的 `env`
93
+ * 这条显式授予),没有一条是从本进程继承的 ⇒ `[]`(core 契约:「`[]` when its shell environment is built rather than inherited」)。 */
94
+ readonly withheldEnv: readonly [];
92
95
  /**
93
96
  * 🔴 `suspendable: false` 在 core 契约里**同时断言**「工作区在目标上外部持久」(remote-env.ts 的
94
97
  * load-bearing 不变量)—— 设备工作区 = 员工机磁盘,真持久 ✅,park-only 降级腿因此合法可用。
@@ -101,14 +104,16 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
101
104
  * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
102
105
  * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
103
106
  * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
104
- * 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
107
+ * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
105
108
  * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
106
109
  * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
107
110
  */
111
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在注册设备上(设备执行器),不在本进程所在的进程表。 */
108
112
  readonly capabilities: {
109
113
  isolation: boolean;
110
114
  suspendable: boolean;
111
115
  canonicalPathAuthoritative: boolean;
116
+ sharesProcessTable: boolean;
112
117
  };
113
118
  readonly externalContentTarget = true;
114
119
  /** [ref]:设备路径不在 worker 宿主文件系统上 —— host-path 回执消费方据此拒绝把它当宿主路径呈现。 */
@@ -13,6 +13,7 @@ export const DEVICE_OUTCOME_TO_FILE_ERROR = {
13
13
  invalid: "invalid",
14
14
  is_directory: "is_directory",
15
15
  liveness_no_output: "unknown",
16
+ name_too_long: "name_too_long",
16
17
  not_directory: "not_directory",
17
18
  not_found: "not_found",
18
19
  not_supported: "not_supported",
@@ -35,6 +36,7 @@ export const DEVICE_OUTCOME_TO_EXEC_ERROR = {
35
36
  invalid: "unknown",
36
37
  is_directory: "unknown",
37
38
  liveness_no_output: "liveness_no_output",
39
+ name_too_long: "unknown",
38
40
  not_directory: "unknown",
39
41
  not_found: "unknown",
40
42
  not_supported: "shell_unavailable",
@@ -123,7 +125,8 @@ export class RemoteDeviceExecutionEnv {
123
125
  cfg;
124
126
  homeDir;
125
127
  cdPath = "unknown";
126
- capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
128
+ withheldEnv = [];
129
+ capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: false };
127
130
  externalContentTarget = true;
128
131
  hostLocalPaths = false;
129
132
  cwd;
@@ -766,6 +769,7 @@ const OUTCOME_TO_REMOTE_ERROR = {
766
769
  invalid: "unknown",
767
770
  is_directory: "unknown",
768
771
  liveness_no_output: "liveness_no_output",
772
+ name_too_long: "unknown",
769
773
  not_directory: "unknown",
770
774
  not_found: "unknown",
771
775
  not_supported: "unsupported",
@@ -104,6 +104,9 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
104
104
  /** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 e2b 沙箱里(模板镜像的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
105
105
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
106
106
  readonly cdPath: "unknown";
107
+ /** core 7.56.0 [ref]:shell 子进程继承的环境里收不到的条目 —— 本车道 shell 的环境在沙箱里**构建**(本进程的 env 从不注入,只有引擎逐次交的 `env`
108
+ * 这条显式授予),没有一条是从本进程继承的 ⇒ `[]`(core 契约:「`[]` when its shell environment is built rather than inherited」)。 */
109
+ readonly withheldEnv: readonly [];
107
110
  private readonly cfg;
108
111
  private readonly sandboxApi;
109
112
  /** This lane's own liveness bound — what {@link commandBoundsOf} falls back to when a command's caller stated no bound. */
@@ -132,10 +135,12 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
132
135
  constructor(config: RemoteContainerEnvConfig, deps?: RemoteContainerEnvDeps);
133
136
  /** [ref] §9: E2B is a FULL RemoteExecutionEnv — an isolated, snapshot-suspendable microVM. (SSH/ADB
134
137
  * adapters declare {isolation:false, suspendable:false} — real machines/devices, not snapshotable.) */
138
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在云沙箱里,不在本进程所在的进程表。 */
135
139
  readonly capabilities: {
136
140
  isolation: boolean;
137
141
  suspendable: boolean;
138
142
  canonicalPathAuthoritative: boolean;
143
+ sharesProcessTable: boolean;
139
144
  };
140
145
  workspaceHandle(): WorkspaceHandle;
141
146
  /**
@@ -20,6 +20,7 @@ export class RemoteContainerExecutionEnv {
20
20
  cwd;
21
21
  homeDir;
22
22
  cdPath = "unknown";
23
+ withheldEnv = [];
23
24
  cfg;
24
25
  sandboxApi;
25
26
  laneLiveness;
@@ -45,7 +46,7 @@ export class RemoteContainerExecutionEnv {
45
46
  this.homeDir = config.homeDir;
46
47
  this.sandboxApi = deps.sandboxApi ?? Sandbox;
47
48
  }
48
- capabilities = { isolation: true, suspendable: true, canonicalPathAuthoritative: true };
49
+ capabilities = { isolation: true, suspendable: true, canonicalPathAuthoritative: true, sharesProcessTable: false };
49
50
  workspaceHandle() {
50
51
  if (!this.handle) {
51
52
  throw new RemoteExecutionError("connect_failed", "workspaceHandle() called before connect()");
@@ -18,6 +18,12 @@
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
+ /**
22
+ * core 7.56.0 [ref]:`ENAMETOOLONG` 的**一只读法**(本文件的 stderr 分类器与 host 车道的 errno 映射同调)—— 路径有一段超过 NAME_MAX(按 UTF-8 字节量)⇒
23
+ * `name_too_long`:没有目录装得下这个名字,引擎读站把它读作缺席(与 `not_found` 同读);段都短、只是整条超 PATH_MAX ⇒ `unknown`(那种文件可能存在,从下层目录够得着)。
24
+ * 没有路径可判 ⇒ `unknown`。与 core `NodeExecutionEnv` 同判(按 `/` 与 `\` 切段)。
25
+ */
26
+ export declare function nameTooLongCode(p: string | undefined): "name_too_long" | "unknown";
21
27
  /**
22
28
  * shell 命令 exit≠0 时把 **stderr** 分类成 `FileError` 的共享分类器([ref] B6 R2 裁决:k8s 形=正形)。
23
29
  *
@@ -29,7 +35,8 @@ export declare function fileErrorFromExec(e: ExecutionError | RemoteExecutionErr
29
35
  * 1. 命令缺失形(`command not found` / `applet not found` / `: not found`)→ `unknown` —— 工具不存在
30
36
  * 不是文件不存在;probe 族的 not_found fallback 绝不能吞它。
31
37
  * 2. 完整硬失败(EIO/ELOOP/ENAMETOOLONG/ENOSPC)→ `unknown` —— 同 k8s 原注:真错不被 read-op 的
32
- * not_found fallback 吞掉。
38
+ * not_found fallback 吞掉。**例外**(core 7.56.0 [ref]):`ENAMETOOLONG` 且路径有一段超过 NAME_MAX ⇒ `name_too_long`
39
+ * ({@link nameTooLongCode},一只读法,host 车道的 errno 映射同调)—— 那个名字没有目录装得下,是一种「不存在」,不是硬失败。
33
40
  *
34
41
  * `fallback` 由站点显式给:probe 族(readlink/realpath/stat——"它在吗"类问题)传 `not_found`
35
42
  * (空 stderr + exit≠0 的主导原因就是缺文件);mutate 族(mkdir/rm/mktemp)传 `unknown`(诚实降级)。
@@ -3,11 +3,17 @@ 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;
7
+ export function nameTooLongCode(p) {
8
+ return p !== undefined && p.split(/[\\/]/).some((part) => Buffer.byteLength(part, "utf8") > NAME_MAX_BYTES) ? "name_too_long" : "unknown";
9
+ }
6
10
  export function classifyFsStderr(stderr, p, fallback = "unknown") {
7
11
  const msg = stderr.trim();
8
12
  if (/command not found|applet not found|:\s+not found\s*$/im.test(msg))
9
13
  return new FileError("unknown", msg, p);
10
- if (/input\/output error|too many levels|file name too long|no space left/i.test(msg))
14
+ if (/file name too long/i.test(msg))
15
+ return new FileError(nameTooLongCode(p), msg, p);
16
+ if (/input\/output error|too many levels|no space left/i.test(msg))
11
17
  return new FileError("unknown", msg, p);
12
18
  if (/no such file/i.test(msg))
13
19
  return new FileError("not_found", msg, p);
@@ -1,4 +1,4 @@
1
- import { FileError, type InheritEnvPolicy, ExecutionError, RemoteExecutionError, SchedulerError, type SchedulerCapability, type ScheduledIntent, type SchedulerContext, type ScheduledTaskId, type ScheduledTaskSummary, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type BoundFileObject, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, BackgroundShellError, type BackgroundShellCapability, type BackgroundShellId, type BackgroundHardKillOutcome, type BackgroundPoll, type BackgroundSpawnOptions } from "@sema-agent/core";
1
+ import { FileError, type InheritEnvPolicy, ExecutionError, RemoteExecutionError, SchedulerError, type SchedulerCapability, type ScheduledIntent, type SchedulerContext, type ScheduledTaskId, type ScheduledTaskSummary, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type BoundFileObject, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, BackgroundShellError, type BackgroundShellCapability, type BackgroundShellId, type BackgroundHardKillOutcome, type BackgroundPoll, type BackgroundSpawnOptions, type SecretEnvFinding } from "@sema-agent/core";
2
2
  /** Options accepted by `ExecutionEnv.exec` (core does not re-export the type; derive it from the seam). */
3
3
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
4
4
  /** SIGTERM → grace → SIGKILL (no host isolation). 🔴 宽限的**唯一属主**:本 driver 的 `kill` 梯子用它排升级定时器;会话级收敛
@@ -71,14 +71,16 @@ 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`。 */
78
79
  readonly capabilities: {
79
80
  isolation: boolean;
80
81
  suspendable: boolean;
81
82
  canonicalPathAuthoritative: boolean;
83
+ sharesProcessTable: boolean;
82
84
  };
83
85
  /** R7 自唤醒: structural delegation to the injected scheduler backend. `hasScheduler(env)` checks BOTH the method
84
86
  * presence (always, since we implement the interface) AND `schedulerCapabilities.supported===true` — which is only
@@ -101,6 +103,12 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
101
103
  * {@link hostShell}(win32 上未落定同步抛 —— 修前每条 Bash 的策略链都因此被拒)。
102
104
  */
103
105
  get cdPath(): readonly string[];
106
+ /**
107
+ * core 7.56.0 [ref]:shell 子进程继承的环境里**收不到**的条目(名 + 规则,从不带值)—— 与 {@link cdPath} 同一只组合的读面
108
+ * (`server-secret-env.ts` 的 `withheldFromHostSubprocess`:继承组合扣下的发现,减去 `cfg.env` 这条显式授予重供的键)。逐命令的 `options.env`
109
+ * 是引擎自己交的显式授予,它知道(core KL-1963-b 同一残余)。只读:不披露、不计数、不碰 shell。
110
+ */
111
+ get withheldEnv(): readonly SecretEnvFinding[];
104
112
  private readonly workspaceDir;
105
113
  private readonly cfg;
106
114
  /** Workspace created on disk (lazily, on first use). */
@@ -11,7 +11,8 @@ import { hostBackgroundShellEnabled, hostExecSpoolEnabled } from "../config.js";
11
11
  import { IS_WIN32, resolveHostShell, hostShell, pinHostShell, spawnGroupOptions, killTreeHard, killTreeSoft, hardKillGroupNow, collapseWin32EnvKeys, adoptHostForegroundChild } from "./host-platform.js";
12
12
  import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
13
13
  import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
14
- import { composeHostSubprocessEnv, hostSubprocessEnv } from "../server-secret-env.js";
14
+ import { composeHostSubprocessEnv, hostSubprocessEnv, withheldFromHostSubprocess } from "../server-secret-env.js";
15
+ import { nameTooLongCode } from "./remote-env-file-error.js";
15
16
  import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
16
17
  const PROVIDER = "host";
17
18
  const DEFAULT_COMMAND_TIMEOUT_MS = 30 * 60_000;
@@ -36,7 +37,7 @@ const UNSUPPORTED_SCHEDULER_CAPS = {
36
37
  maxScheduledHorizonSec: 0,
37
38
  };
38
39
  export class RemoteHostExecutionEnv {
39
- capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true };
40
+ capabilities = { isolation: false, suspendable: false, canonicalPathAuthoritative: true, sharesProcessTable: true };
40
41
  get schedulerCapabilities() {
41
42
  return this.cfg.scheduler?.schedulerCapabilities ?? UNSUPPORTED_SCHEDULER_CAPS;
42
43
  }
@@ -61,6 +62,9 @@ export class RemoteHostExecutionEnv {
61
62
  const value = this.childEnv(undefined, undefined).CDPATH;
62
63
  return value === undefined || value === "" ? [] : value.split(":");
63
64
  }
65
+ get withheldEnv() {
66
+ return withheldFromHostSubprocess({ policy: this.cfg.inheritEnv, allow: [] }, this.cfg.env);
67
+ }
64
68
  workspaceDir;
65
69
  cfg;
66
70
  prepared = false;
@@ -1331,6 +1335,8 @@ export class RemoteHostExecutionEnv {
1331
1335
  return new FileError("not_directory", msg, p);
1332
1336
  if (code === "EINVAL")
1333
1337
  return new FileError("invalid", msg, p);
1338
+ if (code === "ENAMETOOLONG")
1339
+ return new FileError(nameTooLongCode(p), msg, p);
1334
1340
  return new FileError("unknown", msg, p);
1335
1341
  }
1336
1342
  }
@@ -110,9 +110,11 @@ export interface K8sEnvConfig extends LaneHomeConfig {
110
110
  cacheMisses?: CacheMissMemory;
111
111
  /** Operator log seat (the e2b adapter's shape): `workspace_snapshot_failed` / `workspace_snapshot_prune_failed` /
112
112
  * `workspace_space_unavailable` / `workspace_space_unreadable_occupant` / `workspace_commit_abandoned` / `workspace_latest_dangling` /
113
- * `workspace_restore_failed` / `workspace_snapshot_bucket_missing` / `workspace_cache_fresh` / `workspace_cache_unavailable`. */
113
+ * `workspace_restore_failed` / `workspace_snapshot_bucket_missing` / `workspace_cache_fresh` / `workspace_cache_unavailable`, and — at `error` level, on a seat
114
+ * that has one (the deployment's logger does; the seat itself is optional, and the counter behind the line does not depend on it) — `sandbox_pod_delete_failed`. */
114
115
  logger?: {
115
116
  warn(event: string, fields?: Record<string, unknown>): void;
117
+ error?(event: string, fields?: Record<string, unknown>): void;
116
118
  };
117
119
  /** Run-end outcome seat — called once per `destroy()` that attempted a space snapshot (the factory's caller binds it
118
120
  * to the run, so the run's terminal frame can carry `workspaceSnapshot: "failed"`). */
@@ -130,13 +132,39 @@ export interface K8sEnvConfig extends LaneHomeConfig {
130
132
  podSpecPatch?: PodSpecPatch;
131
133
  /** RFC B5 (sema-internal server/docs/design/ENV-SELECT-AND-REGION-SOURCE.md 抽象点②): operator-trusted env injected into the
132
134
  * sandbox container (e.g. `SEMA_PKG_SOURCE=cn|global` — the runtime package-source selector the image's
133
- * pkg-source.sh hook materializes at start). Deployment config, NEVER task-controlled. This is the pod's ONE container-env
134
- * seat: the region package-source set AND the deployer-named forwarding (`K8S_SANDBOX_ENV` keys, original values) arrive
135
- * here already merged by the boot assembly (explicit names win). Values go into `containers[0].env[]` and nowhere else —
136
- * never a command string, the workspace or a snapshot. Absent = the pod is byte-identical to today. */
135
+ * pkg-source.sh hook materializes at start). Deployment config, NEVER task-controlled. The pod's NON-SECRET env seat: the region
136
+ * package-source set (and the cache env layered under it) — values that are no secret go into `containers[0].env[]` as `{ name, value }`.
137
+ * Never a command string, the workspace or a snapshot. Absent = no such entries. */
137
138
  podEnv?: Record<string, string>;
139
+ /** The pod's SECRET env seat: the deployer-named forwarding (`K8S_SANDBOX_ENV` keys, original values; disjoint from {@link podEnv} — the boot
140
+ * assembly drops a collided name from the non-secret seat, explicit wins; a name in both is read here). The values reach the agent container
141
+ * ONLY through this pod's own Secret: the pod spec carries `valueFrom.secretKeyRef` and no value; the Secret is created after the pod, owned by
142
+ * it (`ownerReferences`, `immutable`) — a Secret that cannot be created ⇒ the pod is deleted and the connect refused, never a plaintext fallback.
143
+ * Absent / empty ⇒ no Secret, the pod as without it. */
144
+ podSecretEnv?: Record<string, string>;
145
+ /** The server run that owns this env — the TOP-LEVEL run's id (its task id), handed in by the per-run binding (`boot/execution-env.ts`), which
146
+ * hands none to a delegated child, a run the service starts for itself, or a run without a task id. Carried as the pod label `sema.io/run`
147
+ * (the reaper's key: a pod whose run is over is removed); a value that is no label value is not carried. Absent ⇒ the pod names no run. */
148
+ runId?: string;
149
+ /**
150
+ * The member git-credential broker seat — present ⇔ the per-run binding found the plane on and this run a member's (`sso`) run
151
+ * (`sandbox-git-plane.ts` `sandboxBrokerSeatOf`). The pod then carries the broker container (`containers[1]`: the fixed program of
152
+ * `broker/constants.ts`, image = `image`, the securityContext written key by key, an exec readiness probe), its lease volume (a SECOND
153
+ * per-pod Secret, `sema.io/sandbox-lease` — mutable, owned by the pod, only `broker.json`; created after the pod holding a placeholder lease
154
+ * in which every entry is withdrawn), `shareProcessNamespace: false`, and on the agent container one `GIT_CONFIG_*` insteadOf route per
155
+ * origin (`origins` = the pod's origin table, in order — the same table every lease write is stamped from). The connect waits for the
156
+ * broker container to be ready; any failure on that path deletes the pod and refuses the connect. A deployment patch that could reach
157
+ * the broker refuses the connect before any pod is created. Absent ⇒ the pod as without it (byte-identical).
158
+ */
159
+ sandboxBroker?: {
160
+ readonly image: string;
161
+ readonly origins: readonly string[];
162
+ };
138
163
  }
164
+ import { type SandboxSecretSeatKind, type SandboxSecretVerb } from "./k8s-exec-protocol.js";
139
165
  export { type PodSpecPatch, applyPodSpecPatch } from "./k8s-exec-protocol.js";
166
+ import { type SandboxPodApi, type SandboxPodListing } from "./k8s-pod-reaper.js";
167
+ import { type SandboxLeaseEntryInput } from "../sandbox-git-lease.js";
140
168
  type ConnectResult = {
141
169
  ok: true;
142
170
  value: WorkspaceHandle;
@@ -154,11 +182,14 @@ export interface ExecSocket {
154
182
  * Test seam ([ref] §9.3 fault-injection): inject the REST `request` and/or the exec-socket factory, so pod
155
183
  * lifecycle, retry policy, channel framing and liveness are deterministically testable without a cluster.
156
184
  */
185
+ /** One control-plane request's options: its wall-clock, its abort, and the body's media type (default `application/json`; a merge-patch says so). */
186
+ export interface K8sRequestOptions {
187
+ timeoutMs?: number;
188
+ signal?: AbortSignal;
189
+ contentType?: string;
190
+ }
157
191
  export interface K8sEnvDeps {
158
- request?: (method: string, apiPath: string, body?: unknown, opts?: {
159
- timeoutMs?: number;
160
- signal?: AbortSignal;
161
- }) => Promise<{
192
+ request?: (method: string, apiPath: string, body?: unknown, opts?: K8sRequestOptions) => Promise<{
162
193
  status: number;
163
194
  json: unknown;
164
195
  }>;
@@ -229,6 +260,35 @@ export declare function errorMessageOf(e: unknown): string;
229
260
  export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
230
261
  /** The sandbox namespace when `K8S_NAMESPACE` is unset (the one spelling: the adapter and the boot's egress line read it). */
231
262
  export declare const K8S_SANDBOX_NAMESPACE_DEFAULT = "sandboxes";
263
+ /**
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
+ * 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
266
+ * review itself was refused, the answer had no `status.allowed`) and none was denied.
267
+ */
268
+ export type SandboxSecretSeat = {
269
+ readonly state: "granted";
270
+ } | {
271
+ readonly state: "denied";
272
+ readonly verbs: SandboxSecretVerb[];
273
+ } | {
274
+ readonly state: "unverified";
275
+ readonly detail: string;
276
+ };
277
+ /** Where a pod's lease lives and which origin table it was built with — what {@link writeSandboxLease} needs (read it from {@link RemoteK8sExecutionEnv.sandboxLeaseRef}). */
278
+ export interface SandboxLeaseRef {
279
+ readonly namespace: string;
280
+ readonly podName: string;
281
+ readonly leaseSecretName: string;
282
+ readonly origins: readonly string[];
283
+ }
284
+ /** One lease write's outcome (closed set): written · refused before sending (the broker would refuse it) · refused by the API · the Secret is gone (so is its pod) · any other failure. */
285
+ export type SandboxLeaseWrite = {
286
+ readonly ok: true;
287
+ } | {
288
+ readonly ok: false;
289
+ readonly code: "lease_invalid" | "auth_failed" | "gone" | "write_failed";
290
+ readonly message: string;
291
+ };
232
292
  export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, BackgroundShellCapability {
233
293
  /**
234
294
  * 🔴 **[ref] / core 7.20.0([ref])`canonicalPathAuthoritative: true`** —— 本适配器的 `canonicalPath`
@@ -236,20 +296,24 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
236
296
  * (RPC 断、传输掉),不是「一个它看不见的命名空间」⇒ 读边界的执行期复核**拒**,而不是让词法判决站着。
237
297
  * 缺席是今天每个适配器的读法(整链沉默 ⇒ 词法判决成立、命令照跑),所以这一声明是**收紧**方向。
238
298
  * 严格读法 `=== true`(core `canonicalPathIsAuthoritative`);`isSuspendable` 一族同样严格,对本仓的
239
- * 布尔字面量零影响(三键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
299
+ * 布尔字面量零影响(四键都写死字面 `true`/`false`,不存在「非布尔值被读成缺席」那一形 —— 那一形由
240
300
  * core 的 `config.execution_env_capability_invalid` 普查响亮播报,本仓的受众表已登记)。
241
301
  * 谁该声明由闭集门看着(`test/execution-lane-caps.test.ts` 的逐 lane 对账:新 lane 不表态 = 编译红)。
242
302
  */
303
+ /** `sharesProcessTable`(core 7.57.0,必填):命令跑在沙箱 pod 自己的 PID 命名空间里(不开 hostPID),不在本进程所在的进程表。 */
243
304
  readonly capabilities: {
244
305
  isolation: boolean;
245
306
  suspendable: boolean;
246
307
  canonicalPathAuthoritative: true;
308
+ sharesProcessTable: false;
247
309
  };
248
310
  cwd: string;
249
311
  private podName?;
250
312
  /** Cluster-internal pod address, captured when the pod reaches Running — the LSP `getHost` surface.
251
313
  * Lifecycle mirrors podName exactly (set on connect, cleared on suspend/destroy/dead-reconnect). */
252
314
  private podIP?;
315
+ /** The broker lease seat of the pod this env holds (set with {@link podName}; read through {@link sandboxLeaseRef}). */
316
+ private leaseRef?;
253
317
  private destroyed;
254
318
  /** Set after a successful resumeVM so workspaceHandle() carries the snapshot it was restored from. */
255
319
  private handleSnapshotId?;
@@ -274,6 +338,9 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
274
338
  /** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 pod 里(镜像与 pod spec 的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
275
339
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
276
340
  readonly cdPath: "unknown";
341
+ /** core 7.56.0 [ref]:shell 子进程继承的环境里收不到的条目 —— 本车道 shell 的环境在 pod 里**构建**(本进程的 env 从不注入,只有引擎逐次交的 `env`
342
+ * 这条显式授予),没有一条是从本进程继承的 ⇒ `[]`(core 契约:「`[]` when its shell environment is built rather than inherited」)。 */
343
+ readonly withheldEnv: readonly [];
277
344
  private readonly cfg;
278
345
  private readonly deps;
279
346
  /** The known occupant's incarnation (`undefined` = this run has no known occupant: a delegated child, an unread session, a direct construction) — the
@@ -295,6 +362,8 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
295
362
  private readonly onWorkspaceSnapshot?;
296
363
  private readonly cacheMisses?;
297
364
  constructor(config: K8sEnvConfig, deps?: K8sEnvDeps);
365
+ /** The lease seat of the pod this env holds now — `undefined` without a broker seat or without a pod (the server's lease writes follow it: a resumed env holds a new pod and a new seat). */
366
+ get sandboxLeaseRef(): SandboxLeaseRef | undefined;
298
367
  /** 166-T2 — ExecutionEnv 寿命声明:pod `activeDeadlineSeconds = ceil(timeoutMs/1000)` 是平台真硬死线
299
368
  * (k8s 到点杀 pod,无 keepalive 续期机制)⇒ 恒声明,core 在死线−60s 处 suspend(基建合格)或响亮
300
369
  * 终局(env.lifetime_expired)。`lifetimeStartedAt` 有意不声明:core 只在 prepare 期读一次,lazy-connect
@@ -410,6 +479,15 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
410
479
  * same merge `createPod` applies). A read that is refused / fails / times out is a `unreadable` gap, never a guess. Never throws.
411
480
  */
412
481
  egressPolicyGaps(): Promise<SandboxEgressGap[]>;
482
+ /**
483
+ * The boot reading of one sandbox Secret seat ({@link SandboxSecretSeat}): one SelfSubjectAccessReview per verb of that seat's row in {@link SANDBOX_SECRET_SEAT_VERBS}
484
+ * on `secrets` in this env's namespace. An answer with `status.allowed: false` is a denial (RBAC never answers `denied: true` — "no rule allows
485
+ * it" IS its refusal); a failed request, a refused review or an answer without a boolean `status.allowed` is a verb not asked. The verbs are
486
+ * asked at once (the boot waits one control-plane bound, not four). Never throws.
487
+ */
488
+ secretSeatReading(seat: SandboxSecretSeatKind): Promise<SandboxSecretSeat>;
489
+ /** The sandbox pods of this env's namespace (selected by the identity labels), read for the reaper ({@link createSandboxPodApi}). Throws on a failed / refused listing. */
490
+ listSandboxPods(): Promise<SandboxPodListing[]>;
413
491
  /** The one spelling of "the reclaim call's deadline passed" — both halves of the reclaim (volume and objects) say it with this. */
414
492
  private reclaimDeadlinePassed;
415
493
  /** The cache volume could not be had: counted (`recordFailOpen`) and one operator line — once per env. */
@@ -417,6 +495,34 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
417
495
  /** Create one pod (with the cache volume when given) and wait for it to run. A {@link CacheMiss} = a pod carrying the
418
496
  * volume did not become ready (its claim unbound past the bind deadline, or bound and still not ready) — already deleted. */
419
497
  private createPod;
498
+ /**
499
+ * This pod's own Secret — the secret env seat ({@link K8sEnvConfig.podSecretEnv}): created AFTER the pod, owned by it (`ownerReferences` → the
500
+ * pod's uid, `blockOwnerDeletion: false` — the cluster's garbage collector removes it with the pod), `immutable`, `data["env.<NAME>"]` = the
501
+ * value (base64). The caller deletes the pod on any refusal: no uid to bind (`connect_failed`), refused (401 / 403 ⇒ `auth_failed`, not
502
+ * retried), any other answer or a failed request (`connect_failed`). A refusal never carries a value: the API's text is scrubbed of every value
503
+ * (raw and base64) before it enters the sentence.
504
+ */
505
+ private createPodSecret;
506
+ /**
507
+ * This pod's lease Secret — the member-credential broker's seat ({@link K8sEnvConfig.sandboxBroker}): created after the pod, owned by it
508
+ * (the same `ownerReferences` as the env Secret), NOT immutable (the server rewrites the lease: {@link writeSandboxLease}), one data key
509
+ * `broker.json` = the placeholder lease. Refusals as for the env Secret; the caller deletes the pod on any of them.
510
+ */
511
+ private createLeaseSecret;
512
+ /**
513
+ * One Secret owned by this pod (`ownerReferences` → its uid, `blockOwnerDeletion: false`; the cluster's garbage collector removes it with
514
+ * the pod). No uid to bind ⇒ `connect_failed`; refused (401 / 403) ⇒ `auth_failed`; any other answer or a failed request ⇒ `connect_failed`.
515
+ * A refusal never carries a value: the API's text is scrubbed of every `hidden` string before it enters the sentence.
516
+ */
517
+ private createOwnedSecret;
518
+ /**
519
+ * Rewrite one pod's lease (the server's ONLY lease write — {@link writeSandboxLease}): the file is built by the one lease builder from the
520
+ * pod's origin table (fresh `leaseId` per ready entry, the broker's own parser judges it — a file the broker would refuse is never sent) and
521
+ * lands as a merge-patch of `broker.json` on the pod's lease Secret. Every failure is typed, says one operator line
522
+ * (`sandbox_lease_write_failed`), carries no credential (the API's text is scrubbed of every secret and of the encoded file), and falls
523
+ * back to nothing: the broker keeps the lease it has, which lapses on its own clock.
524
+ */
525
+ writeLease(ref: SandboxLeaseRef, entries: readonly SandboxLeaseEntryInput[]): Promise<SandboxLeaseWrite>;
420
526
  /**
421
527
  * Build a validated, presigned URL for a snapshot object of either lane (see {@link SnapshotTarget}) — the ONE place a
422
528
  * snapshot object key is spelled, and the one place each lane's partition is chosen (park = the session id, space = the
@@ -584,7 +690,20 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
584
690
  error: RemoteExecutionError;
585
691
  }>;
586
692
  reconnect(_sessionToken: SessionToken): Promise<ConnectResult>;
587
- private deletePod;
693
+ /**
694
+ * Delete one pod — the ONE request path of "delete a sandbox pod" (the run-end delete, every refusal / reconnect clean-up, and the reaper's face: no grace).
695
+ * Resolves `"deleted"` (2xx) or `"gone"` (404: the pod never was / is already gone); throws {@link PodDeleteRefused} on any other answer and the request's own error on a transport failure.
696
+ */
697
+ deletePod(name: string): Promise<"deleted" | "gone">;
698
+ /**
699
+ * Give up the pod a refused connect had already created. The refusal is the caller's answer; the delete is the clean-up behind it — and a clean-up that FAILS leaves
700
+ * the pod, and the Secret(s) it owns (the deployer-named env values / the lease placeholder: data at rest that no connection carries), in the sandbox namespace until
701
+ * the pod reaper's backstop reaches them. That is said, not swallowed — through the ONE reporter the reaper's own delete step shares
702
+ * ({@link reportPodDeleteFailed}: the F-class counter `server.sandbox-pod.delete-failed` and one error line naming the pod, the namespace, why the pod was being
703
+ * given up and how the delete failed (`http_<status>` / `request_failed`) — never a value, never the API's own text).
704
+ * Never throws: the refusal the caller goes on to return stays the original one.
705
+ */
706
+ private discardRefusedPod;
588
707
  /**
589
708
  * Delete the sandbox pod. Idempotent, never throws. Space lane open and a pod exists ⇒ FIRST the run-end space
590
709
  * snapshot ({@link persistWorkspace}) — after the pod's processes are confirmed stopped, the commands still in flight having ended or been cut first ({@link quiesce}); from the
@@ -766,6 +885,16 @@ export declare function createSpaceReclaimer(config: K8sEnvConfig, deps?: K8sEnv
766
885
  * nodes that join after boot are read at the next boot. Never throws (a read failure is an `unreadable` gap).
767
886
  */
768
887
  export declare function checkSandboxEgressPolicy(config: K8sEnvConfig, deps?: K8sEnvDeps): Promise<SandboxEgressGap[]>;
888
+ /** The boot reading of one sandbox Secret seat for this k8s config ({@link RemoteK8sExecutionEnv.secretSeatReading}; a control-plane env, no pod). Never throws. */
889
+ export declare function readSandboxSecretSeat(config: K8sEnvConfig, seat: SandboxSecretSeatKind, deps?: K8sEnvDeps): Promise<SandboxSecretSeat>;
890
+ /**
891
+ * The server's one lease writer for a pod that carries the member-credential broker ({@link RemoteK8sExecutionEnv.writeLease}): `ref` is the
892
+ * pod's lease seat ({@link RemoteK8sExecutionEnv.sandboxLeaseRef}), `entries` one per origin of its table. A control-plane env, no pod.
893
+ */
894
+ export declare function writeSandboxLease(config: K8sEnvConfig, ref: SandboxLeaseRef, entries: readonly SandboxLeaseEntryInput[], deps?: K8sEnvDeps): Promise<SandboxLeaseWrite>;
895
+ /** The reaper's narrow face onto the sandbox namespace (list by identity labels, delete one) — a control-plane env, no pod, built at first use
896
+ * (as the space reclaimer builds its envs per call: the boot never constructs an env just to hold this face). */
897
+ export declare function createSandboxPodApi(config: K8sEnvConfig, deps?: K8sEnvDeps): SandboxPodApi;
769
898
  /**
770
899
  * `ExecutionEnvFactory` for the k8s pod-sandbox backend — one fresh (unconnected) env per task; the pod is
771
900
  * created lazily on connect and deleted by `destroy()` (Runner-owned lifetime; `activeDeadlineSeconds` is the