@sema-agent/server 7.150.0 → 7.151.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 (341) hide show
  1. package/MIGRATION.md +185 -0
  2. package/USAGE.md +53 -25
  3. package/deploy/sema-up/README.md +7 -1
  4. package/deploy/sema-up/chart/Chart.yaml +1 -1
  5. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  6. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  8. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  9. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  10. package/deploy/sema-up/chart/values.yaml +8 -1
  11. package/deploy/sema-up/cluster-up.sh +1 -0
  12. package/deploy/sema-up/kube-up.sh +1 -1
  13. package/deploy/sema-up/lib/flagshape.sh +1 -1
  14. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  16. package/dist/adoption/import.js +3 -3
  17. package/dist/approval-ask-machine.d.ts +1 -1
  18. package/dist/approval-card.d.ts +2 -2
  19. package/dist/approval-gate-currency.d.ts +1 -1
  20. package/dist/auth-bridge.d.ts +20 -0
  21. package/dist/auth-bridge.js +9 -0
  22. package/dist/background-session-reap.d.ts +1 -1
  23. package/dist/bake-runner/main.d.ts +4 -2
  24. package/dist/bind-host.d.ts +2 -3
  25. package/dist/bind-host.js +2 -4
  26. package/dist/boot/budget-tracing.js +1 -1
  27. package/dist/boot/engine-lease.d.ts +5 -0
  28. package/dist/boot/engine-lease.js +1 -0
  29. package/dist/boot/execution-env.d.ts +8 -14
  30. package/dist/boot/execution-env.js +54 -17
  31. package/dist/boot/execution-lane.js +32 -5
  32. package/dist/boot/late-binds.d.ts +8 -4
  33. package/dist/boot/late-binds.js +7 -1
  34. package/dist/boot/memory-consolidation.d.ts +3 -2
  35. package/dist/boot/memory-consolidation.js +6 -6
  36. package/dist/boot/reapers.d.ts +5 -0
  37. package/dist/boot/reapers.js +12 -1
  38. package/dist/boot/resolve-spec.d.ts +3 -3
  39. package/dist/boot/resolve-spec.js +31 -22
  40. package/dist/boot/runner-deps.d.ts +9 -2
  41. package/dist/boot/runner-deps.js +3 -3
  42. package/dist/boot/session-age-out.d.ts +36 -0
  43. package/dist/boot/session-age-out.js +101 -0
  44. package/dist/boot/session-faces.d.ts +5 -14
  45. package/dist/boot/session-faces.js +9 -5
  46. package/dist/boot/stage-01-config.js +2 -0
  47. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  48. package/dist/boot/stage-05-execution-env.js +2 -2
  49. package/dist/boot/stage-06-runners.d.ts +7 -4
  50. package/dist/boot/stage-06-runners.js +15 -6
  51. package/dist/boot/stage-07-capability-layer.d.ts +7 -4
  52. package/dist/boot/stage-07-capability-layer.js +2 -1
  53. package/dist/boot/stage-08-reapers.d.ts +7 -4
  54. package/dist/boot/stage-08-reapers.js +4 -2
  55. package/dist/boot/stage-09-leader.d.ts +11 -5
  56. package/dist/boot/stage-09-leader.js +12 -3
  57. package/dist/boot/stage-10-http-server.d.ts +11 -5
  58. package/dist/boot/stage-10-http-server.js +3 -6
  59. package/dist/boot/web-admin.js +3 -1
  60. package/dist/boot/workflow-orchestration.d.ts +7 -2
  61. package/dist/boot/workflow-orchestration.js +24 -7
  62. package/dist/bounded-rotation.d.ts +29 -0
  63. package/dist/bounded-rotation.js +24 -0
  64. package/dist/capability-facts.d.ts +22 -13
  65. package/dist/capability-facts.js +28 -18
  66. package/dist/center-credential.d.ts +25 -6
  67. package/dist/center-credential.js +25 -5
  68. package/dist/config-catalog.d.ts +26 -7
  69. package/dist/config-catalog.js +14 -11
  70. package/dist/config-invariants.d.ts +2 -1
  71. package/dist/config-invariants.js +4 -4
  72. package/dist/config-types.d.ts +10 -4
  73. package/dist/config.d.ts +51 -2
  74. package/dist/config.js +35 -24
  75. package/dist/deployment-shape.d.ts +9 -1
  76. package/dist/deployment-shape.js +2 -1
  77. package/dist/device-protocol.d.ts +5 -4
  78. package/dist/device-protocol.js +1 -1
  79. package/dist/device-ws-hub.js +3 -2
  80. package/dist/device-ws-protocol.d.ts +63 -10
  81. package/dist/device-ws-protocol.js +56 -25
  82. package/dist/engine-identity.d.ts +19 -7
  83. package/dist/engine-identity.js +10 -2
  84. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  85. package/dist/execution-env-refusal.d.ts +23 -0
  86. package/dist/execution-env-refusal.js +5 -0
  87. package/dist/execution-lane-caps.d.ts +3 -1
  88. package/dist/field-conflict.d.ts +1 -1
  89. package/dist/file-history-capture.d.ts +11 -4
  90. package/dist/file-history-capture.js +9 -6
  91. package/dist/file-history-disclosure.d.ts +7 -5
  92. package/dist/file-history-disclosure.js +7 -5
  93. package/dist/fleet/drain-entry.d.ts +4 -0
  94. package/dist/hooks/hook-runner.js +2 -2
  95. package/dist/host-lsp-manager.d.ts +2 -2
  96. package/dist/http/admission.js +17 -2
  97. package/dist/http/approval-sweeps.js +5 -5
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +2 -1
  101. package/dist/http/dispatch.js +34 -27
  102. package/dist/http/durable-run-leg.js +16 -16
  103. package/dist/http/health-self-report.d.ts +9 -1
  104. package/dist/http/health-self-report.js +2 -0
  105. package/dist/http/owner-access.d.ts +31 -11
  106. package/dist/http/owner-access.js +37 -6
  107. package/dist/http/resume-ctx-read.js +25 -3
  108. package/dist/http/resume-legs.d.ts +2 -1
  109. package/dist/http/resume-legs.js +259 -79
  110. package/dist/http/route-ctx.d.ts +18 -8
  111. package/dist/http/route-table.d.ts +1 -1
  112. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  113. package/dist/http/routes/agents-roster.js +2 -3
  114. package/dist/http/routes/approvals-assistant.js +4 -3
  115. package/dist/http/routes/background.d.ts +3 -3
  116. package/dist/http/routes/capabilities.js +8 -5
  117. package/dist/http/routes/fleet.js +10 -8
  118. package/dist/http/routes/projects.d.ts +3 -2
  119. package/dist/http/routes/projects.js +4 -5
  120. package/dist/http/routes/runs.js +102 -36
  121. package/dist/http/routes/session-sync.d.ts +4 -3
  122. package/dist/http/routes/session-sync.js +172 -77
  123. package/dist/http/routes/sessions-list.js +13 -7
  124. package/dist/http/routes/sessions.js +10 -6
  125. package/dist/http/routes/tasks.js +30 -16
  126. package/dist/http/routes/trace-usage.js +2 -2
  127. package/dist/http/server.d.ts +21 -27
  128. package/dist/http/server.js +2 -2
  129. package/dist/http/submit-ctx.js +5 -4
  130. package/dist/http/wire-types.d.ts +14 -4
  131. package/dist/identity-source.d.ts +20 -3
  132. package/dist/identity-source.js +14 -0
  133. package/dist/index.d.ts +4 -0
  134. package/dist/lane-home-probe.d.ts +3 -7
  135. package/dist/lane-home-probe.js +0 -1
  136. package/dist/leader/leader.d.ts +2 -1
  137. package/dist/leg-facts.d.ts +13 -5
  138. package/dist/leg-facts.js +2 -1
  139. package/dist/memory-scope.d.ts +5 -4
  140. package/dist/memory-scope.js +4 -4
  141. package/dist/memory-sync-client.d.ts +30 -2
  142. package/dist/memory-sync-client.js +16 -9
  143. package/dist/observability/err-text.d.ts +7 -0
  144. package/dist/observability/err-text.js +5 -0
  145. package/dist/observability/fail-open.d.ts +7 -3
  146. package/dist/observability/fail-open.js +7 -3
  147. package/dist/observability/metrics.js +1 -0
  148. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  149. package/dist/orchestration/workflow-notify-journal.js +12 -1
  150. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  151. package/dist/orchestration/workflow-park-decision.js +49 -10
  152. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  153. package/dist/orchestration/workflow-park-holder.js +98 -0
  154. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  155. package/dist/orchestration/workflow-record-fence.js +42 -0
  156. package/dist/owner-faces.d.ts +1 -1
  157. package/dist/parked-decide.d.ts +104 -53
  158. package/dist/parked-decide.js +153 -117
  159. package/dist/peer-directory.d.ts +2 -2
  160. package/dist/peer-inbound-face.d.ts +2 -6
  161. package/dist/peer-inbound-face.js +0 -3
  162. package/dist/permission-mode-admission.d.ts +41 -5
  163. package/dist/permission-mode-admission.js +29 -3
  164. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  165. package/dist/plugins/adoption-log-sql.js +7 -1
  166. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  167. package/dist/plugins/approval-ask-store-file.js +8 -0
  168. package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
  169. package/dist/plugins/approval-ask-store-memory.js +28 -9
  170. package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
  171. package/dist/plugins/approval-ask-store-sql.js +34 -5
  172. package/dist/plugins/approval-exemption-store.js +3 -0
  173. package/dist/plugins/approval-nonce-store.js +2 -0
  174. package/dist/plugins/breaker-state-sql.js +2 -0
  175. package/dist/plugins/caching-session-store.d.ts +9 -15
  176. package/dist/plugins/caching-session-store.js +6 -0
  177. package/dist/plugins/device-store-sql.js +6 -0
  178. package/dist/plugins/error-origin.d.ts +18 -0
  179. package/dist/plugins/error-origin.js +10 -0
  180. package/dist/plugins/execution-placement-sql.js +6 -6
  181. package/dist/plugins/image-bake-store-sql.js +6 -0
  182. package/dist/plugins/image-index-sql.js +4 -0
  183. package/dist/plugins/landed-attendants.d.ts +212 -0
  184. package/dist/plugins/landed-attendants.js +161 -0
  185. package/dist/plugins/leader-run-store-sql.js +4 -0
  186. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  187. package/dist/plugins/local-session-live-reference.js +39 -0
  188. package/dist/plugins/local-session-store.d.ts +71 -33
  189. package/dist/plugins/local-session-store.js +129 -27
  190. package/dist/plugins/memory-engine-pg.js +4 -0
  191. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  192. package/dist/plugins/memory-engine-tidb.js +4 -0
  193. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  194. package/dist/plugins/memory-sync-store-pg.js +5 -0
  195. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  196. package/dist/plugins/one-time-migrations.d.ts +57 -63
  197. package/dist/plugins/one-time-migrations.js +162 -90
  198. package/dist/plugins/org-delivery-quota-store.js +4 -1
  199. package/dist/plugins/outcome-ledger-sql.js +2 -0
  200. package/dist/plugins/permission-rule-store-sql.js +11 -4
  201. package/dist/plugins/pg-cost-quota.js +4 -0
  202. package/dist/plugins/pg-pool.js +24 -1
  203. package/dist/plugins/pg-rate-limiter.js +2 -0
  204. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  205. package/dist/plugins/posix-shell-fs.js +4 -1
  206. package/dist/plugins/remote-env-adb.d.ts +19 -15
  207. package/dist/plugins/remote-env-adb.js +41 -15
  208. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  209. package/dist/plugins/remote-env-connect-seat.js +72 -0
  210. package/dist/plugins/remote-env-device.d.ts +22 -5
  211. package/dist/plugins/remote-env-device.js +106 -8
  212. package/dist/plugins/remote-env-e2b.d.ts +15 -6
  213. package/dist/plugins/remote-env-e2b.js +42 -32
  214. package/dist/plugins/remote-env-file-error.js +6 -2
  215. package/dist/plugins/remote-env-host.d.ts +17 -0
  216. package/dist/plugins/remote-env-host.js +30 -38
  217. package/dist/plugins/remote-env-k8s.d.ts +41 -39
  218. package/dist/plugins/remote-env-k8s.js +53 -30
  219. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  220. package/dist/plugins/remote-env-local-docker.js +34 -41
  221. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  222. package/dist/plugins/remote-env-ssh.js +60 -59
  223. package/dist/plugins/remote-shell.d.ts +98 -1
  224. package/dist/plugins/remote-shell.js +218 -0
  225. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  226. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  227. package/dist/plugins/retention-lane-store-sql.js +4 -0
  228. package/dist/plugins/retention-sql.d.ts +20 -2
  229. package/dist/plugins/retention-sql.js +11 -0
  230. package/dist/plugins/run-store-sql.d.ts +21 -2
  231. package/dist/plugins/run-store-sql.js +31 -7
  232. package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
  233. package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
  234. package/dist/plugins/schema-consistency.d.ts +4 -2
  235. package/dist/plugins/schema-consistency.js +19 -2
  236. package/dist/plugins/send-file-ledger.js +3 -0
  237. package/dist/plugins/serving-requirement.d.ts +26 -7
  238. package/dist/plugins/serving-requirement.js +11 -4
  239. package/dist/plugins/session-birth.d.ts +6 -2
  240. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  241. package/dist/plugins/session-ext-table.d.ts +14 -1
  242. package/dist/plugins/session-ext-table.js +5 -1
  243. package/dist/plugins/session-row-sql.d.ts +49 -2
  244. package/dist/plugins/session-row-sql.js +56 -13
  245. package/dist/plugins/session-store-sql.d.ts +34 -18
  246. package/dist/plugins/session-store-sql.js +98 -17
  247. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  248. package/dist/plugins/shared-memory-store-sql.js +6 -2
  249. package/dist/plugins/sql-driver.d.ts +2 -1
  250. package/dist/plugins/sql-driver.js +18 -3
  251. package/dist/plugins/sql-errors.d.ts +11 -6
  252. package/dist/plugins/sql-errors.js +5 -6
  253. package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
  254. package/dist/plugins/stop-then-start-sentence.js +14 -10
  255. package/dist/plugins/stop-then-start.d.ts +109 -0
  256. package/dist/plugins/stop-then-start.js +104 -0
  257. package/dist/plugins/store-backend.d.ts +40 -13
  258. package/dist/plugins/store-backend.js +169 -27
  259. package/dist/plugins/store-boundary.d.ts +57 -0
  260. package/dist/plugins/store-boundary.js +139 -0
  261. package/dist/plugins/store-contracts.d.ts +17 -0
  262. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  263. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  264. package/dist/plugins/store-fault.d.ts +26 -7
  265. package/dist/plugins/store-fault.js +18 -2
  266. package/dist/plugins/task-attachment-store.js +9 -0
  267. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  268. package/dist/plugins/task-list-store-sql.js +10 -8
  269. package/dist/plugins/tidb-cost-quota.js +4 -0
  270. package/dist/plugins/tidb-pool.js +2 -1
  271. package/dist/plugins/tidb-rate-limiter.js +4 -0
  272. package/dist/plugins/user-input-ledger-sql.js +4 -0
  273. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  274. package/dist/plugins/workflow-run-store-sql.js +64 -4
  275. package/dist/plugins/workspace-fill.d.ts +4 -7
  276. package/dist/plugins/workspace-fill.js +2 -1
  277. package/dist/plugins/write-behind-counter.js +5 -0
  278. package/dist/project-context.d.ts +0 -2
  279. package/dist/project-context.js +4 -8
  280. package/dist/project-registry.d.ts +46 -0
  281. package/dist/project-registry.js +44 -0
  282. package/dist/request-key-closure.js +1 -1
  283. package/dist/request-value-closure.d.ts +4 -0
  284. package/dist/request-value-closure.js +10 -0
  285. package/dist/run-acceptance.d.ts +3 -2
  286. package/dist/run-acceptance.js +4 -6
  287. package/dist/run-local.js +1 -0
  288. package/dist/runs.d.ts +37 -8
  289. package/dist/runs.js +53 -12
  290. package/dist/runtime-governance.d.ts +5 -4
  291. package/dist/security.d.ts +30 -32
  292. package/dist/security.js +41 -7
  293. package/dist/server-secret-env.d.ts +12 -10
  294. package/dist/server-secret-env.js +24 -15
  295. package/dist/session-bindings.d.ts +82 -0
  296. package/dist/session-bindings.js +18 -0
  297. package/dist/session-children-retire.d.ts +2 -2
  298. package/dist/session-children-retire.js +2 -6
  299. package/dist/session-deletion.d.ts +16 -1
  300. package/dist/session-error-wire.d.ts +3 -3
  301. package/dist/session-error-wire.js +3 -3
  302. package/dist/session-incarnation.d.ts +10 -0
  303. package/dist/session-incarnation.js +10 -1
  304. package/dist/session-kind.d.ts +57 -0
  305. package/dist/session-kind.js +22 -0
  306. package/dist/session-row.d.ts +21 -1
  307. package/dist/session-row.js +7 -1
  308. package/dist/session-sync-kernel.d.ts +24 -14
  309. package/dist/session-sync-kernel.js +3 -6
  310. package/dist/session-sync.d.ts +4 -98
  311. package/dist/session-sync.js +1 -71
  312. package/dist/session-titler.d.ts +10 -3
  313. package/dist/session-titler.js +19 -6
  314. package/dist/ssh-host-key.d.ts +5 -0
  315. package/dist/ssh-host-key.js +3 -0
  316. package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
  317. package/dist/stores-sql/file-history-store-sql.js +108 -70
  318. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  319. package/dist/stores-sql/pg-session-storage.js +37 -13
  320. package/dist/stores-sql/retention-store-sql.d.ts +1 -9
  321. package/dist/stores-sql/retention-store-sql.js +2 -10
  322. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  323. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  324. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  325. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  326. package/dist/stores-sql/tidb-session-store.js +37 -13
  327. package/dist/submission-exit.d.ts +3 -2
  328. package/dist/submission-exit.js +9 -1
  329. package/dist/task-cwd.d.ts +2 -2
  330. package/dist/task-settings.d.ts +6 -5
  331. package/dist/terminal.d.ts +33 -2
  332. package/dist/terminal.js +139 -9
  333. package/dist/tool-approval.d.ts +140 -6
  334. package/dist/tool-approval.js +165 -72
  335. package/dist/trace/self-minted-wire.d.ts +290 -27
  336. package/dist/trace/self-minted-wire.js +101 -13
  337. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +78 -27
  338. package/package.json +2 -2
  339. package/wire-changes/server@7.151.0.json +762 -0
  340. package/dist/http/session-inherit-env.d.ts +0 -36
  341. package/dist/http/session-inherit-env.js +0 -6
