@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,23 +1,3 @@
1
- /**
2
- * design/158 B6 —— POSIX shell-out 族的共享文件面基座。
3
- *
4
- * 服务对象 = {k8s, ssh, adb, local-docker} 四条「发一条 shell 命令 + 分类 stderr」的腿(lens3 C2 实测
5
- * 42–69% 同构)。**host(node:fs)与 e2b(SDK)是原生 API 腿,不并**(13–27% 是假共性,强行合并会造出
6
- * 一个假基座)。ssh 的 fileInfo/listDir 走 SFTP 原生,只接入本基座的 shell-out 子集。
7
- *
8
- * 五条裁决(B6-posix-shell-fs.md,test/posix-shell-fs.test.ts 逐条钉):
9
- * R1 所有 path 实参前带 `--`(ssh 形):resolve() 先绝对化让风险只剩纵深,但**基座不依赖调用侧不变量**。
10
- * R2 错误分类走 `classifyFsStderr`(k8s 形:分类 + 硬失败/命令缺失护栏 + 站点显式 fallback)。
11
- * R3 listDir 用 `ls -1A`(adb 此前 `-1` 丢 dotfiles —— agent 场景 .env/.gitignore 是一等公民)。
12
- * R4 createTempFile 后缀=尾-X 模板 + `mv`(纯 POSIX;GNU `--suffix` 在 BSD/macOS 远端直接失败;
13
- * 模板尾-X 约束的复发面见 adb 2026-07-25 修案)。
14
- * R5 temp 位置参数化(workspace 腿 `-p <dir>`,ssh `-t`)—— 语义差异,不判正形。
15
- *
16
- * readLink 契约(逐字承各腿原注):必须返回**原始链接目标**(`readlink` 不带 `-f`),core 拿到后自己拼
17
- * 相对目标并走带跳数上限的循环;链接目标含换行是病态形,按 trim 处理,不假装支持。
18
- * statFileInfo 契约:**lstat 语义**(`stat` 不带 `-L`,不跟链)—— symlink 自身可被 fileInfo 判出,
19
- * core 的敏感路径/写门靠这个(exists→canonicalPath 失败→fileInfo 判 symlink→readLink)。
20
- */
21
1
  import path from "node:path";
22
2
  import { FileError, } from "@sema-agent/core";
23
3
  import { shellQuote, kindFromMode } from "./remote-shell.js";
@@ -53,7 +33,6 @@ export function createPosixShellFs(deps) {
53
33
  },
