@sema-agent/server 7.130.0 → 7.132.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 (294) hide show
  1. package/MIGRATION.md +396 -0
  2. package/README.md +13 -6
  3. package/README.zh-CN.md +9 -5
  4. package/USAGE.md +187 -50
  5. package/dist/approval-card.d.ts +3 -1
  6. package/dist/approval-card.js +2 -3
  7. package/dist/audit.d.ts +7 -10
  8. package/dist/audit.js +4 -1
  9. package/dist/auth-bridge.d.ts +4 -0
  10. package/dist/auth-bridge.js +10 -11
  11. package/dist/bench/s1/live-deps.js +2 -0
  12. package/dist/boot/deployment-mods.d.ts +31 -0
  13. package/dist/boot/deployment-mods.js +26 -0
  14. package/dist/boot/execution-env.d.ts +1 -1
  15. package/dist/boot/execution-env.js +19 -6
  16. package/dist/boot/memory-boundary.d.ts +2 -2
  17. package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
  18. package/dist/boot/read-deny-anchor-audit.js +36 -0
  19. package/dist/boot/resolve-spec.d.ts +1 -5
  20. package/dist/boot/resolve-spec.js +56 -61
  21. package/dist/boot/runner-deps.d.ts +19 -8
  22. package/dist/boot/runner-deps.js +14 -2
  23. package/dist/boot/session-faces.d.ts +1 -7
  24. package/dist/boot/stage-02-web-admin-pull.js +9 -1
  25. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  26. package/dist/boot/stage-03-store-backend.js +2 -2
  27. package/dist/boot/stage-04-stores.d.ts +1 -1
  28. package/dist/boot/stage-05-execution-env.d.ts +4 -4
  29. package/dist/boot/stage-05-execution-env.js +7 -10
  30. package/dist/boot/stage-06-runners.d.ts +2 -2
  31. package/dist/boot/stage-06-runners.js +5 -4
  32. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  33. package/dist/boot/stage-07-capability-layer.js +3 -3
  34. package/dist/boot/stage-08-reapers.d.ts +2 -2
  35. package/dist/boot/stage-09-leader.d.ts +3 -9
  36. package/dist/boot/stage-10-http-server.d.ts +3 -9
  37. package/dist/boot/stage-10-http-server.js +12 -10
  38. package/dist/boot/stores.d.ts +1 -1
  39. package/dist/boot/stores.js +5 -6
  40. package/dist/boot/web-admin.d.ts +0 -3
  41. package/dist/boot/web-admin.js +35 -35
  42. package/dist/brain.js +17 -22
  43. package/dist/budget.js +5 -4
  44. package/dist/caller-text-echo.d.ts +20 -0
  45. package/dist/caller-text-echo.js +24 -0
  46. package/dist/capabilities/brand-identity.d.ts +26 -0
  47. package/dist/capabilities/brand-identity.js +27 -0
  48. package/dist/capabilities/builtin-tools.js +2 -1
  49. package/dist/capabilities/center-plugins.js +1 -1
  50. package/dist/capabilities/center-prompts.d.ts +32 -59
  51. package/dist/capabilities/center-prompts.js +18 -32
  52. package/dist/capabilities/composite-members.d.ts +1 -0
  53. package/dist/capabilities/composite-members.js +1 -0
  54. package/dist/capabilities/engine-capabilities.js +2 -1
  55. package/dist/capabilities/hands-lane.d.ts +9 -6
  56. package/dist/capabilities/hands-lane.js +3 -3
  57. package/dist/capabilities/host-tool-names.d.ts +17 -0
  58. package/dist/capabilities/host-tool-names.js +15 -0
  59. package/dist/capabilities/mcp-probe.d.ts +14 -1
  60. package/dist/capabilities/mcp-probe.js +22 -133
  61. package/dist/capabilities/repo-tools.js +4 -3
  62. package/dist/capabilities/scenarios.d.ts +42 -47
  63. package/dist/capabilities/scenarios.js +19 -35
  64. package/dist/capabilities/select-environment-tool.js +2 -1
  65. package/dist/capabilities/send-user-file-tool.d.ts +0 -1
  66. package/dist/capabilities/send-user-file-tool.js +2 -2
  67. package/dist/capabilities/skills.d.ts +19 -4
  68. package/dist/capabilities/skills.js +30 -10
  69. package/dist/capabilities/tool-defer.js +2 -1
  70. package/dist/config-catalog.js +22 -11
  71. package/dist/config-invariants.js +2 -2
  72. package/dist/config-lkg.d.ts +9 -9
  73. package/dist/config-lkg.js +20 -4
  74. package/dist/config-provider.d.ts +3 -1
  75. package/dist/config-provider.js +14 -6
  76. package/dist/config-types.d.ts +13 -9
  77. package/dist/config.d.ts +2 -2
  78. package/dist/config.js +34 -20
  79. package/dist/device-ws-protocol.d.ts +1 -1
  80. package/dist/device-ws-protocol.js +1 -0
  81. package/dist/elicitation.d.ts +7 -0
  82. package/dist/elicitation.js +20 -5
  83. package/dist/env-name-allowlist-knobs.d.ts +12 -2
  84. package/dist/env-name-allowlist-knobs.js +5 -4
  85. package/dist/exec-bounds.d.ts +15 -3
  86. package/dist/exec-bounds.js +4 -0
  87. package/dist/execution-lane-caps.d.ts +9 -2
  88. package/dist/execution-lane-caps.js +11 -0
  89. package/dist/hooks/classic-stations.d.ts +86 -0
  90. package/dist/hooks/classic-stations.js +27 -0
  91. package/dist/hooks/hook-llm.d.ts +8 -5
  92. package/dist/hooks/hook-llm.js +80 -37
  93. package/dist/hooks/hook-runner.d.ts +273 -75
  94. package/dist/hooks/hook-runner.js +293 -317
  95. package/dist/http/admission.d.ts +8 -2
  96. package/dist/http/admission.js +103 -59
  97. package/dist/http/approval-sweeps.js +59 -18
  98. package/dist/http/checkpoint-ctx.d.ts +36 -18
  99. package/dist/http/checkpoint-ctx.js +14 -10
  100. package/dist/http/claimed-run.d.ts +13 -0
  101. package/dist/http/claimed-run.js +15 -0
  102. package/dist/http/device-placement.js +5 -10
  103. package/dist/http/dispatch.d.ts +0 -4
  104. package/dist/http/dispatch.js +6 -10
  105. package/dist/http/durable-run-leg.d.ts +2 -1
  106. package/dist/http/durable-run-leg.js +5 -3
  107. package/dist/http/health-self-report.d.ts +7 -5
  108. package/dist/http/health-self-report.js +4 -18
  109. package/dist/http/principal-gate.d.ts +23 -6
  110. package/dist/http/principal-gate.js +24 -5
  111. package/dist/http/refusal-body.d.ts +21 -0
  112. package/dist/http/refusal-body.js +4 -0
  113. package/dist/http/request-guards.d.ts +27 -0
  114. package/dist/http/request-guards.js +24 -3
  115. package/dist/http/resume-legs.d.ts +25 -0
  116. package/dist/http/resume-legs.js +48 -26
  117. package/dist/http/routes/approvals-assistant.js +1 -1
  118. package/dist/http/routes/capabilities.js +7 -17
  119. package/dist/http/routes/diagnostics.js +1 -1
  120. package/dist/http/routes/rules.js +2 -2
  121. package/dist/http/routes/runs.js +13 -17
  122. package/dist/http/routes/session-sync.js +4 -2
  123. package/dist/http/routes/sessions.js +23 -11
  124. package/dist/http/routes/shared-memory.js +2 -13
  125. package/dist/http/routes/side-query.js +2 -3
  126. package/dist/http/routes/tasks.js +34 -19
  127. package/dist/http/scenario-read-face.d.ts +14 -0
  128. package/dist/http/scenario-read-face.js +5 -0
  129. package/dist/http/send.d.ts +13 -1
  130. package/dist/http/send.js +12 -1
  131. package/dist/http/server.d.ts +37 -23
  132. package/dist/http/server.js +30 -4
  133. package/dist/http/stored-body-replay.d.ts +10 -3
  134. package/dist/http/stored-body-replay.js +27 -6
  135. package/dist/http/submit-ctx.d.ts +25 -0
  136. package/dist/http/submit-ctx.js +39 -0
  137. package/dist/http/wire-types.d.ts +18 -5
  138. package/dist/index.d.ts +3 -2
  139. package/dist/index.js +3 -2
  140. package/dist/lane-home-probe.d.ts +43 -0
  141. package/dist/lane-home-probe.js +35 -0
  142. package/dist/limit-refusal.d.ts +47 -0
  143. package/dist/limit-refusal.js +12 -0
  144. package/dist/main.js +1 -1
  145. package/dist/mcp-assertion-header.d.ts +3 -11
  146. package/dist/mcp-assertion-header.js +3 -9
  147. package/dist/mcp-tool-faces.d.ts +5 -0
  148. package/dist/mcp-tool-faces.js +5 -1
  149. package/dist/memory-scope.d.ts +1 -1
  150. package/dist/model-entry-refusal.d.ts +1 -7
  151. package/dist/model-provider.d.ts +39 -12
  152. package/dist/model-provider.js +19 -2
  153. package/dist/model-route-endpoint.d.ts +52 -12
  154. package/dist/model-route-endpoint.js +48 -26
  155. package/dist/model-route-gap.d.ts +5 -4
  156. package/dist/model-route-gap.js +1 -1
  157. package/dist/model-select.d.ts +58 -0
  158. package/dist/model-select.js +31 -0
  159. package/dist/namespaced-rule-name.d.ts +7 -4
  160. package/dist/namespaced-rule-name.js +4 -3
  161. package/dist/observability/cost-taxonomy.d.ts +7 -7
  162. package/dist/observability/fail-open.d.ts +53 -12
  163. package/dist/observability/fail-open.js +47 -7
  164. package/dist/observability/metrics.js +3 -1
  165. package/dist/observability/prompt-manifest.d.ts +47 -82
  166. package/dist/observability/prompt-manifest.js +119 -17
  167. package/dist/observability/run-terminal.js +14 -6
  168. package/dist/observability/secret-env-scrub.d.ts +13 -1
  169. package/dist/observability/secret-env-scrub.js +13 -1
  170. package/dist/observability/tool-trace.d.ts +16 -21
  171. package/dist/observability/tool-trace.js +51 -42
  172. package/dist/per-task-image.d.ts +7 -1
  173. package/dist/per-task-image.js +3 -3
  174. package/dist/plain-string-record.d.ts +25 -0
  175. package/dist/plain-string-record.js +32 -0
  176. package/dist/plugins/blob-backend.d.ts +68 -13
  177. package/dist/plugins/blob-backend.js +53 -10
  178. package/dist/plugins/checkpoint-store-sql.d.ts +14 -4
  179. package/dist/plugins/checkpoint-store-sql.js +39 -26
  180. package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
  181. package/dist/plugins/k8s-bg-scripts.js +22 -0
  182. package/dist/plugins/local-checkpoint-store.js +5 -2
  183. package/dist/plugins/local-session-store.js +3 -3
  184. package/dist/plugins/one-time-migrations.d.ts +147 -91
  185. package/dist/plugins/one-time-migrations.js +372 -208
  186. package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
  187. package/dist/plugins/permission-rule-store-sql.js +4 -18
  188. package/dist/plugins/pg-session-storage.d.ts +4 -5
  189. package/dist/plugins/pg-session-storage.js +7 -17
  190. package/dist/plugins/remote-env-adb.d.ts +6 -0
  191. package/dist/plugins/remote-env-adb.js +9 -4
  192. package/dist/plugins/remote-env-device.js +3 -0
  193. package/dist/plugins/remote-env-e2b.d.ts +1 -1
  194. package/dist/plugins/remote-env-e2b.js +21 -9
  195. package/dist/plugins/remote-env-host.d.ts +9 -9
  196. package/dist/plugins/remote-env-host.js +10 -32
  197. package/dist/plugins/remote-env-k8s.d.ts +95 -26
  198. package/dist/plugins/remote-env-k8s.js +265 -90
  199. package/dist/plugins/remote-env-local-docker.d.ts +20 -1
  200. package/dist/plugins/remote-env-local-docker.js +77 -9
  201. package/dist/plugins/remote-env-ssh.d.ts +15 -0
  202. package/dist/plugins/remote-env-ssh.js +109 -3
  203. package/dist/plugins/remote-shell.d.ts +0 -1
  204. package/dist/plugins/remote-shell.js +1 -2
  205. package/dist/plugins/retention-store-sql.js +6 -2
  206. package/dist/plugins/schema-consistency.d.ts +54 -0
  207. package/dist/plugins/schema-consistency.js +52 -0
  208. package/dist/plugins/session-epoch-anchor.d.ts +13 -0
  209. package/dist/plugins/session-epoch-anchor.js +23 -0
  210. package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
  211. package/dist/plugins/shared-memory-store-sql.js +5 -16
  212. package/dist/plugins/sql-driver.d.ts +141 -0
  213. package/dist/plugins/sql-driver.js +115 -1
  214. package/dist/plugins/sql-errors.d.ts +41 -0
  215. package/dist/plugins/sql-errors.js +36 -0
  216. package/dist/plugins/store-backend.js +2 -1
  217. package/dist/plugins/tidb-session-storage.d.ts +7 -11
  218. package/dist/plugins/tidb-session-storage.js +6 -16
  219. package/dist/plugins/tidb-session-store.js +1 -1
  220. package/dist/prompts-domain-validate.d.ts +17 -61
  221. package/dist/prompts-domain-validate.js +9 -119
  222. package/dist/question.d.ts +18 -9
  223. package/dist/question.js +21 -13
  224. package/dist/request-key-closure.d.ts +4 -4
  225. package/dist/request-key-closure.js +18 -13
  226. package/dist/rule-entry-list.d.ts +134 -24
  227. package/dist/rule-entry-list.js +162 -30
  228. package/dist/run-local.d.ts +3 -3
  229. package/dist/run-local.js +26 -25
  230. package/dist/runs.d.ts +48 -2
  231. package/dist/runs.js +61 -2
  232. package/dist/server-secret-env.d.ts +44 -18
  233. package/dist/server-secret-env.js +49 -8
  234. package/dist/session-incarnation.d.ts +15 -5
  235. package/dist/session-incarnation.js +16 -9
  236. package/dist/session-titler.js +12 -2
  237. package/dist/spec-fields.d.ts +34 -18
  238. package/dist/spec-fields.js +46 -53
  239. package/dist/sql-engine-posture.d.ts +29 -4
  240. package/dist/sql-engine-posture.js +36 -1
  241. package/dist/stored-request-hooks-m20.d.ts +23 -0
  242. package/dist/stored-request-hooks-m20.js +124 -0
  243. package/dist/stored-request-tool-axes-m19.d.ts +20 -5
  244. package/dist/stored-request-tool-axes-m19.js +30 -2
  245. package/dist/task-a2a.d.ts +9 -1
  246. package/dist/task-a2a.js +73 -47
  247. package/dist/task-mcp.d.ts +54 -102
  248. package/dist/task-mcp.js +194 -92
  249. package/dist/task-settings.d.ts +47 -25
  250. package/dist/task-settings.js +46 -61
  251. package/dist/text-bidi.d.ts +3 -46
  252. package/dist/text-bidi.js +3 -3
  253. package/dist/tool-approval.d.ts +1 -1
  254. package/dist/trace/core-keyset-guard.d.ts +24 -4
  255. package/dist/trace/engine-notice-wire.d.ts +11 -3
  256. package/dist/trace/engine-notice-wire.js +8 -1
  257. package/dist/trace/ledger-events.d.ts +3 -1
  258. package/dist/trace/ledger-sink.d.ts +29 -0
  259. package/dist/trace/ledger-sink.js +15 -1
  260. package/dist/trace/model-segment.js +1 -1
  261. package/dist/trace/project.d.ts +25 -6
  262. package/dist/trace/project.js +68 -22
  263. package/dist/trace/wire-projection-faces.d.ts +80 -2
  264. package/dist/trace/wire-projection-faces.js +23 -1
  265. package/dist/user-authored-text.d.ts +7 -0
  266. package/dist/user-authored-text.js +3 -0
  267. package/dist/web-admin-client/apply-effective.js +10 -5
  268. package/dist/web-admin-client/candidate-gate.d.ts +6 -6
  269. package/dist/web-admin-client/candidate-gate.js +3 -3
  270. package/dist/web-admin-client/http-client.js +0 -6
  271. package/dist/web-admin-client/read-warnings.d.ts +13 -0
  272. package/dist/web-admin-client/read-warnings.js +10 -0
  273. package/dist/web-admin-client/skills-mcp.d.ts +3 -0
  274. package/dist/web-admin-client/skills-mcp.js +9 -6
  275. package/dist/web-admin-client/types.d.ts +3 -4
  276. package/dist/workspace-cache.d.ts +35 -0
  277. package/dist/workspace-cache.js +36 -0
  278. package/dist/workspace-space.d.ts +3 -2
  279. package/dist/workspace-space.js +2 -0
  280. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +8 -1
  281. package/package.json +4 -4
  282. package/scripts/drain-check-local.mjs +37 -3
  283. package/scripts/drain-check.mysql.sql +30 -2
  284. package/scripts/drain-check.pg.sql +29 -2
  285. package/dist/capabilities/memory-notice.d.ts +0 -138
  286. package/dist/capabilities/memory-notice.js +0 -64
  287. package/dist/capabilities/prompt.d.ts +0 -17
  288. package/dist/capabilities/prompt.js +0 -6
  289. package/dist/mcp-content-origin.d.ts +0 -34
  290. package/dist/mcp-content-origin.js +0 -10
  291. package/dist/tool-axis-words.d.ts +0 -27
  292. package/dist/tool-axis-words.js +0 -9
  293. package/dist/wire-name-bound.d.ts +0 -14
  294. package/dist/wire-name-bound.js +0 -13