@@ -5,6 +5,8 @@ import { atomicWriteFile, ensureDir } from "@sema-agent/core";
5
5
  import { mysqlDriver, pgDriver, sqlDbNowMs } from "./sql-driver.js";
6
6
  import { retryOnDeadlock } from "./sql-errors.js";
7
7
  import { alignedWindow, retryAfterSecUntil } from "../observability/cost-quota.js";
8
+ import { sealStoreClass } from "./store-boundary.js";
9
+ import { storeBoundaryFault } from "../terminal.js";
8
10
  export const ORG_DELIVERY_SENDER_QUOTA_TABLE = "org_delivery_sender_quota";
9
11
  export const ORG_DELIVERY_RECIPIENT_QUOTA_TABLE = "org_delivery_recipient_quota";
10
12
  export const ORG_DELIVERY_PAIR_QUOTA_TABLE = "org_delivery_pair_quota";
@@ -169,7 +171,7 @@ export class SqlOrgDeliveryQuotaStore {
169
171
  }
170
172
  catch (err) {
171
173
  await conn.rollback().catch((rollbackErr) => {
172
- throw new AggregateError([err, rollbackErr], `${table}: the quota transaction failed and its rollback failed too`);
174
+ throw new AggregateError([storeBoundaryFault(err, "sql-org-delivery-quota-store.inTx"), storeBoundaryFault(rollbackErr, "sql-org-delivery-quota-store.inTx")], `${table}: the quota transaction failed and its rollback failed too`);
173
175
  });
174
176
  throw err;
175
177
  }