54
34
  async statFileInfo(p, abortSignal) {
55
35
  const abs = resolve(p);
56
- // %s size / %Y mtime(epoch s)/ %f raw hex mode —— GNU、busybox、toybox 三家一致;无 `-L` = lstat。
57
36
  const r = await run(`stat -c '%s %Y %f' -- ${shellQuote(abs)}`, abortSignal);
58
37
  if (!r.ok)
59
38
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
@@ -79,7 +58,6 @@ export function createPosixShellFs(deps) {
79
58
  const out = [];
80
59
  for (const name of names) {
81
60
  const info = await fs.statFileInfo(path.posix.join(abs, name), abortSignal);
82
- // 竞态容忍(承各腿原形):条目在 ls 与 stat 之间消失/不可读时降级为占位行,不让整个列表失败。
83
61
  if (info.ok)
84
62
  out.push(info.value);
85
63
  else
@@ -89,11 +67,6 @@ export function createPosixShellFs(deps) {
89
67
  },
90
68
  async exists(p, abortSignal) {
91
69
  const abs = resolve(p);
92
- // R6(k8s 形=正形,原注全额承接):`[ -e ]` 经 stdout 答 yes/no —— 不依赖 stderr(鲁棒于 fast-exit
93
- // 下 stderr 被截断的竞态),单往返。`|| [ -L ]` 是 2026-07-25 补的跨腿分叉:`[ -e ]` **跟随**符号链接,
94
- // 悬空链接(`latest -> ./build-123` 未构建)会答 false,而 core 的 canonicalizeTarget 第一步就是
95
- // exists —— 答 false ⇒ 走「目标不存在」分支,敏感路径门拿到的键是无害的链接名而非真目标(core 注释
96
- // 点名要 fail-closed 防的绕过形)。`[ -e ] || [ -L ]` = lstat 语义,与 host 腿对齐。
97
70
  const r = await run(`{ [ -e ${shellQuote(abs)} ] || [ -L ${shellQuote(abs)} ]; } && printf yes || printf no`, abortSignal);
98
71
  if (!r.ok)
99
72
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
@@ -106,7 +79,6 @@ export function createPosixShellFs(deps) {
106
79
  },
107
80
  async createDir(p, options) {
108
81
  const abs = resolve(p);
109
- // sftp/一次性 mkdir 都非递归 ⇒ 恒 `-p`(对已存在目录也幂等)——承 ssh/adb/k8s 原形。
110
82
  const r = await run(`mkdir -p -- ${shellQuote(abs)}`, options?.abortSignal);
111
83
  if (!r.ok)
112
84
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
@@ -122,7 +94,7 @@ export function createPosixShellFs(deps) {
122
94
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
123
95
  if (r.value.exitCode !== 0) {
124
96
  if (options?.force && /no such file/i.test(r.value.stderr))
125
- return ok(undefined); // force = 幂等删除
97
+ return ok(undefined);
126
98
  return { ok: false, error: classifyFsStderr(r.value.stderr, abs, "unknown") };
127
99
  }
128
100
  return ok(undefined);
@@ -138,8 +110,6 @@ export function createPosixShellFs(deps) {
138
110
  const sfx = options?.suffix ?? "";
139
111
  const dir = deps.tempDir();
140
112
  const where = dir === null ? "-t" : `-p ${shellQuote(dir)}`;
141
- // R4:`mktemp` 要求 X 在**末尾**,后缀不能折进模板(折进去产出的是一个带字面 XXXXXX 的文件名,
142
- // 第二次调用同名撞车)。正解=合法尾-X 模板建(基名唯一),后缀 `mv` 上去 —— 目标名因基名唯一而唯一。
143
113
  const cmd = sfx
144
114
  ? `f=$(mktemp ${where} ${shellQuote(`${prefix}XXXXXX`)}) && mv -- "$f" "$f"${shellQuote(sfx)} && printf '%s' "$f"${shellQuote(sfx)}`
145
115
  : `mktemp ${where} ${shellQuote(`${prefix}XXXXXX`)}`;
@@ -1,29 +1,3 @@
1
- /**
2
- * `RemoteAdbExecutionEnv` — the ADB implementation of core's `RemoteExecutionEnv` seam (design/61). A PEER
3
- * adapter to SSH/E2B (NOT chained). Use case: an AI driving on-device APP testing over `adb` (a real phone /
4
- * emulator).
5
- *
6
- * 🔴 **An ADB target is a REAL device, not a disposable sandbox** (design/61 §5) — same posture as SSH:
7
- * - `capabilities = { isolation: false, suspendable: false }` — actions are PERMANENT; the leader tightens
8
- * autonomy + the design/37 gate + HITL run on a non-isolated env (design/53).
9
- * - `destroy()` / `cleanup()` **`adb disconnect`** the (TCP) device — they do NOT wipe/reset the phone.
10
- * - `suspendVM`/`resumeVM`/`postResumeInit` → typed `"unsupported"` (a physical device isn't snapshotable).
11
- * `reconnect` re-runs `adb connect`.
12
- *
13
- * 🔴 **First-attach is flaky → retry** (design/61 §2A): a fresh `adb connect` / a device that is still
14
- * `unauthorized` (USB-debug prompt) or `offline` surfaces `auth_transient`, which `connect` retries via
15
- * `withRetry` (idempotent establishment only). A genuinely rejected device → `auth_failed` (permanent).
16
- *
17
- * 🔴 **Credentials / device identity** are control-plane config (the device `serial` / `host:port`), injected
18
- * to the ADAPTER — never the workspace (design/61 §5 #2).
19
- *
20
- * **APP install** is NOT a seam method: the agent pushes the APK with `writeFile` then runs `pm install` (or
21
- * `adb install` is reachable via the device's own shell) through `exec` — the ExecutionEnv stays generic.
22
- *
23
- * ⚠️ **Device-untested:** built mirroring the SSH adapter (which IS proven against a real host); no physical
24
- * device/emulator was available to integration-test the `adb` paths. The contracts are locked by unit tests;
25
- * the live `adb` round-trip needs a device run before production use.
26
- */
27
1
  import path from "node:path";
28
2
  import os from "node:os";
29
3
  import fs from "node:fs/promises";
@@ -40,8 +14,6 @@ const unsupported = (op) => ({
40
14
  error: new RemoteExecutionError("unsupported", `${op} is not supported on an ADB device (real hardware — not snapshotable; capabilities.suspendable=false)`),
41
15
  });
42
16
  const isTcp = (serial) => /:\d+$/.test(serial);
43
- /** [R78]#1: adb stderr markers that mean the DEVICE TRANSPORT died mid-op (not a normal command failure) —
44
- * typed retryable (reconnect + retry per idempotency), never a fake command result. Exported for tests. */
45
17
  export function isAdbTransportLost(stderr) {
46
18
  return /device offline|device '.*' not found|no devices\/emulators found|connection reset|protocol fault|device disconnected/i.test(stderr);
47
19
  }
@@ -49,12 +21,6 @@ export class RemoteAdbExecutionEnv {
49
21
  capabilities = { isolation: false, suspendable: false };
50
22
  cwd;
51
23
  connected = false;
52
- /** 🔴 拆除是**终态**(2026-07-25 补齐:此前本适配器是同族里唯一没有这个概念的一条腿)。
53
- * 没有它的后果实测:`destroy()` 只清 `connected`,于是任何残留引用**一次 `connect()` 就把 env 整个复活**,
54
- * `exec` 又能在真设备上跑;而 `reconnect(sessionToken)` 就在同一个接缝上摆着。读面更松 —— 它连
55
- * `connected` 都不查,所以 destroy 之后 `readTextFile` 照样从真设备把文件读出来。
56
- * 同姿态的 ssh 腿早有先例(`remote-env-ssh.ts`「destroy 后一律不复活,显式 connect/reconnect 同禁」),
57
- * 五条兄弟腿全部 fail-closed,只有 adb 例外 —— 补齐它。 */
58
24
  destroyed = false;
59
25
  cfg;
60
26
  spawnFn;
@@ -78,14 +44,11 @@ export class RemoteAdbExecutionEnv {
78
44
  workspaceHandle() {
79
45
  return { sandboxId: this.cfg.serial, provider: PROVIDER, mountPath: this.cwd };
80
46
  }
81
- // ─────────────────────────────── lifecycle ───────────────────────────────
82
47
  async connect(config) {
83
- // 拆除后一律不复活(显式 connect / reconnect 同禁)—— 与 ssh 腿逐字同口径:被拆除的 env 是 teardown
84
- // 的产物,重连它等于把已释放的真实设备重新挂上,而那时已经没有谁负责再断开它。要新连接就建新 env。
85
48
  if (this.destroyed)
86
49
  return { ok: false, error: new RemoteExecutionError("connect_failed", "ADB env destroyed — create a fresh env instead of reviving") };
87
50
  const policy = {
88
- retryableCodes: ["auth_transient", "connect_failed", "timeout"], // first-attach flakiness; NOT auth_failed
51
+ retryableCodes: ["auth_transient", "connect_failed", "timeout"],
89
52
  maxAttempts: this.cfg.retry.maxAttempts,
90
53
  backoffMs: (failed) => this.cfg.retry.backoffMs * failed,
91
54
  };
@@ -105,7 +68,6 @@ export class RemoteAdbExecutionEnv {
105
68
  if (/cannot connect|connection refused|no route/.test(out))
106
69
  return { ok: false, error: new RemoteExecutionError("connect_failed", c.value.stdout.trim()) };
107
70
  }
108
- // Confirm the device is actually usable (not unauthorized/offline) before declaring connected.
109
71
  const st = await this.adb(["-s", this.cfg.serial, "get-state"], { signal, timeoutMs: this.cfg.commandTimeoutMs });
110
72
  if (!st.ok)
111
73
  return { ok: false, error: new RemoteExecutionError("connect_failed", `adb get-state failed: ${st.error.message}`, st.error) };
@@ -135,23 +97,19 @@ export class RemoteAdbExecutionEnv {
135
97
  this.connected = false;
136
98
  return this.connect();
137
99
  }
138
- /** `adb disconnect` the TCP device (USB: just drop our flag). Does NOT reset the phone — idempotent, never throws. */
139
100
  async destroy() {
140
101
  if (isTcp(this.cfg.serial))
141
102
  await this.adb(["disconnect", this.cfg.serial], { timeoutMs: 10_000 }).catch(() => { });
142
103
  this.connected = false;
143
- this.destroyed = true; // 终态(见字段旁注):此后 connect/reconnect 与整个文件面一律 fail-closed
104
+ this.destroyed = true;
144
105
  }
145
106
  async cleanup() {
146
107
  return this.destroy();
147
108
  }
148
- // ─────────────────────────────── exec ───────────────────────────────
149
109
  async exec(command, options) {
150
110
  if (!this.connected)
151
111
  return { ok: false, error: new ExecutionError("shell_unavailable", "ADB device not connected — call connect() first") };
152
112
  const wrapped = this.withCwdEnv(command, options?.cwd, options?.env);
153
- // exec-out (shell protocol v2, NO pty): clean separated stdout/stderr + a real exit code. `adb shell` allocates
154
- // a pty that merges stderr into stdout and does CRLF translation — wrong for capture/streaming (council §9.2).
155
113
  const r = await this.adb(["-s", this.cfg.serial, "exec-out", wrapped], {
156
114
  signal: options?.abortSignal,
157
115
  timeoutMs: options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs,
@@ -160,10 +118,8 @@ export class RemoteAdbExecutionEnv {
160
118
  });
161
119
  if (!r.ok)
162
120
  return { ok: false, error: r.error };
163
- // A device-transport drop mid-op is NOT a command result — typed "transport_lost" since core 1.89
164
- // (outcome UNKNOWN — reconnect, then retry only per the op's idempotency).
165
121
  if (r.value.exitCode !== 0 && isAdbTransportLost(r.value.stderr)) {
166
- this.connected = false; // dead transport — the next exec fails closed; reconnect() re-establishes
122
+ this.connected = false;
167
123
  return { ok: false, error: new ExecutionError("transport_lost", `adb transport lost mid-command: ${r.value.stderr.trim().slice(0, 200)} — reconnect + retry per idempotency`) };
168
124
  }
169
125
  return ok(r.value);
@@ -172,7 +128,7 @@ export class RemoteAdbExecutionEnv {
172
128
  if (!this.connected)
173
129
  throw new RemoteExecutionError("connect_failed", "ADB device not connected — call connect() first");
174
130
  const wrapped = this.withCwdEnv(command, options?.cwd, options?.env);
175
- const child = this.spawnFn(this.cfg.adbPath, ["-s", this.cfg.serial, "exec-out", wrapped], { stdio: ["ignore", "pipe", "pipe"] }); // exec-out (no pty) — see exec()
131
+ const child = this.spawnFn(this.cfg.adbPath, ["-s", this.cfg.serial, "exec-out", wrapped], { stdio: ["ignore", "pipe", "pipe"] });
176
132
  const queue = [];
177
133
  let finished = false;
178
134
  let failure;
@@ -188,8 +144,6 @@ export class RemoteAdbExecutionEnv {
188
144
  let paused = false;
189
145
  let onAbort;
190
146
  const teardown = () => {
191
- // every exit path (normal end, timeout/failure throw, consumer early-break via .return()): drop the abort
192
- // listener (council #5) + kill the child if still running.
193
147
  if (onAbort) {
194
148
  options?.signal?.removeEventListener("abort", onAbort);
195
149
  onAbort = undefined;
@@ -199,7 +153,6 @@ export class RemoteAdbExecutionEnv {
199
153
  child.kill("SIGKILL");
200
154
  }
201
155
  catch {
202
- /* noop */
203
156
  }
204
157
  }
205
158
  };
@@ -219,12 +172,12 @@ export class RemoteAdbExecutionEnv {
219
172
  lastChunk = Date.now();
220
173
  queue.push(chunk);
221
174
  if (queue.length >= 512 && !paused) {
222
- paused = true; // backpressure: bound queue growth on a fast producer / slow consumer (council design #1)
175
+ paused = true;
223
176
  child.stdout.pause();
224
177
  }
225
178
  signalReady();
226
179
  };
227
- let stderrTail = ""; // [R78]#1: watch for device-transport-drop markers (bounded tail, not the whole stream)
180
+ let stderrTail = "";
228
181
  child.stdout.on("data", (d) => enqueue({ type: "stdout", data: d.toString() }));
229
182
  child.stderr.on("data", (d) => {
230
183
  stderrTail = (stderrTail + d.toString()).slice(-512);
@@ -237,12 +190,9 @@ export class RemoteAdbExecutionEnv {
237
190
  });
238
191
  child.on("close", (code, signal) => {
239
192
  if ((code ?? 0) !== 0 && isAdbTransportLost(stderrTail) && !failure) {
240
- // device transport died mid-stream — typed retryable, not a fake exit ([R78]#1)
241
193
  this.connected = false;
242
194
  failure = new RemoteExecutionError("transport_lost", `adb transport lost mid-stream: ${stderrTail.trim().slice(0, 200)}`);
243
195
  }
244
- // code==null WITH a signal = the adb child was killed by an external signal (SIGKILL/OOM) — conventional
245
- // 128+signo, NOT a fake success 0 ([R78]#2; the second `close` argument was dropped before this fix).
246
196
  exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 9) : 0);
247
197
  finished = true;
248
198
  signalReady();
@@ -260,7 +210,6 @@ export class RemoteAdbExecutionEnv {
260
210
  if (finished)
261
211
  break;
262
212
  const waitMs = idleMs ? Math.max(1, idleMs - (Date.now() - lastChunk)) : 30_000;
263
- // [4947] A-002.11 sibling: clear the losing timer — wake() firing first used to leave it armed.
264
213
  let waitTimer;
265
214
  try {
266
215
  await new Promise((r) => {
@@ -284,15 +233,12 @@ export class RemoteAdbExecutionEnv {
284
233
  teardown();
285
234
  }
286
235
  }
287
- // ─────────────────────────────── FileSystem (adb shell + push/exec-out) ───────────────────────────────
288
236
  async absolutePath(p) {
289
237
  return ok(this.resolve(p));
290
238
  }
291
239
  async joinPath(parts) {
292
240
  return ok(path.posix.join(...parts));
293
241
  }
294
- /** 文件面的终态门 —— 读面此前**连 `connected` 都不查**,所以 destroy 之后还能从真设备读出文件内容。
295
- * 与 host/local-docker/k8s/e2b 的姿态对齐(它们的文件面都在 destroy 后 fail-closed)。 */
296
242
  fsGate(p) {
297
243
  if (this.destroyed)
298
244
  return { ok: false, error: new FileError("unknown", "execution env already destroyed", this.resolve(p)) };
@@ -303,7 +249,6 @@ export class RemoteAdbExecutionEnv {
303
249
  if (gated)
304
250
  return gated;
305
251
  const abs = this.resolve(p);
306
- // exec-out streams the file's raw bytes (binary-safe); stderr carries cat's error if any.
307
252
  const r = await this.adb(["-s", this.cfg.serial, "exec-out", `cat ${shellQuote(abs)}`], { signal: abortSignal, timeoutMs: this.cfg.dataTimeoutMs, binary: true });
308
253
  if (!r.ok)
309
254
  return { ok: false, error: fileErrorFromExec(r.error, abs) };
@@ -317,8 +262,6 @@ export class RemoteAdbExecutionEnv {
317
262
  return { ok: false, error: new FileError("is_directory", errOut, abs) };
318
263
  return { ok: false, error: new FileError("unknown", errOut, abs) };
319
264
  }
320
- // exec-out gives no exit code on the wire, but the adb transport itself can fail non-zero with empty stderr
321
- // (device dropped mid-read) — don't mistake that for a legit empty file (council #6).
322
265
  if (r.value.exitCode !== 0)
323
266
  return { ok: false, error: new FileError("unknown", `adb exec-out cat exited ${r.value.exitCode}`, abs) };
324
267
  return ok(r.value.stdoutBytes ?? new Uint8Array(0));
@@ -343,16 +286,11 @@ export class RemoteAdbExecutionEnv {
343
286
  const dir = path.posix.dirname(abs);
344
287
  if (dir && dir !== "." && dir !== "/") {
345
288
  const mk = await this.exec(`mkdir -p ${shellQuote(dir)}`, { abortSignal });
346
- // 🔴 `!mk.ok` 也必须 fail-closed(2026-07-25 补):原来只看 `exitCode !== 0`,于是 **exec 本身失败**
347
- // (设备/连接在这一拍掉线 ⇒ `transport_lost` / `shell_unavailable`)被整条吞掉,后面照样推载荷,
348
- // 而整个 `writeFile` 返回 **ok:true** —— 调用方看到的成功与真实故障点完全脱节。同族的
349
- // local-docker / k8s 用的正是 `if (!mk.ok) return`,这两条腿是漏的。
350
289
  if (!mk.ok)
351
290
  return { ok: false, error: fileErrorFromExec(mk.error, dir) };
352
291
  if (mk.value.exitCode !== 0)
353
292
  return { ok: false, error: new FileError("unknown", mk.value.stderr.trim() || `mkdir -p failed for ${dir}`, dir) };
354
293
  }
355
- // Stage to a local temp then `adb push` (binary-safe; avoids shell-escaping the payload).
356
294
  let local;
357
295
  try {
358
296
  local = path.join(os.tmpdir(), `adb-push-${Date.now()}-${Math.random().toString(36).slice(2)}`);
@@ -391,9 +329,6 @@ export class RemoteAdbExecutionEnv {
391
329
  merged.set(add, base.length);
392
330
  return this.writeFile(abs, merged, abortSignal);
393
331
  }
394
- // ── design/158 B6:以下 POSIX shell-out 文件面全部委托共享基座(posix-shell-fs.ts)──────────────
395
- // 五裁决(`--`/classifyFsStderr/ls -1A/mv 后缀形/temp 位置参数化)与 readLink・lstat 契约见基座头注;
396
- // 委托钉在 test/posix-shell-fs.test.ts(腿内不再自拼 readlink/realpath 命令串)。
397
332
  posixFsInst;
398
333
  get posixFs() {
399
334
  return (this.posixFsInst ??= createPosixShellFs({
@@ -429,42 +364,27 @@ export class RemoteAdbExecutionEnv {
429
364
  async createTempFile(options) {
430
365
  return this.posixFs.createTempFile(options);
431
366
  }
432
- // ─────────────────────────────── helpers ───────────────────────────────
433
- /** Spawn the adb binary with the given args; collect stdout/stderr (+ raw bytes when binary), bound by timeout/abort. */
434
367
  adb(args, opts) {
435
368
  return new Promise((resolve) => {
436
369
  let settled = false;
437
- // D5 pipe-destroy grace: armed by killGrace() on abort/timeout; destroys stdout/stderr pipes if `close`
438
- // never fires (a re-parented grandchild holding the FD). Cleared in finish() — the real `close` routes
439
- // through finish() — so the timer never leaks. Does NOT change the synchronous settle below.
440
370
  let forceSettleTimer;
441
371
  const child = this.spawnFn(this.cfg.adbPath, args, { stdio: ["ignore", "pipe", "pipe"] });
442
- // SIGKILL the child then arm the D5 pipe-destroy grace (only on the abort/timeout paths — a normal `close`
443
- // never calls this). Idempotent: only one timer is ever armed.
444
372
  const killGrace = () => {
445
373
  try {
446
374
  child.kill("SIGKILL");
447
375
  }
448
- catch { /* noop */ }
376
+ catch { }
449
377
  forceSettleTimer ??= armPipeDestroyGrace(child);
450
378
  };
451
- // 🔴 binary caveat (D2): this helper is SHARED by the model-facing TEXT exec (must be bounded — a
452
- // `yes`/`cat big` could OOM the durable worker) and by readBinaryFile (`exec-out cat`, opts.binary=true,
453
- // which MUST keep EVERY byte or binary/large reads silently corrupt). So we branch on opts.binary:
454
- // - binary=true → full Buffer[] + concat (byte-exact stdoutBytes; readBinaryFile path).
455
- // - binary=false → bounded rolling-TAIL for stdout (keep last 8MB + dropped-byte marker, O(maxBytes)).
456
- // stderr is ALWAYS bounded — it never carries the binary payload (just cat's error text).
457
379
  const binary = !!opts?.binary;
458
- const outBufs = []; // binary path only: full bytes for stdoutBytes
459
- const outTail = binary ? undefined : new RollingTailBuffer(); // text path only: bounded stdout
460
- const errBuf = new RollingTailBuffer(); // Buffer[]+concat replaced by rolling-tail — same O(n) push, bounded total
380
+ const outBufs = [];
381
+ const outTail = binary ? undefined : new RollingTailBuffer();
382
+ const errBuf = new RollingTailBuffer();
461
383
  const finish = (r) => {
462
384
  if (settled)
463
385
  return;
464
386
  settled = true;
465
387
  clearTimeout(timer);
466
- // D5 grace cleared in the `close` handler (pipes closed naturally), NOT here — abort/timeout reaches
467
- // finish() BEFORE `close`, so clearing here would cancel the grace before it can free a leaked FD.
468
388
  opts?.signal?.removeEventListener("abort", onAbort);
469
389
  resolve(r);
470
390
  };
@@ -487,7 +407,7 @@ export class RemoteAdbExecutionEnv {
487
407
  outBufs.push(d);
488
408
  else
489
409
  outTail.push(d);
490
- opts?.onStdout?.(d.toString()); // streaming callback still gets the full chunk; only the buffered return is bounded
410
+ opts?.onStdout?.(d.toString());
491
411
  });
492
412
  child.stderr.on("data", (d) => {
493
413
  errBuf.push(d);
@@ -496,15 +416,11 @@ export class RemoteAdbExecutionEnv {
496
416
  child.on("error", (e) => finish({ ok: false, error: new ExecutionError("spawn_error", `adb spawn failed (is '${this.cfg.adbPath}' installed?): ${e.message}`, e) }));
497
417
  child.on("close", (code, signal) => {
498
418
  if (forceSettleTimer)
499
- clearTimeout(forceSettleTimer); // `close` fired → pipes closed naturally; clear D5 grace
419
+ clearTimeout(forceSettleTimer);
500
420
  const err = errBuf.result();
501
421
  const stderr = markTruncated(err.text, err.droppedBytes);
502
- // code==null WITH a signal = the adb child was killed by an external signal (SIGKILL/OOM) —
503
- // conventional 128+signo, NOT a fake success 0 ([R78]#2; the second `close` argument was dropped
504
- // before this fix).
505
422
  const exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 9) : 0);
506
423
  if (binary) {
507
- // byte-exact: readBinaryFile decodes stdoutBytes; never truncate.
508
424
  const stdoutBytes = Buffer.concat(outBufs);
509
425
  finish(ok({ stdout: stdoutBytes.toString("utf8"), stderr, exitCode, stdoutBytes: new Uint8Array(stdoutBytes) }));
510
426
  }
@@ -527,11 +443,6 @@ export class RemoteAdbExecutionEnv {
527
443
  return parts.join(" ");
528
444
  }
529
445
  }
530
- /**
531
- * `ExecutionEnvFactory` for the ADB backend — one fresh (unconnected) env per task against the same device
532
- * (lazy: `adb connect` on first use). Config (serial) is control-plane-supplied; the Runner owns the lifetime
533
- * and calls `destroy()` (= `adb disconnect`, never a device wipe) on task end.
534
- */
535
446
  export function adbExecutionEnvFactory(config) {
536
447
  return (_ctx) => new RemoteAdbExecutionEnv(config);
537
448
  }