@@ -437,13 +437,17 @@ export interface UnreadableRuleBucket {
437
437
  readonly ownerKey: string;
438
438
  readonly why: string;
439
439
  }
440
- /** M11 一次跑完的读数。 */
441
- export interface UndurableScopePurge {
442
- readonly removed: readonly UndurableScopeRuleRow[];
440
+ /**
441
+ * M11 写进的读数账 —— 三格与一次性迁移段那一只收据账(`one-time-migrations.ts` 的 `MigrationReceipts`)上的同名三格**同名同形**,迁移段把它的账直接递进来
442
+ * (S-1594:一次起服一只收据账,M11 不另起第二套):删掉的行在那只桶的回写**提交的那一刻**追加,之后任何一步失败,拒因随账带走它们。
443
+ */
444
+ export interface UndurableScopePurgeReadings {
445
+ /** 删掉的行(一行一条读数;每只桶的回写提交之后才追加)。 */
446
+ readonly undurableScopeRuleRows: UndurableScopeRuleRow[];
443
447
  /** 连续争不过在线写者(OCC 每次都输)而留到下一次起服的桶数。那些桶里的坏行这期间照旧由引擎读侧丢掉并告知。 */
444
- readonly bucketsDeferred: number;
448
+ undurableScopeRuleBucketsDeferred: number;
445
449
  /** 读腿整桶拒的桶(形 / 指纹 / behavior 闭集 / 墓碑规范文本 / 单一验证器):一个字节不动,逐桶交出拒因,等人处置。 */
446
- readonly bucketsUnreadable: readonly UnreadableRuleBucket[];
450
+ readonly undurableScopeRuleBucketsUnreadable: UnreadableRuleBucket[];
447
451
  }
448
452
  /** {@link purgeUndurableScopeRows} 要的最小查询口(一次性迁移段的 `MigrationDb` 同形;占位符按方言由本函数写)。 */
449
453
  export interface UndurableScopePurgeDb {
@@ -458,13 +462,15 @@ export interface UndurableScopePurgeDb {
458
462
  * 而撤销口删不动它(引擎的写臂对这种 scope 的墓碑同样拒)。⇒ 一次性迁移,不留「撤销口对坏 scope 另开一条读法」的长期双读。
459
463
  *
460
464
  * 读法与写法都是**店的那一只**:每只桶先过唯一判官 {@link decodeBucketRow}(读腿用的同一只),判官整桶拒 ⇒ 这只桶**一个字节不动**,
461
- * 进 `bucketsUnreadable`(桶键 + 不带行内容的拒因;读腿对它的整桶拒仍是它的属主,出路是人);判官放行 ⇒ 在那份合法状态上筛 scope,
465
+ * 进 `undurableScopeRuleBucketsUnreadable`(桶键 + 不带行内容的拒因;读腿对它的整桶拒仍是它的属主,出路是人);判官放行 ⇒ 在那份合法状态上筛 scope,
462
466
  * 经写臂的同一只 {@link writeBucket} 整桶 OCC 回写(`WHERE owner_key = ? AND rev = ?`,rev+1,落盘字节与指纹同写臂)—— 不碰别的桶。
463
467
  * **硬删、不铸墓碑**:墓碑的作用是挡同步回灌,而引擎的 sync-join 对 scope 不合法的入站行本来就丢(`applySyncJoin`),
464
468
  * 本店也不实现同步(见 `apply` 的 sync-join 臂)。
465
469
  * 幂等:第二跑零命中零写(读不出的桶每一跑照报,直到有人处置)。
470
+ * 读数写进调用方递进来的账(`into`,{@link UndurableScopePurgeReadings}):删掉的行在那只桶的回写提交之后追加 ⇒ 本函数半途抛出时,已删的行已经在账上;
471
+ * 错误**原样**上抛(不加工、不自铸拒因句 —— 收据由迁移段那一只收据账随拒因带走,S-1594)。
466
472
  */
467
- export declare function purgeUndurableScopeRows(db: UndurableScopePurgeDb, dialect: "tidb" | "pg", now?: () => number): Promise<UndurableScopePurge>;
473
+ export declare function purgeUndurableScopeRows(db: UndurableScopePurgeDb, dialect: "tidb" | "pg", into: UndurableScopePurgeReadings, now?: () => number): Promise<void>;
468
474
  /** 三个 SQL 面的一次性装配束(one driver, three faces)。`durable` = [ref] 的 durable 分区后端;
469
475
  * 引擎接缝上的统一店由 `main.ts` 用 `createPermissionRuleStoreProvider({ durable })` 合成一次。 */
470
476
  export interface PermissionRuleStores {
@@ -783,21 +783,7 @@ export const RULE_PENDING_APPROVAL_RETENTION_MS = 24 * 60 * 60_000;
783
783
  export const RULE_REAP_BATCH = 500;
784
784
  const UNDURABLE_PURGE_PAGE = 200;
785
785
  const UNDURABLE_PURGE_OCC_ATTEMPTS = 5;
786
- export async function purgeUndurableScopeRows(db, dialect, now = Date.now) {
787
- const acc = { removed: [], bucketsDeferred: 0, bucketsUnreadable: [] };
788
- try {
789
- await purgeAllBuckets(db, dialect, now, acc);
790
- }
791
- catch (e) {
792
- if (acc.removed.length === 0)
793
- throw e;
794
- throw new Error(`the one-time removal of persisted permission rules whose scope the engine refuses failed AFTER removing ${acc.removed.length} row(s) — record them before retrying: ` +
795
- acc.removed.map((r) => `${r.kind} in bucket ${r.ownerKey} (${r.behavior} ${r.tool}, scope root ${JSON.stringify(r.scopeRoot)})`).join("; ") +
796
- `. Underlying failure: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
797
- }
798
- return acc;
799
- }
800
- async function purgeAllBuckets(db, dialect, now, acc) {
786
+ export async function purgeUndurableScopeRows(db, dialect, into, now = Date.now) {
801
787
  const cols = `owner_key, ${bucketRowColumns(dialect)}`;
802
788
  let after = "";
803
789
  for (;;) {
@@ -813,11 +799,11 @@ async function purgeAllBuckets(db, dialect, now, acc) {
813
799
  const outcome = await purgeOneBucket(db, dialect, ownerKey, row, now);
814
800
  switch (outcome.kind) {
815
801
  case "purged":
816
- acc.removed.push(...outcome.rows);
802
+ into.undurableScopeRuleRows.push(...outcome.rows);
817
803
  settled = true;
818
804
  break;
819
805
  case "unreadable":
820
- acc.bucketsUnreadable.push({ ownerKey, why: outcome.why });
806
+ into.undurableScopeRuleBucketsUnreadable.push({ ownerKey, why: outcome.why });
821
807
  settled = true;
822
808
  break;
823
809
  case "clean":
@@ -835,7 +821,7 @@ async function purgeAllBuckets(db, dialect, now, acc) {
835
821
  }
836
822
  }
837
823
  if (!settled)
838
- acc.bucketsDeferred += 1;
824
+ into.undurableScopeRuleBucketsDeferred += 1;
839
825
  }
840
826
  if (page.length < UNDURABLE_PURGE_PAGE)
841
827
  break;
@@ -1,4 +1,4 @@
1
- import { BaseSessionStorage, type PromptEpochDescriptor, type AcquiredSession, type PlacedSessionRow, type SessionMetadata, type SessionPlacement, type SessionPlacementRecord, type SessionStore, type SessionTreeEntry, type SessionWriteOptions } from "@sema-agent/core";
1
+ import { BaseSessionStorage, type AcquiredSession, type PlacedSessionRow, type SessionMetadata, type SessionPlacement, type SessionPlacementRecord, type SessionStore, type SessionTreeEntry, type SessionWriteOptions } from "@sema-agent/core";
2
2
  import type { Pool, PoolClient } from "pg";
3
3
  import type { SessionSummary } from "./store-contracts.js";
4
4
  import { type SessionRowPatch } from "../session-row.js";
@@ -30,10 +30,9 @@ export declare class PgSessionStorage extends BaseSessionStorage {
30
30
  private readonly sessionId;
31
31
  private readonly floorSeq;
32
32
  constructor(pool: Pool, sessionId: string, metadata: SessionMetadata, entries: SessionTreeEntry[], leafId: string | null, floorEntryId?: string | null, floorSeq?: number);
33
- /** @see TiDBSessionStorage.getEpochAnchor — [ref] S3 [ref] pull seam, PG dialect twin
34
- * (keyset-paged to exhaustion: a bounded scan over restatement-less pre-epoch compactions would
35
- * misreport an epoch-bearing session as pre-epoch — codex HIGH). */
36
- getEpochAnchor(): Promise<PromptEpochDescriptor | undefined>;
33
+ /** @see TiDBSessionStorage.getEpochAnchor — [ref] S3 [ref] pull seam, PG dialect twin: the same reading
34
+ * ({@link scanEpochAnchor}, the record as stored), this backend's page query. */
35
+ getEpochAnchor(): Promise<unknown>;
37
36
  /** @see TiDBSessionStorage.wake — F3 bounded `[floor..leaf]` rebuild; null if the session does not exist. */
38
37
  static wake(pool: Pool, sessionId: string): Promise<PgSessionStorage | null>;
39
38
  /** The bounded-window floor = last compaction's firstKeptEntryId (and its seq). @see TiDBSessionStorage.computeFloor */
@@ -1,8 +1,9 @@
1
1
  import { emitLeafAdvance } from "../session-leaf-bus.js";
2
- import { BaseSessionStorage, StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, normalizePromptEpoch, } from "@sema-agent/core";
2
+ import { BaseSessionStorage, StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, } from "@sema-agent/core";
3
3
  import { SESSION_TITLE_MAX_CHARS } from "../session-row.js";
4
4
  import { escapeLike } from "./sql-escape.js";
5
5
  import { decodeSessionEventPayload } from "./session-event-payload.js";
6
+ import { scanEpochAnchor } from "./session-epoch-anchor.js";
6
7
  import { jsonColumnText } from "./sql-json-column.js";
7
8
  import { pgSafeJsonStringify } from "./pg-safe-json.js";
8
9
  import { toIso } from "./sql-row-helpers.js";
@@ -82,21 +83,10 @@ export class PgSessionStorage extends BaseSessionStorage {
82
83
  this.floorSeq = floorSeq;
83
84
  }
84
85
  async getEpochAnchor() {
85
- if (this.floorSeq <= 0)
86
- return undefined;
87
- let cursor = this.floorSeq;
88
- for (;;) {
89
- const res = await this.pool.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 AND seq < $2 AND type IN ('prompt_epoch','compaction') ORDER BY seq DESC LIMIT 100`, [this.sessionId, cursor]);
90
- if (res.rows.length === 0)
91
- return undefined;
92
- for (const r of res.rows) {
93
- const e = decodeSessionEventPayload(r.payload, this.sessionId, r.seq);
94
- const d = e.type === "prompt_epoch" ? normalizePromptEpoch(e) : normalizePromptEpoch(e.details?.promptEpoch);
95
- if (d)
96
- return d;
97
- }
98
- cursor = Number(res.rows[res.rows.length - 1].seq);
99
- }
86
+ return scanEpochAnchor(this.sessionId, this.floorSeq, async (beforeSeq) => {
87
+ const res = await this.pool.query(`SELECT seq, ${jsonColumnText("pg", "payload")} FROM session_event WHERE session_id = $1 AND seq < $2 AND type IN ('prompt_epoch','compaction') ORDER BY seq DESC LIMIT 100`, [this.sessionId, beforeSeq]);
88
+ return res.rows;
89
+ });
100
90
  }
101
91
  static async wake(pool, sessionId) {
102
92
  const metaRes = await pool.query(`SELECT created_at, leaf_id, leaf_seq, incarnation, ${PLACEMENT_SELECT_COLUMNS} FROM session_meta WHERE session_id = $1`, [sessionId]);
@@ -516,7 +506,7 @@ export class PgSessionStore {
516
506
  }
517
507
  await conn.query("DELETE FROM session_event WHERE session_id = $1", [sessionId]);
518
508
  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]);
519
- await this.insertValidatedLog(conn, sessionId, owner, validated, carriedIncarnation(placedRes.rows[0], owner));
509
+ await this.insertValidatedLog(conn, sessionId, owner, validated, carriedIncarnation(sessionId, placedRes.rows[0], owner));
520
510
  await conn.query("COMMIT");
521
511
  }
522
512
  catch (err) {
@@ -204,6 +204,12 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
204
204
  suffix?: string;
205
205
  abortSignal?: AbortSignal;
206
206
  }): Promise<Result<string, FileError>>;
207
+ /**
208
+ * 交给宿主 `adb` CLI 的 env —— 宿主子进程 env 的唯一组合(`server-secret-env.ts` `hostSubprocessEnv`),继承词 `"all"`:本服务自有凭据(起服声明的名与值)
209
+ * 任何词都不交,adb 自己要读的变量(`ADB_VENDOR_KEYS` / `ANDROID_*` …)照交 —— 形状剥会剥 `ADB_VENDOR_KEYS`(名形),设备鉴权因此断。首次调用拉起的常驻 adb server
210
+ * 继承的就是这一份。命令本身跑在设备的 shell 里,本 env 到不了设备。每次起进程现算一次(与 host shell 同)。
211
+ */
212
+ private cliEnv;
207
213
  /** Spawn the adb binary with the given args; collect stdout/stderr (+ raw bytes when binary), bound by timeout/abort. */
208
214
  private adb;
209
215
  /**
@@ -2,9 +2,10 @@ import path from "node:path";
2
2
  import os from "node:os";
3
3
  import fs from "node:fs/promises";
4
4
  import { spawn } from "node:child_process";
5
+ import { hostSubprocessEnv } from "../server-secret-env.js";
5
6
  import { buildCwdEnvCommand, createCwdGuardedCall, shellQuote, armPipeDestroyGrace } from "./remote-shell.js";
6
7
  import { signalNumber } from "./host-platform.js";
7
- import { commandBoundsOf, livenessCutMessage } from "../exec-bounds.js";
8
+ import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
8
9
  import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
9
10
  import { classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
10
11
  import { createPosixShellFs } from "./posix-shell-fs.js";
@@ -180,7 +181,7 @@ export class RemoteAdbExecutionEnv {
180
181
  if (this.destroyed)
181
182
  throw new RemoteExecutionError("connect_failed", "execution env already destroyed");
182
183
  const wrapped = buildCwdEnvCommand(command, this.workingDirOf(options?.cwd), options?.env, "raw").command;
183
- const child = this.spawnFn(this.cfg.adbPath, this.deviceShellArgs(wrapped), { stdio: ["ignore", "pipe", "pipe"] });
184
+ const child = this.spawnFn(this.cfg.adbPath, this.deviceShellArgs(wrapped), { stdio: ["ignore", "pipe", "pipe"], env: this.cliEnv() });
184
185
  const queue = [];
185
186
  let finished = false;
186
187
  let failure;
@@ -276,7 +277,8 @@ export class RemoteAdbExecutionEnv {
276
277
  clearTimeout(waitTimer);
277
278
  }
278
279
  if (liveness !== undefined && !finished && Date.now() - lastChunk >= liveness.ms) {
279
- throw new RemoteExecutionError("timeout", livenessCutMessage(liveness));
280
+ const cut = buildLivenessCut(liveness);
281
+ throw new RemoteExecutionError(cut.code, cut.message);
280
282
  }
281
283
  }
282
284
  while (queue.length)
@@ -417,13 +419,16 @@ export class RemoteAdbExecutionEnv {
417
419
  async createTempFile(options) {
418
420
  return this.posixFs.createTempFile(options);
419
421
  }
422
+ cliEnv() {
423
+ return hostSubprocessEnv({ site: "server.remote-env-adb.spawn-env", policy: "all", allow: [] });
424
+ }
420
425
  adb(args, opts) {
421
426
  if (this.destroyed)
422
427
  return Promise.resolve({ ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") });
423
428
  return new Promise((resolve) => {
424
429
  let settled = false;
425
430
  let forceSettleTimer;
426
- const child = this.spawnFn(this.cfg.adbPath, args, { stdio: ["ignore", "pipe", "pipe"] });
431
+ const child = this.spawnFn(this.cfg.adbPath, args, { stdio: ["ignore", "pipe", "pipe"], env: this.cliEnv() });
427
432
  const killGrace = () => {
428
433
  try {
429
434
  child.kill("SIGKILL");
@@ -12,6 +12,7 @@ export const DEVICE_OUTCOME_TO_FILE_ERROR = {
12
12
  callback_error: "unknown",
13
13
  invalid: "invalid",
14
14
  is_directory: "is_directory",
15
+ liveness_no_output: "unknown",
15
16
  not_directory: "not_directory",
16
17
  not_found: "not_found",
17
18
  not_supported: "not_supported",
@@ -33,6 +34,7 @@ export const DEVICE_OUTCOME_TO_EXEC_ERROR = {
33
34
  callback_error: "callback_error",
34
35
  invalid: "unknown",
35
36
  is_directory: "unknown",
37
+ liveness_no_output: "liveness_no_output",
36
38
  not_directory: "unknown",
37
39
  not_found: "unknown",
38
40
  not_supported: "shell_unavailable",
@@ -763,6 +765,7 @@ const OUTCOME_TO_REMOTE_ERROR = {
763
765
  callback_error: "unknown",
764
766
  invalid: "unknown",
765
767
  is_directory: "unknown",
768
+ liveness_no_output: "liveness_no_output",
766
769
  not_directory: "unknown",
767
770
  not_found: "unknown",
768
771
  not_supported: "unsupported",
@@ -321,7 +321,7 @@ export declare class RemoteContainerExecutionEnv implements RemoteExecutionEnv,
321
321
  * a command that hangs DURING execution (not creation) would otherwise wait the E2B command timeout (= the
322
322
  * lifetime when none is set, the original hang bug). With a `liveness` bound (the caller stated no `timeout` —
323
323
  * `exec-bounds.ts`) it races wait() against an idle check: no stdout/stderr for `liveness.ms` ⇒ hung → kill + typed
324
- * retryable "timeout" with the shared cut sentence. `lastProgress()` is bumped by the stdout/stderr callbacks, so a
324
+ * retryable `liveness_no_output` (the shared cut: code, sentence and the window in whole seconds — `buildLivenessCut`). `lastProgress()` is bumped by the stdout/stderr callbacks, so a
325
325
  * slow-but-PROGRESSING command (long `npm ci`) is never killed. Both cuts reject with an `ExecutionError` carrying
326
326
  * its real code (exec returns it as-is) — a `RemoteExecutionError` here would reach {@link execError}'s text match
327
327
  * and land as `unknown`. The rejection is armed BEFORE the kill: the kill rejects `wait()` too, and the race must
@@ -3,7 +3,7 @@ import { Sandbox, CommandExitError, FileType } from "e2b";
3
3
  import { FileError, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, } from "@sema-agent/core";
4
4
  import { fileErrorFromExec } from "./remote-env-file-error.js";
5
5
  import { numEnvOr, runSymlinkChain, shellQuote } from "./remote-shell.js";
6
- import { commandBoundsOf, firstBoundOf, laneLivenessOf, livenessCutMessage, LIVENESS_MS_DEFAULT } from "../exec-bounds.js";
6
+ import { buildLivenessCut, commandBoundsOf, firstBoundOf, laneLivenessOf, LIVENESS_MS_DEFAULT } from "../exec-bounds.js";
7
7
  import { BackgroundShellManager } from "./background-shell-support.js";
8
8
  const PROVIDER = "e2b";
9
9
  const DEFAULT_MOUNT_PATH = "/home/user";
@@ -297,7 +297,10 @@ export class RemoteContainerExecutionEnv {
297
297
  startCut = true;
298
298
  this.settleAbandonedStart(starting);
299
299
  const cut = e instanceof RemoteExecutionError && e.code === "timeout" && !options?.abortSignal?.aborted;
300
- return { ok: false, error: cut ? new ExecutionError("timeout", boundCutMessage(first)) : this.execError(e, options?.abortSignal) };
300
+ if (!cut)
301
+ return { ok: false, error: this.execError(e, options?.abortSignal) };
302
+ const bc = boundCut(first);
303
+ return { ok: false, error: new ExecutionError(bc.code, bc.message, undefined, undefined, bc.noOutputSec) };
301
304
  }
302
305
  const onAbort = () => { if (!detached)
303
306
  void handle.kill().catch(() => { }); };
@@ -425,8 +428,10 @@ export class RemoteContainerExecutionEnv {
425
428
  catch (e) {
426
429
  finished = true;
427
430
  this.settleAbandonedStart(starting);
428
- if (e instanceof RemoteExecutionError && e.code === "timeout" && !options?.signal?.aborted)
429
- throw new RemoteExecutionError("timeout", boundCutMessage(first), e);
431
+ if (e instanceof RemoteExecutionError && e.code === "timeout" && !options?.signal?.aborted) {
432
+ const bc = boundCut(first);
433
+ throw new RemoteExecutionError(bc.code, bc.message, e);
434
+ }
430
435
  throw this.remoteError(e, "connect_failed");
431
436
  }
432
437
  handle
@@ -490,7 +495,10 @@ export class RemoteContainerExecutionEnv {
490
495
  const idle = liveness === undefined
491
496
  ? undefined
492
497
  : new Promise((_, reject) => {
493
- timer = setTimeout(() => reject(new RemoteExecutionError("timeout", livenessCutMessage(liveness))), liveness.ms);
498
+ timer = setTimeout(() => {
499
+ const cut = buildLivenessCut(liveness);
500
+ reject(new RemoteExecutionError(cut.code, cut.message));
501
+ }, liveness.ms);
494
502
  });
495
503
  try {
496
504
  await (idle === undefined ? next : Promise.race([next, idle]));
@@ -850,7 +858,8 @@ export class RemoteContainerExecutionEnv {
850
858
  return;
851
859
  const idleFor = Date.now() - lastProgress();
852
860
  if (idleFor >= bound.ms) {
853
- reject(new ExecutionError("timeout", livenessCutMessage(bound)));
861
+ const cut = buildLivenessCut(bound);
862
+ reject(new ExecutionError(cut.code, cut.message, undefined, undefined, cut.noOutputSec));
854
863
  void handle.kill().catch(() => { });
855
864
  this.cfg.metrics?.inc("remote_env_exec_idle_timeout_total", { provider: PROVIDER });
856
865
  }
@@ -866,7 +875,7 @@ export class RemoteContainerExecutionEnv {
866
875
  wallTimer = setTimeout(() => {
867
876
  if (opts.disarmed?.())
868
877
  return;
869
- reject(new ExecutionError("timeout", boundCutMessage({ ms: wall.totalMs, cut: "wall" })));
878
+ reject(new ExecutionError("timeout", wallCutMessage(wall.totalMs)));
870
879
  void handle.kill().catch(() => { });
871
880
  }, wall.leftMs);
872
881
  }
@@ -969,8 +978,11 @@ export function e2bExecutionEnvFactory(config, deps) {
969
978
  function errMsg(e) {
970
979
  return e instanceof Error ? e.message : String(e);
971
980
  }
972
- function boundCutMessage(first) {
973
- return first.cut === "wall" ? `exec exceeded ${first.ms}ms` : livenessCutMessage(first.cut);
981
+ function wallCutMessage(ms) {
982
+ return `exec exceeded ${ms}ms`;
983
+ }
984
+ function boundCut(first) {
985
+ return first.cut === "wall" ? { code: "timeout", message: wallCutMessage(first.ms) } : buildLivenessCut(first.cut);
974
986
  }
975
987
  function providerTimedOut(e) {
976
988
  return e?.name === "TimeoutError" || /timeout|timed out/i.test(errMsg(e));
@@ -314,17 +314,17 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
314
314
  canonicalPath(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
315
315
  readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
316
316
  /**
317
- * S-362 / core 7.21.0 [ref] —— **实现**(表态:本腿实现 `canonicalChain`)。host 腿是 node:fs 腿,所以按
318
- * 提货单 [ref] @server 五适配器段的第一行,与 core 的 `NodeExecutionEnv.canonicalChain` **同形**:对整条
319
- * 拼法 `readlink`,再对每一跳 `readlink`,**上限 64**、**只锚不折**、**遇环即止**;win32 宿主上跳到共享
320
- * (UNC)时**记下该跳并停止探测**(不碰网络,CC `vr` 对网络拼法只给拼法)。
317
+ * S-362 / core 7.21.0 [ref] —— **实现**(表态:本腿实现 `canonicalChain`)。host 腿是 node:fs 腿:走法(对整条拼法 `readlink`、再对每一跳
318
+ * `readlink`,**上限**、**只锚不折**、**遇环即止**、win32 宿主上跳到共享(UNC)时**记下该跳并停止探测** —— 不碰网络)是 core 导出的
319
+ * `walkSymlinkChain`,本腿只供它**逐跳的读口**;上限、播种、锚法、UNC 判都在那一只里,本仓不再重写第二遍。
321
320
  *
322
- * 🔴 **不复用 core 的实现而是同形重写**:`NodeExecutionEnv` 是 core 的内部类,包根不导出它的这只方法;
323
- * 而本腿的每一次读都必须先过 {@link fsReady}(2026-07-25 全族统一:拆除之后只读面不得与写面自相矛盾),
324
- * 那道门在 core 的实现里不存在。同形的判据钉在 `test/remote-env-canonical-chain.test.ts`。
321
+ * 🔴 为什么本腿仍自备读口而不是整体复用 core 的 `NodeExecutionEnv.canonicalChain`:本腿的每一次读都必须先过 {@link fsReady}
322
+ * (2026-07-25 全族统一:拆除之后只读面不得与写面自相矛盾),那道门在 core 的实现里不存在 —— 门在**起点**过一次,读口不再逐跳过
323
+ * (中途 destroy 不改变「这一次链读」的读数:每跳是同一次往返里对本机盘的读,与旧循环逐字同姿势)。
325
324
  *
326
- * 链尽的三种 errno(`EINVAL` 不是软链 / `ENOENT` 不存在 / `ENOTDIR`)⇒ `ok` + 到此为止的跳;其余 errno
327
- * (`EACCES` …)⇒ `ok:false`(core 读作「这一次没有跳」,**永不是拒绝**)。非链接的叶答 `[]`。
325
+ * 链尽的三种 errno(`EINVAL` 不是软链 / `ENOENT` 不存在 / `ENOTDIR`)由本腿的 `fileError` 归成 `invalid` / `not_found` / `not_directory`,
326
+ * core 的走法读作「到此为止」⇒ `ok` + 到此为止的跳;其余 errno(`EACCES` …)⇒ `ok:false`(core 读作「这一次没有跳」,**永不是拒绝**)。
327
+ * 非链接的叶答 `[]`。同形的判据钉在 `test/remote-env-canonical-chain.test.ts`,「读 core 导出」的钉在 `test/core-export-single-source-*.test.ts`。
328
328
  */
329
329
  canonicalChain(p: string, abortSignal?: AbortSignal): Promise<Result<SymlinkChain, FileError>>;
330
330
  exists(p: string, abortSignal?: AbortSignal): Promise<Result<boolean, FileError>>;
@@ -5,12 +5,12 @@ import fs from "node:fs/promises";
5
5
  import { randomBytes } from "node:crypto";
6
6
  import { openSync, closeSync, readSync, statSync, truncateSync, unlinkSync, mkdirSync, rmdirSync, mkdtempSync, existsSync, realpathSync, accessSync, constants as FS_CONST } from "node:fs";
7
7
  import { StringDecoder } from "node:string_decoder";
8
- import { armPipeDestroyGrace, numEnvOr, SYMLINK_CHAIN_MAX_HOPS } from "./remote-shell.js";
9
- import { commandBoundsOf, livenessCutMessage } from "../exec-bounds.js";
8
+ import { armPipeDestroyGrace, numEnvOr } from "./remote-shell.js";
9
+ import { buildLivenessCut, commandBoundsOf } from "../exec-bounds.js";
10
10
  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
- import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
13
+ import { FileError, NodeExecutionEnv, inheritEnvPolicyProblem, ExecutionError, RemoteExecutionError, RollingTailBuffer, walkSymlinkChain, markTruncated, SchedulerError, BackgroundShellError, } from "@sema-agent/core";
14
14
  import { composeHostSubprocessEnv, hostSubprocessEnv } from "../server-secret-env.js";
15
15
  import { trackEphemeralHostWorkspace, untrackEphemeralHostWorkspace } from "./host-workspace-registry.js";
16
16
  const PROVIDER = "host";
@@ -24,7 +24,6 @@ const HOST_BG_FILE_CAP = numEnvOr("HOST_BG_FILE_CAP", 64 * 1024 * 1024, HOST_BG_
24
24
  export const HOST_BG_KILL_GRACE_MS = numEnvOr("HOST_BG_KILL_GRACE_MS", 1000, 0);
25
25
  const HOST_BG_MEM_CAP = numEnvOr("HOST_BG_MEM_CAP", 8 * 1024 * 1024, 64 * 1024);
26
26
  const ok = (value) => ({ ok: true, value });
27
- const UNC_SPELLING = /^[\\/]{2}[^\\/]+[\\/]+[^\\/]/;
28
27
  const unsupported = (op) => ({
29
28
  ok: false,
30
29
  error: new RemoteExecutionError("unsupported", `${op} is not supported on a host target (the operator's own machine — no container boundary, not snapshotable; capabilities.suspendable=false)`),
@@ -919,7 +918,8 @@ export class RemoteHostExecutionEnv {
919
918
  if (liveness !== undefined && !finished && Date.now() - lastChunk >= liveness.ms) {
920
919
  finished = true;
921
920
  kill();
922
- throw new RemoteExecutionError("timeout", livenessCutMessage(liveness));
921
+ const cut = buildLivenessCut(liveness);
922
+ throw new RemoteExecutionError(cut.code, cut.message);
923
923
  }
924
924
  }
925
925
  while (queue.length)
@@ -1099,36 +1099,14 @@ export class RemoteHostExecutionEnv {
1099
1099
  const ready = await this.fsReady(p);
1100
1100
  if (!ready.ok)
1101
1101
  return ready;
1102
- const hops = [];
1103
- let cur = this.resolve(p);
1104
- const seen = new Set([cur]);
1105
- for (let i = 0; i < SYMLINK_CHAIN_MAX_HOPS; i += 1) {
1106
- let target;
1102
+ return walkSymlinkChain(this.resolve(p), async (hop) => {
1107
1103
  try {
1108
- target = await fs.readlink(cur);
1104
+ return ok(await fs.readlink(hop));
1109
1105
  }
1110
1106
  catch (e) {
1111
- const fe = this.fileError(e, cur, abortSignal);
1112
- if (fe.code === "invalid" || fe.code === "not_found" || fe.code === "not_directory")
1113
- break;
1114
- return { ok: false, error: fe };
1107
+ return { ok: false, error: this.fileError(e, hop, abortSignal) };
1115
1108
  }
1116
- let next;
1117
- if (path.isAbsolute(target))
1118
- next = target;
1119
- else {
1120
- const dir = path.dirname(cur);
1121
- next = dir.endsWith(path.sep) ? `${dir}${target}` : `${dir}${path.sep}${target}`;
1122
- }
1123
- if (seen.has(next))
1124
- break;
1125
- seen.add(next);
1126
- hops.push(next);
1127
- if (IS_WIN32 && UNC_SPELLING.test(next))
1128
- break;
1129
- cur = next;
1130
- }
1131
- return ok({ hops });
1109
+ });
1132
1110
  }
1133
1111
  async exists(p, abortSignal) {
1134
1112
  const ready = await this.fsReady(p);
@@ -1325,7 +1303,7 @@ export class RemoteHostExecutionEnv {
1325
1303
  }
1326
1304
  childEnv(perCommand, spawnShell) {
1327
1305
  const base = spawnShell !== undefined
1328
- ? hostSubprocessEnv({ site: "server.remote-env-host.inherit-scrub", policy: this.cfg.inheritEnv, allow: [] })
1306
+ ? hostSubprocessEnv({ site: "server.remote-env-host.spawn-env", policy: this.cfg.inheritEnv, allow: [] })
1329
1307
  : composeHostSubprocessEnv({ policy: this.cfg.inheritEnv, allow: [] });
1330
1308
  if (this.cfg.env)
1331
1309
  Object.assign(base, this.cfg.env);