@@ -221,6 +223,7 @@ export class SqlOrgDeliveryQuotaStore {
221
223
  return true;
222
224
  }
223
225
  }
226
+ sealStoreClass(SqlOrgDeliveryQuotaStore, "sql-org-delivery-quota-store", { admitAttempt: "exit", take: "exit" });
224
227
  export class TiDBOrgDeliveryQuotaStore extends SqlOrgDeliveryQuotaStore {
225
228
  constructor(pool) {
226
229
  super(mysqlDriver(pool));
@@ -1,6 +1,7 @@
1
1
  import { randomUUID, createHash } from "node:crypto";
2
2
  import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
3
3
  import { ensureIndexes } from "./ensure-index.js";
4
+ import { sealStoreClass } from "./store-boundary.js";
4
5
  export function taskSignature(s) {
5
6
  if (s.tag)
6
7
  return `tag:${s.tag}`;
@@ -148,6 +149,7 @@ export class SqlOutcomeLedger {
148
149
  }));
149
150
  }
150
151
  }
152
+ sealStoreClass(SqlOutcomeLedger, "sql-outcome-ledger", { record: "exit", recordCore: "exit", summary: "exit" });
151
153
  export class TiDBOutcomeLedger extends SqlOutcomeLedger {
152
154
  constructor(pool) {
153
155
  super(mysqlDriver(pool));
@@ -7,6 +7,7 @@ import { probedMissingColumnRefusal } from "./schema-drift-sentence.js";
7
7
  import { canonicalJsonStringify } from "@sema-agent/core";
8
8
  import { ensureIndexes } from "./ensure-index.js";
9
9
  import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, StoreCorruptError } from "./sql-json-column.js";
10
+ import { sealStoreClass, sealStoreObject } from "./store-boundary.js";
10
11
  export function writerOfSqlRuleStore(store) {
11
12
  const w = Reflect.get(store, PERMISSION_RULE_WRITER);
12
13
  if (w === null || typeof w !== "object")
@@ -494,7 +495,7 @@ class SqlDurableRulePartition {
494
495
  get [PERMISSION_RULE_WRITER]() {
495
496
  return this.writer;
496
497
  }
497
- writer = {
498
+ writer = sealStoreObject({
498
499
  nextDot: async () => {
499
500
  await this.ensureBucket();
500
501
  for (let attempt = 0; attempt < NEXT_DOT_MAX_ATTEMPTS; attempt++) {
@@ -561,8 +562,9 @@ class SqlDurableRulePartition {
561
562
  }
562
563
  return { rev: opts.expectedRev + 1 };
563
564
  },
564
- };
565
+ }, "sql-durable-rule-partition-writer", { nextDot: "exit", readRaw: "exit", apply: "exit" });
565
566
  }
567
+ sealStoreClass(SqlDurableRulePartition, "sql-durable-rule-partition", { list: "exit", quarantined: "exit" });
566
568
  export class SqlDurableRulePartitionProvider {
567
569
  db;
568
570
  now;
@@ -583,6 +585,7 @@ export class SqlDurableRulePartitionProvider {
583
585
  return new SqlDurableRulePartition(this.db, { kind: "local-owner" }, this.now);
584
586
  }
585
587
  }
588
+ sealStoreClass(SqlDurableRulePartitionProvider, "sql-durable-rule-partition-provider", { forPrincipal: "exit", forLocalOwner: "exit" });
586
589
  export function recordOwnerOf(record) {
587
590
  return readRuleOwner(record.owner ?? { kind: "principal", principal: record.principal });
588
591
  }
@@ -734,6 +737,7 @@ export class SqlRuleApprovalRecordStore {
734
737
  return affected === 1;
735
738
  }
736
739
  }
740
+ sealStoreClass(SqlRuleApprovalRecordStore, "sql-rule-approval-record-store", { get: "exit", create: "exit", discardPendingRecord: "exit", cas: "exit" });
737
741
  export const RULE_IMPORT_TICKET_GC_GRACE_MS = 10 * 60_000;
738
742
  export function buildRulePayloadHash(candidates) {
739
743
  return createHash("sha256").update(canonicalJsonStringify(candidates), "utf8").digest("hex");
@@ -853,11 +857,14 @@ export class SqlRuleImportTicketStore {
853
857
  };
854
858
  }
855
859
  }
860
+ sealStoreClass(SqlRuleImportTicketStore, "sql-rule-import-ticket-store", {
861
+ mint: "exit", consume: "exit", release: "exit", settle: "exit", peek: "exit", reclaim: "exit",
862
+ });
856
863
  export const RULE_PENDING_APPROVAL_RETENTION_MS = 24 * 60 * 60_000;
857
864
  export const RULE_REAP_BATCH = 500;
858
865
  export function createSqlPermissionRuleStores(db, now) {
859
866
  const q = (mysql, pg) => (db.dialect === "mysql" ? mysql : pg);
860
- return {
867
+ return sealStoreObject({
861
868
  durable: new SqlDurableRulePartitionProvider(db, now),
862
869
  approvals: new SqlRuleApprovalRecordStore(db, now),
863
870
  tickets: new SqlRuleImportTicketStore(db),
@@ -873,6 +880,6 @@ export function createSqlPermissionRuleStores(db, now) {
873
880
  throw new Error(`${PERMISSION_RULE_TABLE} bucket count came back unreadable (${String(rows[0]?.["n"])})`);
874
881
  return n;
875
882
  },
876
- };
883
+ }, "sql-permission-rule-stores", { reapExpired: "exit", countBuckets: "exit" });
877
884
  }
878
885
  //# sourceMappingURL=permission-rule-store-sql.js.map
@@ -1,6 +1,7 @@
1
1
  import { decideQuota } from "../observability/cost-quota.js";
2
2
  import { PgWriteBehindCounter } from "./write-behind-counter.js";
3
3
  import { ensureIndexes } from "./ensure-index.js";
4
+ import { sealStoreClass } from "./store-boundary.js";
4
5
  const COST_QUOTA_TABLE = { table: "cost_quota", keyCol: "scope_key", valCol: "micro_usd" };
5
6
  export class PgCostQuota {
6
7
  counter;
@@ -52,6 +53,9 @@ export class PgCostQuota {
52
53
  this.counter.stop();
53
54
  }
54
55
  }
56
+ sealStoreClass(PgCostQuota, "pg-cost-quota", {
57
+ setLimits: "exit", check: "exit", add: "exit", flush: "exit", refresh: "exit", reap: "exit", startRefresh: "exit", stop: "exit",
58
+ });
55
59
  export const PG_COST_QUOTA_SCHEMA = [
56
60
  `CREATE TABLE IF NOT EXISTS cost_quota (
57
61
  scope_key VARCHAR(190) COLLATE "C" NOT NULL,
@@ -1,10 +1,11 @@
1
1
  import { createLogger } from "../observability/logger.js";
2
2
  import pg from "pg";
3
3
  import { recordFailOpen } from "../observability/fail-open.js";
4
+ import { markErrorOrigin } from "./error-origin.js";
4
5
  import { acquireAdvisoryLock, releaseAdvisoryLock } from "./ensure-schema-lock-policy.js";
5
6
  import { RUN_IDENTITY_COLUMNS } from "../identity-source.js";
6
7
  import { ensureSchema as ensureSessionSchema } from "../stores-sql/pg-session-storage.js";
7
- import { sessionExtDdl } from "./session-ext-table.js";
8
+ import { SESSION_EXT_INDEXES, sessionExtDdl } from "./session-ext-table.js";
8
9
  import { CHECKPOINT_HOST_SESSION_COLUMN } from "./checkpoint-index-columns.js";
9
10
  import { ensureSchema as ensureToolResultSchema } from "../stores-sql/tool-result-store-sql.js";
10
11
  import { ensureSchema as ensureCostQuotaSchema } from "./pg-cost-quota.js";
@@ -480,8 +481,29 @@ export function createPgPool(opts, onIdleClientError) {
480
481
  checkoutGuards.delete(client);
481
482
  }
482
483
  });
484
+ markPgDriverOrigins(pool);
483
485
  return pool;
484
486
  }
487
+ function markPgDriverOrigins(pool) {
488
+ const query = pool.query.bind(pool);
489
+ const connect = pool.connect.bind(pool);
490
+ shadowMethod(pool, "query", (...args) => markRejection(query(...args)));
491
+ shadowMethod(pool, "connect", (...args) => (args.length > 0 ? connect(...args) : markRejection(connect())));
492
+ pool.on("connect", (client) => {
493
+ const clientQuery = client.query.bind(client);
494
+ shadowMethod(client, "query", (...args) => markRejection(clientQuery(...args)));
495
+ });
496
+ }
497
+ function shadowMethod(target, key, fn) {
498
+ Object.defineProperty(target, key, { value: fn, writable: true, configurable: true, enumerable: false });
499
+ }
500
+ function markRejection(out) {
501
+ if (out === null || (typeof out !== "object" && typeof out !== "function") || typeof out.then !== "function")
502
+ return out;
503
+ return out.then(undefined, (err) => {
504
+ throw markErrorOrigin(err, "driver");
505
+ });
506
+ }
485
507
  export const ENSURE_SCHEMA_LOCK_KEY = 4906;
486
508
  const ENSURE_SCHEMA_LOCK_ID = { dialect: "pg", keys: [ENSURE_SCHEMA_LOCK_KEY] };
487
509
  export async function acquirePgEnsureSchemaLock(client, tag) {
@@ -501,6 +523,7 @@ export async function ensurePgSchema(pool) {
501
523
  await client.query(markIfAbsentSql("pg"), [SKILL_EXEMPTION_CUTOFF_ID, Date.now()]);
502
524
  await ensureSessionSchema(client);
503
525
  await client.query(sessionExtDdl("pg"));
526
+ await ensureIndexes((text, params) => client.query(text, params), "pg", SESSION_EXT_INDEXES);
504
527
  await ensureToolResultSchema(client);
505
528
  await ensureCostQuotaSchema(client);
506
529
  await ensureRateLimitSchema(client);
@@ -1,5 +1,6 @@
1
1
  import { PgWriteBehindCounter } from "./write-behind-counter.js";
2
2
  import { ensureIndexes } from "./ensure-index.js";
3
+ import { sealStoreClass } from "./store-boundary.js";
3
4
  const RATE_LIMIT_TABLE = { table: "rate_limit", keyCol: "limit_key", valCol: "hits" };
4
5
  export class PgRateLimiter {
5
6
  counter;
@@ -50,6 +51,7 @@ export class PgRateLimiter {
50
51
  this.counter.stop();
51
52
  }
52
53
  }
54
+ sealStoreClass(PgRateLimiter, "pg-rate-limiter", { setLimits: "exit", check: "exit", sweep: "exit", flush: "exit", refresh: "exit", startRefresh: "exit", stop: "exit" });
53
55
  export const PG_RATE_LIMITER_SCHEMA = [
54
56
  `CREATE TABLE IF NOT EXISTS rate_limit (
55
57
  limit_key VARCHAR(190) COLLATE "C" NOT NULL,
@@ -1,4 +1,5 @@
1
- import { FileError, type ExecutionError, type RemoteExecutionError, type FileInfo, type Result, type SymlinkChain } from "@sema-agent/core";
1
+ import { FileError, type ExecutionError, type RemoteExecutionError, type BoundFileObject, type FileInfo, type Result, type SymlinkChain } from "@sema-agent/core";
2
+ import { type BoundReadLane } from "./remote-shell.js";
2
3
  export interface ShellRunResult {
3
4
  stdout: string;
4
5
  stderr: string;
@@ -11,6 +12,9 @@ export interface PosixShellFsDeps {
11
12
  resolve: (p: string) => string;
12
13
  /** R5:临时件落点。返回目录 ⇒ `mktemp -p <dir>`(workspace 腿);null ⇒ `mktemp -t`(ssh,系统 temp)。 */
13
14
  tempDir: () => string | null;
15
+ /** 绑定读的传输:该腿**不丢字节**的那一条(k8s `execData` · local-docker 二进制 `docker exec` · adb `shell -T` 二进制 · ssh 按本答上界封顶的收集器),
16
+ * 起动形与该腿 `readBinaryFile` 同一形(适配器自己的固定命令)。 */
17
+ boundLane: BoundReadLane;
14
18
  }
15
19
  export interface PosixShellFs {
16
20
  readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
@@ -34,6 +38,7 @@ export interface PosixShellFs {
34
38
  suffix?: string;
35
39
  abortSignal?: AbortSignal;
36
40
  }): Promise<Result<string, FileError>>;
41
+ openBound(p: string, abortSignal?: AbortSignal): Promise<Result<BoundFileObject, FileError>>;
37
42
  }
38
43
  export declare function createPosixShellFs(deps: PosixShellFsDeps): PosixShellFs;
39
44
  //# sourceMappingURL=posix-shell-fs.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { anchorUnder } from "./exec-path-law.js";
3
3
  import { FileError, } from "@sema-agent/core";
4
- import { runSymlinkChain, shellQuote, kindFromMode } from "./remote-shell.js";
4
+ import { openBoundOverShell, runSymlinkChain, shellQuote, kindFromMode } from "./remote-shell.js";
5
5
  import { classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
6
6
  const ok = (value) => ({ ok: true, value });
7
7
  export function createPosixShellFs(deps) {
@@ -121,6 +121,9 @@ export function createPosixShellFs(deps) {
121
121
  const r = await run(cmd, options?.abortSignal);
122
122
  return tempResult(r);
123
123
  },
124
+ async openBound(p, abortSignal) {
125
+ return openBoundOverShell(resolve(p), deps.boundLane, abortSignal);
126
+ },
124
127
  };
125
128
  return fs;
126
129
  }
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { type LaneHomeConfig } from "../lane-home-probe.js";
3
- import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type SymlinkChain, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory } from "@sema-agent/core";
3
+ import { type ConnectResult } from "./remote-env-connect-seat.js";
4
+ import { FileError, ExecutionError, RemoteExecutionError, 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 } from "@sema-agent/core";
4
5
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
5
6
  export interface AdbEnvConfig extends LaneHomeConfig {
6
7
  /** Device serial or `host:port` (TCP). `host:port` triggers `adb connect` on connect()/reconnect(). */
@@ -22,13 +23,6 @@ export interface AdbEnvConfig extends LaneHomeConfig {
22
23
  export interface AdbEnvLogger {
23
24
  warn(event: string, fields?: Record<string, unknown>): void;
24
25
  }
25
- type ConnectResult = {
26
- ok: true;
27
- value: WorkspaceHandle;
28
- } | {
29
- ok: false;
30
- error: RemoteExecutionError;
31
- };
32
26
  /**
33
27
  * Test seam ([ref] §9.3 fault-injection): inject `spawn` and/or a `connectOnce` that replays an error
34
28
  * sequence, so the retry policy + first-attach behaviour is deterministically testable without a real device
@@ -69,15 +63,19 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
69
63
  * 同姿态的 ssh 腿早有先例(`remote-env-ssh.ts`「destroy 后一律不复活,显式 connect/reconnect 同禁」),
70
64
  * 五条兄弟腿全部 fail-closed,只有 adb 例外 —— 补齐它。 */
71
65
  private destroyed;
72
- /** 在飞的那一次懒连接(单飞记忆)——见 {@link ensureConnected}。settle 即清,**不缓存败诺**。 */
66
+ /** 在飞的那一次连接(单飞记忆:显式 connect、首用的懒连接、reconnect 共用)。落定即清(按身份),**不缓存败诺**。 */
73
67
  private connectPromise?;
74
68
  /** core `ExecutionEnv.homeDir`([ref]):设备 shell 里执行用户的 home —— 声明在场 ⇒ 声明(首连时与设备 shell 的 `$HOME` 对证过),否则首连时 `adb shell` 实测的 `$HOME`(Android 常为 `/` 或 `/data/local/tmp` 一类,读到什么填什么,绝对即可),见 {@link LaneHomeSeat}。读时才取(没声明时连上之前缺席)。 */
75
69
  get homeDir(): string | undefined;
76
70
  /** 家目录的座(声明在场 ⇒ 首连时对证 · 否则首连时实测 · 实测缺席 ⇒ 缺席 + 一行 warn)。设备 shell 答得出 `$HOME`,命令里的 `~` / `$HOME` 展开的就是它 ——
77
71
  * 声明与它不一致时规则守的是声明那个目录,所以与其余读得出家目录的车道同一只判官、同一句拒。 */
78
72
  private readonly homeSeat;
79
- /** 家目录:设备答了而与声明不一致(或答的不是绝对路径)⇒ 这只环境的拒,记一次、此后每次连接原样回它(同一台设备同一个答案:不重试、不再问)。 */
80
- private homeRefusal?;
73
+ /**
74
+ * 建连座(`remote-env-connect-seat.ts`):`connect` 的唯一次序(记住的拒 ⇒ 原样再答 · 一次连接在飞 ⇒ 等它 · 已连 ⇒ 当下的把手、不再握手 · 否则握手一次),
75
+ * 并持有这只环境记住的拒 —— 家目录:设备答了而与声明不一致(或答的不是绝对路径)⇒ 立定的拒(`env.home_unconfirmed`),记一次、此后每次连接原样回它
76
+ * (同一台设备同一个答案:不重试、不再问)。
77
+ */
78
+ private readonly seat;
81
79
  /** core 7.49.0 [ref]:shell 子进程真拿到的 `CDPATH` 条目 —— 本车道的命令跑在 Android 设备的 shell 里(设备自己的 env),本类不读那份 env ⇒ `"unknown"`(core 契约:说不出就说 unknown,
82
80
  * 不许报一张没读过的空表;删除守卫因此对裸 `cd <相对名>` 之后的删除问人,而不是按「没有 CDPATH」放行)。 */
83
81
  readonly cdPath: "unknown";
@@ -98,13 +96,14 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
98
96
  ok: false;
99
97
  error: RemoteExecutionError;
100
98
  }>;
99
+ /** 握手一次(含重试策略):建连座在没有记住的拒、没有在飞、没连上时起它。 */
100
+ private handshake;
101
101
  /**
102
102
  * 懒连接:**首次调用任一面**(exec / execStream / 文件面)时自动 `connect()`(含重试策略),在飞的
103
- * 那一次由全部调用者共用,失败**不缓存**(下一次调用重新试),`destroyed` 是终态不复活。
103
+ * 那一次由全部调用者共用(建连座),失败**不缓存**(下一次调用重新试;记住的拒例外,原样再答),`destroyed` 是终态不复活。
104
104
  *
105
- * 🔴 为什么这条必须在 adapter 里:core 的 task/run 车道**没有任何人**替 adapter 调 `connect()` ——
106
- * 工厂铸出 env 就直接用它的 exec/文件面(本仓亲核:`src/` 里对执行环境的 `.connect()` 只有 bench 与
107
- * 生命周期面自己)。所以一条「设了未连闸、又不自连」的腿,它的每一次工具调用都被自家的闸挡回;
105
+ * 🔴 为什么这条必须在 adapter 里:面可能在任何人连它之前就被调到(工厂交出之前的附件物化层、委派子代的放置检查;
106
+ * 引擎在本腿的建立座上显式 `connect()`,那常常不是第一次 —— 建连座让它幂等)。所以一条「设了未连闸、又不自连」的腿,它的每一次工具调用都被自家的闸挡回;
108
107
  * 那不是 adb 特有语义,而是与 ssh([ref] 同一实红点)/ k8s / e2b / local-docker / device 不均匀。
109
108
  * 未连时的错误文案因此从「call connect() first」(这条车道上没人能照做)换成**真试过之后**的拒因。
110
109
  */
@@ -194,6 +193,9 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
194
193
  private push;
195
194
  private posixFsInst?;
196
195
  private get posixFs();
196
+ /** 绑定读一答的往返:与 {@link readBinaryFile} 同一道门(已拆 ⇒ 拒;首用建连)、同一只起动(`shell -T -- <命令>`)、同一只二进制收集器与数据界;
197
+ * 设备传输半途断 ⇒ `transport_lost`(与 {@link execCall} 同判)。 */
198
+ private boundExec;
197
199
  fileInfo(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo, FileError>>;
198
200
  listDir(p: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
199
201
  readLink(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
@@ -201,6 +203,8 @@ export declare class RemoteAdbExecutionEnv implements RemoteExecutionEnv {
201
203
  * 循环(上限 64、只锚不折、遇环即止),脚本与读法的单一属主在 `remote-shell.ts`,四条 shell 腿共用同一份。
202
204
  * 缺席读法用不上(本腿在场);`ok:false` 由 core 读作「这一次没有跳」,永不是拒绝。 */
203
205
  canonicalChain(p: string, abortSignal?: AbortSignal): Promise<Result<SymlinkChain, FileError>>;
206
+ /** 绑定读(逐答重绑,Linux 读法 —— Android 是 Linux 内核;脚本与读法的单一属主在 `remote-shell.ts`,四条 shell 腿共用同一份)。 */
207
+ openBound(p: string, abortSignal?: AbortSignal): Promise<Result<BoundFileObject, FileError>>;
204
208
  canonicalPath(p: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
205
209
  exists(p: string, abortSignal?: AbortSignal): Promise<Result<boolean, FileError>>;
206
210
  createDir(p: string, options?: {
@@ -5,6 +5,7 @@ import fs from "node:fs/promises";
5
5
  import { spawn } from "node:child_process";
6
6
  import { hostSubprocessEnv } from "../server-secret-env.js";
7
7
  import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
8
+ import { connectRefusal, createConnectSeat } from "./remote-env-connect-seat.js";
8
9
  import { appendFileStaged, rewriteAppend, buildCwdEnvCommand, createCwdGuardedCall, shellQuote, armPipeDestroyGrace, writeFileStaged } from "./remote-shell.js";
9
10
  import { OWNER_ONLY_FILE_MODE } from "./owner-only-file-mode.js";
10
11
  import { signalNumber } from "./host-platform.js";
@@ -17,6 +18,7 @@ import { createPosixShellFs } from "./posix-shell-fs.js";
17
18
  import { createLogger } from "../observability/logger.js";
18
19
  import { seatText } from "../trace/exit-seat.js";
19
20
  const PROVIDER = "adb";
21
+ const ADB_SETTLING_STATES = new Set(["bootloader", "recovery", "sideload", "rescue", "connecting", "authorizing"]);
20
22
  const DEVICE_PUSHED_FILE_MODE = "0644";
21
23
  const ok = (value) => ({ ok: true, value });
22
24
  const unsupported = (op) => ({
@@ -43,7 +45,18 @@ export class RemoteAdbExecutionEnv {
43
45
  return this.homeSeat.home;
44
46
  }
45
47
  homeSeat;
46
- homeRefusal;
48
+ seat = createConnectSeat({
49
+ live: () => (this.connected && !this.destroyed ? this.workspaceHandle() : undefined),
50
+ inFlight: () => this.connectPromise,
51
+ establish: (config) => {
52
+ const walk = this.handshake(config).finally(() => {
53
+ if (this.connectPromise === walk)
54
+ this.connectPromise = undefined;
55
+ });
56
+ this.connectPromise = walk;
57
+ return walk;
58
+ },
59
+ });
47
60
  cdPath = "unknown";
48
61
  withheldEnv = [];
49
62
  cfg;
@@ -74,8 +87,9 @@ export class RemoteAdbExecutionEnv {
74
87
  async connect(config) {
75
88
  if (this.destroyed)
76
89
  return { ok: false, error: new RemoteExecutionError("connect_failed", "ADB env destroyed — create a fresh env instead of reviving") };
77
- if (this.homeRefusal)
78
- return { ok: false, error: this.homeRefusal };
90
+ return this.seat.connect(config);
91
+ }
92
+ handshake(config) {
79
93
  const policy = {
80
94
  retryableCodes: ["auth_transient", "connect_failed", "timeout"],
81
95
  maxAttempts: this.cfg.retry.maxAttempts,
@@ -86,7 +100,7 @@ export class RemoteAdbExecutionEnv {
86
100
  const halt = config?.abortSignal ? AbortSignal.any([config.abortSignal, refused.signal]) : refused.signal;
87
101
  return withRetry(async () => {
88
102
  const r = await once(config?.abortSignal);
89
- if (!r.ok && this.homeRefusal)
103
+ if (!r.ok && this.seat.held)
90
104
  refused.abort();
91
105
  return r;
92
106
  }, policy, { signal: halt });
@@ -96,19 +110,13 @@ export class RemoteAdbExecutionEnv {
96
110
  return { ok: false, error: new RemoteExecutionError("connect_failed", "ADB env destroyed — no lazy reconnect after teardown") };
97
111
  if (this.connected)
98
112
  return { ok: true };
99
- const started = this.connectPromise ?? this.connect();
100
- this.connectPromise = started;
101
113
  try {
102
- const r = await started;
114
+ const r = await this.connect();
103
115
  return r.ok ? { ok: true } : { ok: false, error: r.error };
104
116
  }
105
117
  catch (err) {
106
118
  return { ok: false, error: new RemoteExecutionError("connect_failed", `adb lazy-connect threw: ${err instanceof Error ? err.message : String(err)}`) };
107
119
  }
108
- finally {
109
- if (this.connectPromise === started)
110
- this.connectPromise = undefined;
111
- }
112
120
  }
113
121
  async connectOnce(signal) {
114
122
  if (signal?.aborted)
@@ -147,10 +155,7 @@ export class RemoteAdbExecutionEnv {
147
155
  return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted while reading the device's home") };
148
156
  const verdict = this.homeSeat.settle(home.ok && home.value.exitCode === 0 ? { ok: true, stdout: home.value.stdout } : { ok: false, reason: home.ok ? `exit ${home.value.exitCode}` : home.error.message });
149
157
  if (verdict !== undefined) {
150
- const error = new RemoteExecutionError("connect_failed", verdict.refusal);
151
- if (verdict.answered)
152
- this.homeRefusal = error;
153
- return { ok: false, error };
158
+ return verdict.answered ? this.seat.answer(connectRefusal("home_unconfirmed", verdict.refusal)) : { ok: false, error: new RemoteExecutionError("connect_failed", verdict.refusal) };
154
159
  }
155
160
  }
156
161
  this.connected = true;
@@ -161,6 +166,8 @@ export class RemoteAdbExecutionEnv {
161
166
  }
162
167
  if (/device.*not found|no devices/.test(err))
163
168
  return { ok: false, error: new RemoteExecutionError("connect_failed", `device ${this.cfg.serial} not found`) };
169
+ if (ADB_SETTLING_STATES.has(state))
170
+ return connectRefusal("target_settling", `device ${this.cfg.serial} is not usable yet (state=${state}) — it answers from a state it passes through on its way back to \`device\` (rebooting, an update applying, the transport handshake still running)`);
164
171
  return { ok: false, error: new RemoteExecutionError("auth_failed", `device ${this.cfg.serial} unusable (state=${state || "?"})`) };
165
172
  }
166
173
  async suspendVM(_options) {
@@ -434,8 +441,24 @@ export class RemoteAdbExecutionEnv {
434
441
  run: (cmd, abortSignal) => this.fsExec(cmd, abortSignal),
435
442
  resolve: (p) => this.resolve(p),
436
443
  tempDir: () => this.cwd,
444
+ boundLane: { run: (cmd, _cap, abortSignal) => this.boundExec(cmd, abortSignal), payload: "raw", boundSec: this.cfg.dataTimeoutMs / 1000 },
437
445
  }));
438
446
  }
447
+ async boundExec(cmd, abortSignal) {
448
+ if (this.destroyed)
449
+ return { ok: false, error: new ExecutionError("shell_unavailable", "execution env already destroyed") };
450
+ const ready = await this.ensureConnected();
451
+ if (!ready.ok)
452
+ return { ok: false, error: lazyConnectExecError(ready.error) };
453
+ const r = await this.adb(this.deviceShellArgs(cmd), { signal: abortSignal, timeoutMs: this.cfg.dataTimeoutMs, binary: true });
454
+ if (!r.ok)
455
+ return r;
456
+ if (r.value.exitCode !== 0 && isAdbTransportLost(r.value.stderr)) {
457
+ this.connected = false;
458
+ return { ok: false, error: new ExecutionError("transport_lost", `adb transport lost mid-command: ${r.value.stderr.trim().slice(0, 200)} — reconnect + retry per idempotency`) };
459
+ }
460
+ return ok({ stdout: r.value.stdoutBytes ?? new Uint8Array(0), stderr: r.value.stderr, exitCode: r.value.exitCode });
461
+ }
439
462
  async fileInfo(p, abortSignal) {
440
463
  return this.posixFs.statFileInfo(p, abortSignal);
441
464
  }
@@ -448,6 +471,9 @@ export class RemoteAdbExecutionEnv {
448
471
  async canonicalChain(p, abortSignal) {
449
472
  return this.posixFs.canonicalChain(p, abortSignal);
450
473
  }
474
+ async openBound(p, abortSignal) {
475
+ return this.posixFs.openBound(p, abortSignal);
476
+ }
451
477
  async canonicalPath(p, abortSignal) {
452
478
  return this.posixFs.canonicalPath(p, abortSignal);
453
479
  }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * 远端执行环境的**建连座** —— 每只远端适配器的 `connect` 只走这一只座:幂等的次序、记住的拒、拒码的拼法,都只写在这里。
3
+ *
4
+ * ## 律(两条)
5
+ * ① **`connect` 幂等**(引擎会在本腿的建立座上显式调它,而那常常不是第一次建立:委派子代在放置检查期已懒连过、工厂交出环境之前往里写过附件、
6
+ * 停驻腿交回的是同一只活环境)。答案的次序只有一种,写在 {@link createConnectSeat}:
7
+ * 这只环境**记住了拒** ⇒ 原样再答(不再建 pod / 沙箱 / 连接、不再读、不再等)· 有一走**正在建立**它(一次 connect、首用的懒连、一次恢复)⇒ 等**那一走**的答,
8
+ * 等它的调用方的取消只退**它自己**的等待(那一走照常服务其余等待者)· **已连上** ⇒ 当下的把手、什么都不建 · 都不是 ⇒ 起一走(适配器把它记成自己的在飞那一走;
9
+ * 起它的调用方的取消信号交给那一走,由那一走自己收场 —— 落定之前不交回,落定之后它就不再在飞)。
10
+ * 车道只答三件事实(记住的拒归座,在飞那一走与已连的把手归车道),不各写一份次序。
11
+ * ② **记住的拒 = 立定的拒 = 码在引擎可重试表之外**,三者是一个事实:拒因按事实词拼码只走 {@link connectRefusal}(拒因表 {@link CONNECT_REFUSAL_CODE},
12
+ * 闭集:表外的词编译红);立定与否不另写,读引擎那一张可重试表(`isRetryableRemoteErrorCode`)—— 码在表外 ⇒ 立定 ⇒ 座记住它;码在表内 ⇒ 会自愈 ⇒
13
+ * 座不记、下一次建连重来。座只收表拼出来的立定拒({@link StandingRefusal} 带品牌,别处铸不出),所以「记住了一只可重试码的拒」写不出来。
14
+ * 表外的码不是 `unknown` / `aborted`(那两词是引擎读作「不说环境本身」的两类,拒因表一个都不拼)。
15
+ *
16
+ * 车道每次尝试各自的答(`connect_failed` / `timeout` / 私钥被拒的 `auth_failed` / 不支持的 `unsupported` …)不经本表、不被记住 —— 那是一次尝试的读数,不是这只环境的立定事实。
17
+ */
18
+ import { RemoteExecutionError, type RemoteConnectConfig, type WorkspaceHandle } from "@sema-agent/core";
19
+ /** 一次建连的答(core `RemoteExecutionEnv.connect` 的返回形)。 */
20
+ export type ConnectResult = {
21
+ ok: true;
22
+ value: WorkspaceHandle;
23
+ } | {
24
+ ok: false;
25
+ error: RemoteExecutionError;
26
+ };
27
+ /**
28
+ * **拒因表**:远端车道在建连时答的、需要判「立定还是会自愈」的拒,按事实词 → 码。立定的事实拼引擎可重试表之外的码(本仓 `env.*` 族,
29
+ * 或引擎既有的永久词);会自愈的事实拼可重试表之内的码。
30
+ *
31
+ * 立定(此后每一次建连都是同一个答;座记住它):
32
+ * · `home_unconfirmed` —— 环境**答了**自己的 `$HOME`,与主张不一致 / 不是绝对路径(k8s / e2b / local-docker / adb / ssh;host 在构造时以同一码拒);
33
+ * · `snapshot_bucket_missing` —— k8s:对象存储对 `latest` 或归档的读答 `404 NoSuchBucket`;
34
+ * · `occupant_missing` —— k8s:会话行在、却不带住客令牌(`session.incarnation_missing`,存储数据缺陷);
35
+ * · `pod_admission_refused` —— k8s:集群建出 pod 后在它跑起来之前就判它 `Failed` / `Succeeded`(kubelet 准入拒:`SysctlForbidden`、节点亲和不中、节点放不下 …);
36
+ * · `credential_broker_refused` —— k8s:成员凭据中继在这只 pod 上建不起来(中继表被拒、部署补丁够得着中继);
37
+ * · `archive_unrecoverable` —— k8s 空间车道:`latest` 指的归档在桶里、却在有界重试之后仍取不回;
38
+ * · `workspace_fill_refused` —— 隔离车道:恢复点的填充(会话附件 / 项目包种子)放不进、换一台机器也是同一个答(`workspace-fill.ts` 的 `standing`);
39
+ * · `host_key_rejected` —— ssh:目标机出示的主机密钥过不了部署钉的判(指纹不符、条目换了钥、被吊销、没有条目;钉的文件读不出那一形不在此列)。
40
+ * 会自愈(座不记;下一次建连重来):
41
+ * · `pod_quota_exceeded` —— k8s:建 pod 撞命名空间的 ResourceQuota(403 `exceeded quota`):别的 pod 结束就有额度;
42
+ * · `occupant_unreadable` —— k8s:读会话住客超时或店报错(重跑这条 run 会再读);
43
+ * · `target_settling` —— adb:设备答的是它在走向可用途中会经过的态(重启进 / 出引导、恢复、刷机模式,传输握手还在进行)。
44
+ */
45
+ export declare const CONNECT_REFUSAL_CODE: {
46
+ readonly home_unconfirmed: "env.home_unconfirmed";
47
+ readonly snapshot_bucket_missing: "env.snapshot_bucket_missing";
48
+ readonly occupant_missing: "env.occupant_missing";
49
+ readonly pod_admission_refused: "env.sandbox_refused";
50
+ readonly credential_broker_refused: "env.sandbox_refused";
51
+ readonly archive_unrecoverable: "env.sandbox_refused";
52
+ readonly workspace_fill_refused: "env.sandbox_refused";
53
+ readonly host_key_rejected: "env.host_key_rejected";
54
+ readonly pod_quota_exceeded: "connect_failed";
55
+ readonly occupant_unreadable: "connect_failed";
56
+ readonly target_settling: "connect_failed";
57
+ };
58
+ export type ConnectRefusalFact = keyof typeof CONNECT_REFUSAL_CODE;
59
+ /** 拒因表拼得出的码。 */
60
+ export type ConnectRefusalCode = (typeof CONNECT_REFUSAL_CODE)[ConnectRefusalFact];
61
+ declare const STANDING: unique symbol;
62
+ /** 立定的拒:只由 {@link connectRefusal} 铸(品牌位),座只记它。 */
63
+ export type StandingRefusal = {
64
+ readonly ok: false;
65
+ readonly error: RemoteExecutionError;
66
+ readonly standing: true;
67
+ readonly [STANDING]: true;
68
+ };
69
+ /** 会自愈的拒:这一次不开,下一次建连重来。 */
70
+ export type PassingRefusal = {
71
+ readonly ok: false;
72
+ readonly error: RemoteExecutionError;
73
+ readonly standing: false;
74
+ };
75
+ /** 一次建连的拒,带它是不是立定的。 */
76
+ export type ConnectRefusal = StandingRefusal | PassingRefusal;
77
+ /** 按事实词拼一次建连的拒(码从拒因表来,立定与否从引擎的可重试表来)。句子 = 车道的拒句原文。 */
78
+ export declare function connectRefusal(fact: ConnectRefusalFact, message: string, cause?: Error): ConnectRefusal;
79
+ /** 建连座向车道要的三件事实(记住的拒由座自己持有)。 */
80
+ export interface ConnectLane {
81
+ /** 已连上(且没有一走在飞)⇒ 当下的把手;否则 `undefined`。 */
82
+ live(): WorkspaceHandle | undefined;
83
+ /** 正在建立这只环境的那一走(一次 connect、首用的懒连、一次恢复);没有 ⇒ `undefined`。 */
84
+ inFlight(): Promise<ConnectResult> | undefined;
85
+ /**
86
+ * 起一走:车道把它记成自己的在飞那一走({@link inFlight} 随即答它),落定时按身份清。`config.abortSignal` 是起它的调用方的:那一走被它取消时答 `aborted`
87
+ * 并收掉自己建到一半的东西;共享尝试不带调用方信号的车道(ssh)经 {@link withdrawOnAbort} 只退这一位调用方的等待。
88
+ */
89
+ establish(config?: RemoteConnectConfig): Promise<ConnectResult>;
90
+ }
91
+ /** 一只环境的建连座(活对象:持有记住的拒)。 */
92
+ export interface ConnectSeat {
93
+ /** 这只环境记住的拒(立定;直到销毁)。 */
94
+ readonly held: StandingRefusal | undefined;
95
+ /** 交出一次拒:立定的记住(先到者为准),会自愈的不记;原样交回(调用方以它作答)。 */
96
+ answer(refusal: ConnectRefusal): ConnectRefusal;
97
+ /** `connect` 的唯一入口(次序见文件头 ①)。 */
98
+ connect(config?: RemoteConnectConfig): Promise<ConnectResult>;
99
+ }
100
+ /** 调用方的取消只退它自己的等待:共享的那一走不动。已取消 ⇒ 当场答 `aborted`。 */
101
+ export declare function withdrawOnAbort(walk: Promise<ConnectResult>, signal: AbortSignal | undefined): Promise<ConnectResult>;
102
+ /** 铸一只建连座(每只远端环境一只)。 */
103
+ export declare function createConnectSeat(lane: ConnectLane): ConnectSeat;
104
+ export {};
105
+ //# sourceMappingURL=remote-env-connect-seat.d.ts.map
@@ -0,0 +1,72 @@
1
+ import { RemoteExecutionError, isRetryableRemoteErrorCode } from "@sema-agent/core";
2
+ export const CONNECT_REFUSAL_CODE = {
3
+ home_unconfirmed: "env.home_unconfirmed",
4
+ snapshot_bucket_missing: "env.snapshot_bucket_missing",
5
+ occupant_missing: "env.occupant_missing",
6
+ pod_admission_refused: "env.sandbox_refused",
7
+ credential_broker_refused: "env.sandbox_refused",
8
+ archive_unrecoverable: "env.sandbox_refused",
9
+ workspace_fill_refused: "env.sandbox_refused",
10
+ host_key_rejected: "env.host_key_rejected",
11
+ pod_quota_exceeded: "connect_failed",
12
+ occupant_unreadable: "connect_failed",
13
+ target_settling: "connect_failed",
14
+ };
15
+ const ENGINE_FIELD_STILL_CLOSED = true;
16
+ void ENGINE_FIELD_STILL_CLOSED;
17
+ function mintRefusalError(code, message, cause) {
18
+ return new RemoteExecutionError(code, message, cause);
19
+ }
20
+ function standsFor(code) {
21
+ return !isRetryableRemoteErrorCode(code);
22
+ }
23
+ export function connectRefusal(fact, message, cause) {
24
+ const code = CONNECT_REFUSAL_CODE[fact];
25
+ const error = mintRefusalError(code, message, cause);
26
+ return standsFor(code) ? { ok: false, error, standing: true } : { ok: false, error, standing: false };
27
+ }
28
+ export function withdrawOnAbort(walk, signal) {
29
+ if (signal === undefined)
30
+ return walk;
31
+ if (signal.aborted)
32
+ return Promise.resolve({ ok: false, error: new RemoteExecutionError("aborted", "connect aborted") });
33
+ return new Promise((resolve, reject) => {
34
+ const onAbort = () => resolve({ ok: false, error: new RemoteExecutionError("aborted", "connect aborted while waiting") });
35
+ signal.addEventListener("abort", onAbort, { once: true });
36
+ walk.then((r) => {
37
+ signal.removeEventListener("abort", onAbort);
38
+ resolve(r);
39
+ }, (e) => {
40
+ signal.removeEventListener("abort", onAbort);
41
+ reject(e);
42
+ });
43
+ });
44
+ }
45
+ export function createConnectSeat(lane) {
46
+ let held;
47
+ return {
48
+ get held() {
49
+ return held;
50
+ },
51
+ answer(refusal) {
52
+ if (refusal.standing && held === undefined)
53
+ held = refusal;
54
+ return refusal;
55
+ },
56
+ connect(config) {
57
+ if (held !== undefined)
58
+ return Promise.resolve(held);
59
+ const signal = config?.abortSignal;
60
+ const walking = lane.inFlight();
61
+ if (walking !== undefined)
62
+ return withdrawOnAbort(walking, signal);
63
+ const handle = lane.live();
64
+ if (handle !== undefined)
65
+ return Promise.resolve({ ok: true, value: handle });
66
+ if (signal?.aborted)
67
+ return Promise.resolve({ ok: false, error: new RemoteExecutionError("aborted", "connect aborted") });
68
+ return lane.establish(config);
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=remote-env-connect-seat.js.map