@sema-agent/server 7.149.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 (441) hide show
  1. package/MIGRATION.md +307 -9
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +92 -40
  5. package/deploy/sema-up/README.md +7 -1
  6. package/deploy/sema-up/chart/Chart.yaml +1 -1
  7. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  8. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  9. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  10. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  11. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  12. package/deploy/sema-up/chart/values.yaml +8 -1
  13. package/deploy/sema-up/cluster-up.sh +1 -0
  14. package/deploy/sema-up/kube-up.sh +1 -1
  15. package/deploy/sema-up/lib/flagshape.sh +1 -1
  16. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  17. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  18. package/dist/adoption/import.js +3 -3
  19. package/dist/approval-ask-machine.d.ts +1 -1
  20. package/dist/approval-card.d.ts +3 -3
  21. package/dist/approval-gate-currency.d.ts +2 -2
  22. package/dist/audit.d.ts +1 -1
  23. package/dist/audit.js +2 -2
  24. package/dist/auth-bridge.d.ts +20 -0
  25. package/dist/auth-bridge.js +9 -0
  26. package/dist/background-session-reap.d.ts +2 -2
  27. package/dist/bake-runner/main.d.ts +4 -2
  28. package/dist/bind-host.d.ts +2 -3
  29. package/dist/bind-host.js +2 -4
  30. package/dist/boot/budget-tracing.js +1 -1
  31. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  32. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  33. package/dist/boot/engine-lease.d.ts +5 -0
  34. package/dist/boot/engine-lease.js +1 -0
  35. package/dist/boot/execution-env.d.ts +8 -14
  36. package/dist/boot/execution-env.js +54 -17
  37. package/dist/boot/execution-lane.js +32 -5
  38. package/dist/boot/late-binds.d.ts +8 -4
  39. package/dist/boot/late-binds.js +7 -1
  40. package/dist/boot/memory-consolidation.d.ts +3 -2
  41. package/dist/boot/memory-consolidation.js +6 -6
  42. package/dist/boot/parked-revive-gate.d.ts +5 -5
  43. package/dist/boot/parked-revive-gate.js +2 -3
  44. package/dist/boot/reapers.d.ts +5 -0
  45. package/dist/boot/reapers.js +12 -1
  46. package/dist/boot/resolve-spec.d.ts +7 -8
  47. package/dist/boot/resolve-spec.js +80 -35
  48. package/dist/boot/retention-lane.d.ts +1 -1
  49. package/dist/boot/runner-deps.d.ts +9 -2
  50. package/dist/boot/runner-deps.js +3 -3
  51. package/dist/boot/session-age-out.d.ts +36 -0
  52. package/dist/boot/session-age-out.js +101 -0
  53. package/dist/boot/session-faces.d.ts +5 -14
  54. package/dist/boot/session-faces.js +28 -24
  55. package/dist/boot/stage-01-config.js +2 -0
  56. package/dist/boot/stage-03-store-backend.js +2 -2
  57. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  58. package/dist/boot/stage-05-execution-env.js +2 -2
  59. package/dist/boot/stage-06-runners.d.ts +7 -4
  60. package/dist/boot/stage-06-runners.js +16 -8
  61. package/dist/boot/stage-07-capability-layer.d.ts +7 -4
  62. package/dist/boot/stage-07-capability-layer.js +3 -2
  63. package/dist/boot/stage-08-reapers.d.ts +7 -4
  64. package/dist/boot/stage-08-reapers.js +4 -2
  65. package/dist/boot/stage-09-leader.d.ts +11 -5
  66. package/dist/boot/stage-09-leader.js +12 -3
  67. package/dist/boot/stage-10-http-server.d.ts +44 -6
  68. package/dist/boot/stage-10-http-server.js +12 -10
  69. package/dist/boot/web-admin.d.ts +1 -1
  70. package/dist/boot/web-admin.js +3 -1
  71. package/dist/boot/workflow-orchestration.d.ts +7 -2
  72. package/dist/boot/workflow-orchestration.js +24 -7
  73. package/dist/bounded-rotation.d.ts +29 -0
  74. package/dist/bounded-rotation.js +24 -0
  75. package/dist/capability-facts.d.ts +84 -66
  76. package/dist/capability-facts.js +58 -41
  77. package/dist/center-credential.d.ts +25 -6
  78. package/dist/center-credential.js +25 -5
  79. package/dist/config-catalog.d.ts +26 -7
  80. package/dist/config-catalog.js +15 -12
  81. package/dist/config-invariants.d.ts +2 -1
  82. package/dist/config-invariants.js +4 -4
  83. package/dist/config-types.d.ts +22 -15
  84. package/dist/config.d.ts +51 -2
  85. package/dist/config.js +35 -24
  86. package/dist/deployment-governance.d.ts +7 -28
  87. package/dist/deployment-governance.js +1 -14
  88. package/dist/deployment-shape.d.ts +9 -1
  89. package/dist/deployment-shape.js +2 -1
  90. package/dist/device-protocol.d.ts +5 -4
  91. package/dist/device-protocol.js +1 -1
  92. package/dist/device-ws-hub.d.ts +1 -0
  93. package/dist/device-ws-hub.js +66 -29
  94. package/dist/device-ws-protocol.d.ts +79 -12
  95. package/dist/device-ws-protocol.js +64 -26
  96. package/dist/engine-identity.d.ts +23 -5
  97. package/dist/engine-identity.js +13 -0
  98. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  99. package/dist/execution-env-refusal.d.ts +23 -0
  100. package/dist/execution-env-refusal.js +5 -0
  101. package/dist/execution-lane-caps.d.ts +3 -1
  102. package/dist/field-conflict.d.ts +3 -3
  103. package/dist/file-history-capture.d.ts +11 -4
  104. package/dist/file-history-capture.js +9 -6
  105. package/dist/file-history-disclosure.d.ts +7 -5
  106. package/dist/file-history-disclosure.js +7 -5
  107. package/dist/fleet/drain-entry.d.ts +4 -0
  108. package/dist/fleet/engine-signal.d.ts +14 -1
  109. package/dist/fleet/engine-signal.js +5 -1
  110. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  111. package/dist/fleet/serving-lease.d.ts +18 -3
  112. package/dist/fleet/serving-lease.js +81 -35
  113. package/dist/fleet-client.d.ts +13 -11
  114. package/dist/fleet-client.js +53 -33
  115. package/dist/history-anchor.js +1 -1
  116. package/dist/hitl-delivery.d.ts +1 -3
  117. package/dist/hitl-delivery.js +1 -1
  118. package/dist/hooks/hook-runner.js +2 -2
  119. package/dist/host-lsp-manager.d.ts +2 -2
  120. package/dist/http/active-run-conflict.d.ts +3 -7
  121. package/dist/http/active-run-conflict.js +2 -2
  122. package/dist/http/admission.js +19 -4
  123. package/dist/http/approval-sweeps.js +7 -6
  124. package/dist/http/caller-trust.d.ts +2 -2
  125. package/dist/http/caller-trust.js +3 -3
  126. package/dist/http/checkpoint-ctx.d.ts +20 -63
  127. package/dist/http/checkpoint-ctx.js +2 -24
  128. package/dist/http/claim-heartbeat.d.ts +4 -4
  129. package/dist/http/claim-heartbeat.js +3 -3
  130. package/dist/http/dispatch.d.ts +2 -1
  131. package/dist/http/dispatch.js +36 -29
  132. package/dist/http/durable-run-leg.js +17 -17
  133. package/dist/http/health-self-report.d.ts +9 -1
  134. package/dist/http/health-self-report.js +2 -0
  135. package/dist/http/owner-access.d.ts +264 -20
  136. package/dist/http/owner-access.js +215 -5
  137. package/dist/http/readiness.d.ts +4 -2
  138. package/dist/http/resume-ctx-read.js +38 -1
  139. package/dist/http/resume-legs.d.ts +2 -1
  140. package/dist/http/resume-legs.js +273 -89
  141. package/dist/http/route-ctx.d.ts +41 -19
  142. package/dist/http/route-table.d.ts +1 -1
  143. package/dist/http/routes/a2a-serve.js +15 -14
  144. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  145. package/dist/http/routes/agents-roster.js +2 -3
  146. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  147. package/dist/http/routes/approvals-assistant.js +12 -12
  148. package/dist/http/routes/background.d.ts +3 -3
  149. package/dist/http/routes/capabilities.js +10 -6
  150. package/dist/http/routes/config-catalog.d.ts +1 -1
  151. package/dist/http/routes/fleet.js +10 -8
  152. package/dist/http/routes/mcp-reconnect.js +2 -2
  153. package/dist/http/routes/memory-origin.d.ts +1 -1
  154. package/dist/http/routes/notify-wake.js +2 -3
  155. package/dist/http/routes/projects.d.ts +4 -2
  156. package/dist/http/routes/projects.js +7 -6
  157. package/dist/http/routes/retention-ops.d.ts +1 -1
  158. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  159. package/dist/http/routes/run-permission-mode.js +2 -2
  160. package/dist/http/routes/runs.d.ts +1 -1
  161. package/dist/http/routes/runs.js +125 -66
  162. package/dist/http/routes/session-memory-sync.js +4 -5
  163. package/dist/http/routes/session-sync.d.ts +4 -3
  164. package/dist/http/routes/session-sync.js +174 -83
  165. package/dist/http/routes/sessions-list.js +13 -7
  166. package/dist/http/routes/sessions.d.ts +1 -1
  167. package/dist/http/routes/sessions.js +26 -48
  168. package/dist/http/routes/tasks.js +38 -23
  169. package/dist/http/routes/trace-usage.js +14 -14
  170. package/dist/http/routes/workflows.d.ts +1 -0
  171. package/dist/http/routes/workflows.js +20 -38
  172. package/dist/http/server.d.ts +28 -30
  173. package/dist/http/server.js +7 -3
  174. package/dist/http/stored-body-replay.d.ts +3 -24
  175. package/dist/http/stored-body-replay.js +2 -12
  176. package/dist/http/submit-ctx.d.ts +1 -2
  177. package/dist/http/submit-ctx.js +8 -6
  178. package/dist/http/submit-replay.d.ts +15 -2
  179. package/dist/http/submit-replay.js +10 -4
  180. package/dist/http/wire-types.d.ts +19 -6
  181. package/dist/identity-source.d.ts +20 -3
  182. package/dist/identity-source.js +14 -0
  183. package/dist/index.d.ts +4 -0
  184. package/dist/lane-home-probe.d.ts +3 -7
  185. package/dist/lane-home-probe.js +0 -1
  186. package/dist/leader/endpoint.js +9 -8
  187. package/dist/leader/leader.d.ts +2 -1
  188. package/dist/leg-facts.d.ts +19 -5
  189. package/dist/leg-facts.js +3 -1
  190. package/dist/mcp-authorization-face.d.ts +18 -14
  191. package/dist/mcp-authorization-face.js +3 -2
  192. package/dist/memory-scope.d.ts +5 -4
  193. package/dist/memory-scope.js +4 -4
  194. package/dist/memory-store-refusal.d.ts +1 -1
  195. package/dist/memory-sync-client.d.ts +30 -2
  196. package/dist/memory-sync-client.js +16 -9
  197. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  198. package/dist/observability/err-text.d.ts +7 -0
  199. package/dist/observability/err-text.js +5 -0
  200. package/dist/observability/fail-open.d.ts +12 -8
  201. package/dist/observability/fail-open.js +12 -8
  202. package/dist/observability/logger.d.ts +7 -0
  203. package/dist/observability/logger.js +3 -1
  204. package/dist/observability/metrics.js +1 -0
  205. package/dist/observability/run-terminal.d.ts +1 -1
  206. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  207. package/dist/orchestration/workflow-notify-journal.js +12 -1
  208. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  209. package/dist/orchestration/workflow-park-decision.js +49 -10
  210. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  211. package/dist/orchestration/workflow-park-holder.js +98 -0
  212. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  213. package/dist/orchestration/workflow-record-fence.js +42 -0
  214. package/dist/owner-faces.d.ts +28 -4
  215. package/dist/owner-faces.js +2 -0
  216. package/dist/parked-decide.d.ts +104 -53
  217. package/dist/parked-decide.js +153 -117
  218. package/dist/peer-directory.d.ts +2 -2
  219. package/dist/peer-inbound-face.d.ts +2 -6
  220. package/dist/peer-inbound-face.js +0 -3
  221. package/dist/peer-seat.d.ts +1 -1
  222. package/dist/peer-seat.js +52 -59
  223. package/dist/permission-mode-admission.d.ts +41 -5
  224. package/dist/permission-mode-admission.js +29 -3
  225. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  226. package/dist/plugins/adoption-log-sql.js +7 -1
  227. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  228. package/dist/plugins/approval-ask-store-file.js +8 -0
  229. package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
  230. package/dist/plugins/approval-ask-store-memory.js +28 -9
  231. package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
  232. package/dist/plugins/approval-ask-store-sql.js +34 -5
  233. package/dist/plugins/approval-exemption-store.js +3 -0
  234. package/dist/plugins/approval-nonce-store.js +2 -0
  235. package/dist/plugins/breaker-state-sql.js +2 -0
  236. package/dist/plugins/caching-session-store.d.ts +9 -15
  237. package/dist/plugins/caching-session-store.js +6 -0
  238. package/dist/plugins/device-store-sql.js +6 -0
  239. package/dist/plugins/error-origin.d.ts +18 -0
  240. package/dist/plugins/error-origin.js +10 -0
  241. package/dist/plugins/exec-path-law.d.ts +34 -0
  242. package/dist/plugins/exec-path-law.js +13 -0
  243. package/dist/plugins/execution-placement-sql.js +6 -6
  244. package/dist/plugins/file-run-store.js +1 -1
  245. package/dist/plugins/image-bake-store-sql.js +6 -0
  246. package/dist/plugins/image-index-sql.js +4 -0
  247. package/dist/plugins/landed-attendants.d.ts +212 -0
  248. package/dist/plugins/landed-attendants.js +161 -0
  249. package/dist/plugins/leader-run-store-sql.js +4 -0
  250. package/dist/plugins/local-checkpoint-store.d.ts +4 -1
  251. package/dist/plugins/local-checkpoint-store.js +3 -0
  252. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  253. package/dist/plugins/local-session-live-reference.js +39 -0
  254. package/dist/plugins/local-session-store.d.ts +71 -33
  255. package/dist/plugins/local-session-store.js +129 -27
  256. package/dist/plugins/memory-engine-pg.js +4 -0
  257. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  258. package/dist/plugins/memory-engine-tidb.js +4 -0
  259. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  260. package/dist/plugins/memory-run-store.js +1 -1
  261. package/dist/plugins/memory-sync-store-pg.js +5 -0
  262. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  263. package/dist/plugins/one-time-migrations.d.ts +80 -61
  264. package/dist/plugins/one-time-migrations.js +241 -104
  265. package/dist/plugins/org-delivery-quota-store.js +4 -1
  266. package/dist/plugins/outcome-ledger-sql.js +2 -0
  267. package/dist/plugins/permission-rule-store-sql.js +11 -4
  268. package/dist/plugins/pg-cost-quota.js +4 -0
  269. package/dist/plugins/pg-pool.js +28 -1
  270. package/dist/plugins/pg-rate-limiter.js +2 -0
  271. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  272. package/dist/plugins/posix-shell-fs.js +8 -3
  273. package/dist/plugins/remote-env-adb.d.ts +19 -15
  274. package/dist/plugins/remote-env-adb.js +44 -17
  275. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  276. package/dist/plugins/remote-env-connect-seat.js +72 -0
  277. package/dist/plugins/remote-env-device.d.ts +22 -5
  278. package/dist/plugins/remote-env-device.js +114 -14
  279. package/dist/plugins/remote-env-e2b.d.ts +18 -7
  280. package/dist/plugins/remote-env-e2b.js +53 -40
  281. package/dist/plugins/remote-env-file-error.js +6 -2
  282. package/dist/plugins/remote-env-host.d.ts +18 -1
  283. package/dist/plugins/remote-env-host.js +48 -47
  284. package/dist/plugins/remote-env-k8s.d.ts +41 -39
  285. package/dist/plugins/remote-env-k8s.js +56 -32
  286. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  287. package/dist/plugins/remote-env-local-docker.js +37 -43
  288. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  289. package/dist/plugins/remote-env-ssh.js +64 -62
  290. package/dist/plugins/remote-shell.d.ts +101 -2
  291. package/dist/plugins/remote-shell.js +221 -3
  292. package/dist/plugins/replica-registration.d.ts +35 -0
  293. package/dist/plugins/replica-registration.js +48 -0
  294. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  295. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  296. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  297. package/dist/plugins/retention-lane-store-sql.js +4 -0
  298. package/dist/plugins/retention-sql.d.ts +20 -2
  299. package/dist/plugins/retention-sql.js +11 -0
  300. package/dist/plugins/run-store-sql.d.ts +22 -3
  301. package/dist/plugins/run-store-sql.js +33 -9
  302. package/dist/plugins/scenario-prefix-switch-m42.d.ts +6 -12
  303. package/dist/plugins/scenario-prefix-switch-m42.js +7 -5
  304. package/dist/plugins/schema-consistency.d.ts +9 -5
  305. package/dist/plugins/schema-consistency.js +48 -18
  306. package/dist/plugins/schema-drift-sentence.js +1 -1
  307. package/dist/plugins/send-file-ledger.js +3 -0
  308. package/dist/plugins/serving-requirement.d.ts +68 -0
  309. package/dist/plugins/serving-requirement.js +60 -0
  310. package/dist/plugins/session-birth.d.ts +6 -2
  311. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  312. package/dist/plugins/session-ext-table.d.ts +14 -1
  313. package/dist/plugins/session-ext-table.js +5 -1
  314. package/dist/plugins/session-row-sql.d.ts +49 -2
  315. package/dist/plugins/session-row-sql.js +56 -13
  316. package/dist/plugins/session-store-sql.d.ts +34 -18
  317. package/dist/plugins/session-store-sql.js +98 -17
  318. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  319. package/dist/plugins/shared-memory-store-sql.js +6 -2
  320. package/dist/plugins/sql-driver.d.ts +2 -1
  321. package/dist/plugins/sql-driver.js +18 -3
  322. package/dist/plugins/sql-errors.d.ts +11 -6
  323. package/dist/plugins/sql-errors.js +5 -6
  324. package/dist/plugins/stop-then-start-sentence.d.ts +48 -3
  325. package/dist/plugins/stop-then-start-sentence.js +48 -0
  326. package/dist/plugins/stop-then-start.d.ts +109 -0
  327. package/dist/plugins/stop-then-start.js +104 -0
  328. package/dist/plugins/store-backend.d.ts +40 -13
  329. package/dist/plugins/store-backend.js +169 -27
  330. package/dist/plugins/store-boundary.d.ts +57 -0
  331. package/dist/plugins/store-boundary.js +139 -0
  332. package/dist/plugins/store-contracts.d.ts +29 -2
  333. package/dist/plugins/store-contracts.js +3 -0
  334. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  335. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  336. package/dist/plugins/store-fault.d.ts +26 -7
  337. package/dist/plugins/store-fault.js +18 -2
  338. package/dist/plugins/task-attachment-store.js +9 -0
  339. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  340. package/dist/plugins/task-list-store-sql.js +10 -8
  341. package/dist/plugins/tidb-cost-quota.js +4 -0
  342. package/dist/plugins/tidb-pool.js +6 -1
  343. package/dist/plugins/tidb-rate-limiter.js +4 -0
  344. package/dist/plugins/user-input-ledger-sql.js +4 -0
  345. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  346. package/dist/plugins/workflow-run-store-sql.js +64 -4
  347. package/dist/plugins/workspace-fill.d.ts +4 -7
  348. package/dist/plugins/workspace-fill.js +2 -1
  349. package/dist/plugins/write-behind-counter.js +5 -0
  350. package/dist/process-instance.d.ts +3 -0
  351. package/dist/process-instance.js +7 -0
  352. package/dist/project-context.d.ts +64 -0
  353. package/dist/project-context.js +66 -0
  354. package/dist/project-registry.d.ts +46 -0
  355. package/dist/project-registry.js +44 -0
  356. package/dist/question.d.ts +9 -21
  357. package/dist/question.js +28 -19
  358. package/dist/request-key-closure.js +1 -1
  359. package/dist/request-value-closure.d.ts +4 -0
  360. package/dist/request-value-closure.js +10 -0
  361. package/dist/run-acceptance.d.ts +3 -2
  362. package/dist/run-acceptance.js +4 -6
  363. package/dist/run-cancel-card.d.ts +1 -1
  364. package/dist/run-cancel-card.js +2 -1
  365. package/dist/run-local.js +1 -0
  366. package/dist/runs.d.ts +37 -8
  367. package/dist/runs.js +53 -12
  368. package/dist/runtime-governance.d.ts +5 -4
  369. package/dist/security.d.ts +31 -33
  370. package/dist/security.js +41 -7
  371. package/dist/server-secret-env.d.ts +12 -10
  372. package/dist/server-secret-env.js +24 -15
  373. package/dist/session-agent.d.ts +1 -10
  374. package/dist/session-agent.js +0 -6
  375. package/dist/session-bindings.d.ts +82 -0
  376. package/dist/session-bindings.js +18 -0
  377. package/dist/session-children-retire.d.ts +2 -2
  378. package/dist/session-children-retire.js +2 -6
  379. package/dist/session-deletion.d.ts +16 -1
  380. package/dist/session-error-wire.d.ts +3 -3
  381. package/dist/session-error-wire.js +3 -3
  382. package/dist/session-incarnation.d.ts +28 -4
  383. package/dist/session-incarnation.js +14 -2
  384. package/dist/session-kind.d.ts +57 -0
  385. package/dist/session-kind.js +22 -0
  386. package/dist/session-notify-door.d.ts +4 -4
  387. package/dist/session-notify-door.js +5 -6
  388. package/dist/session-row.d.ts +21 -1
  389. package/dist/session-row.js +7 -1
  390. package/dist/session-rules-seat.d.ts +4 -3
  391. package/dist/session-rules-seat.js +3 -11
  392. package/dist/session-sync-kernel.d.ts +24 -14
  393. package/dist/session-sync-kernel.js +3 -6
  394. package/dist/session-sync.d.ts +4 -98
  395. package/dist/session-sync.js +4 -74
  396. package/dist/session-titler.d.ts +10 -3
  397. package/dist/session-titler.js +19 -6
  398. package/dist/ssh-host-key.d.ts +5 -0
  399. package/dist/ssh-host-key.js +3 -0
  400. package/dist/stores-sql/checkpoint-store-sql.d.ts +21 -2
  401. package/dist/stores-sql/checkpoint-store-sql.js +106 -18
  402. package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
  403. package/dist/stores-sql/file-history-store-sql.js +108 -70
  404. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  405. package/dist/stores-sql/pg-session-storage.js +37 -13
  406. package/dist/stores-sql/retention-store-sql.d.ts +2 -10
  407. package/dist/stores-sql/retention-store-sql.js +2 -10
  408. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  409. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  410. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  411. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  412. package/dist/stores-sql/tidb-session-store.js +37 -13
  413. package/dist/submission-exit.d.ts +3 -2
  414. package/dist/submission-exit.js +9 -1
  415. package/dist/task-cwd.d.ts +6 -5
  416. package/dist/task-settings.d.ts +19 -5
  417. package/dist/task-settings.js +13 -7
  418. package/dist/terminal.d.ts +33 -2
  419. package/dist/terminal.js +139 -9
  420. package/dist/tool-approval.d.ts +147 -12
  421. package/dist/tool-approval.js +206 -109
  422. package/dist/trace/engine-notice-wire.d.ts +14 -0
  423. package/dist/trace/engine-notice-wire.js +18 -1
  424. package/dist/trace/project.d.ts +4 -3
  425. package/dist/trace/project.js +3 -2
  426. package/dist/trace/projection-table.d.ts +33 -2
  427. package/dist/trace/self-minted-wire.d.ts +422 -26
  428. package/dist/trace/self-minted-wire.js +149 -23
  429. package/dist/trace/tool-card.d.ts +3 -0
  430. package/dist/trace/tool-card.js +24 -0
  431. package/dist/trace/tool-output-media.d.ts +2 -1
  432. package/dist/unattended-permission-mode.d.ts +1 -1
  433. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  434. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  435. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  436. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +120 -41
  437. package/package.json +3 -3
  438. package/wire-changes/server@7.150.0.json +335 -0
  439. package/wire-changes/server@7.151.0.json +762 -0
  440. package/dist/http/session-inherit-env.d.ts +0 -36
  441. package/dist/http/session-inherit-env.js +0 -6
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { anchoredPath, anchorUnder, joinedPath } from "./exec-path-law.js";
2
3
  import { Client } from "ssh2";
3
4
  import { createSshHostKeyVerifier } from "../ssh-host-key.js";
4
5
  import { appendFileStaged, buildCwdEnvCommand, createCwdGuardedCall, kindFromMode, writeFileStaged } from "./remote-shell.js";
@@ -10,6 +11,7 @@ import { FileError, ExecutionError, RemoteExecutionError, withRetry, RollingTail
10
11
  import { capabilitiesFor } from "../execution-lane-caps.js";
11
12
  import { fileErrorFromExec } from "./remote-env-file-error.js";
12
13
  import { LANE_HOME_PROBE_COMMAND, LaneHomeSeat } from "../lane-home-probe.js";
14
+ import { connectRefusal, createConnectSeat, withdrawOnAbort } from "./remote-env-connect-seat.js";
13
15
  const PROVIDER = "ssh";
14
16
  const ok = (value) => ({ ok: true, value });
15
17
  const unsupported = (op) => ({
@@ -32,7 +34,11 @@ export class RemoteSshExecutionEnv {
32
34
  }
33
35
  homeSeat;
34
36
  homeChecked;
35
- homeRefusal;
37
+ seat = createConnectSeat({
38
+ live: () => (this.conn !== undefined && !this.destroyed ? this.workspaceHandle() : undefined),
39
+ inFlight: () => (this.inFlight !== undefined && this.inFlight.epoch === this.connEpoch ? this.inFlight.promise : undefined),
40
+ establish: (config) => withdrawOnAbort(this.startConnect("establish"), config?.abortSignal),
41
+ });
36
42
  cdPath = "unknown";
37
43
  withheldEnv = [];
38
44
  cfg;
@@ -57,7 +63,7 @@ export class RemoteSshExecutionEnv {
57
63
  this.homeSeat = new LaneHomeSeat(PROVIDER, config);
58
64
  }
59
65
  resolve(p) {
60
- return path.posix.isAbsolute(p) ? path.posix.normalize(p) : path.posix.normalize(path.posix.join(this.cwd, p));
66
+ return anchoredPath(path.posix, this.cwd, p);
61
67
  }
62
68
  workspaceHandle() {
63
69
  return { sandboxId: `${this.cfg.host}:${this.cfg.port}`, provider: PROVIDER, mountPath: this.cwd };
@@ -65,26 +71,10 @@ export class RemoteSshExecutionEnv {
65
71
  async connect(config) {
66
72
  if (this.destroyed)
67
73
  return { ok: false, error: new RemoteExecutionError("connect_failed", "SSH env destroyed — create a fresh env instead of reviving") };
68
- if (this.conn)
69
- return ok(this.workspaceHandle());
70
- return this.raceConnAbort(this.startConnect("establish"), config?.abortSignal);
74
+ return this.seat.connect(config);
71
75
  }
72
- async raceConnAbort(shared, signal) {
73
- if (!signal)
74
- return shared;
75
- if (signal.aborted)
76
- return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted") };
77
- let onAbort;
78
- const aborted = new Promise((res) => {
79
- onAbort = () => res("aborted");
80
- signal.addEventListener("abort", onAbort, { once: true });
81
- });
82
- const raced = await Promise.race([shared, aborted]);
83
- if (onAbort)
84
- signal.removeEventListener("abort", onAbort);
85
- if (raced === "aborted")
86
- return { ok: false, error: new RemoteExecutionError("aborted", "connect aborted while waiting") };
87
- return raced;
76
+ refusalOnExecFace(refusal) {
77
+ return new ExecutionError("shell_unavailable", refusal.error.message, refusal.error);
88
78
  }
89
79
  startConnect(kind) {
90
80
  if (this.inFlight && this.inFlight.epoch === this.connEpoch)
@@ -114,10 +104,15 @@ export class RemoteSshExecutionEnv {
114
104
  }
115
105
  async ensureConnected(signal) {
116
106
  for (let round = 0; round < 3; round++) {
117
- if (this.homeRefusal)
118
- return { ok: false, error: this.homeRefusal };
107
+ const held = this.seat.held;
108
+ if (held)
109
+ return { ok: false, error: this.refusalOnExecFace(held) };
119
110
  const transport = await this.ensureTransport(signal);
120
- if (!transport.ok || !this.homeSeat.asks)
111
+ if (!transport.ok) {
112
+ const now = this.seat.held;
113
+ return now ? { ok: false, error: this.refusalOnExecFace(now) } : transport;
114
+ }
115
+ if (!this.homeSeat.asks)
121
116
  return transport;
122
117
  const conn = this.conn;
123
118
  if (!conn)
@@ -126,7 +121,7 @@ export class RemoteSshExecutionEnv {
126
121
  const checked = { conn, verdict: this.homeVerdict(conn) };
127
122
  this.homeChecked = checked;
128
123
  void checked.verdict.then((r) => {
129
- if (!r.ok && this.homeRefusal === undefined && this.homeChecked === checked)
124
+ if (!r.ok && this.seat.held === undefined && this.homeChecked === checked)
130
125
  this.homeChecked = undefined;
131
126
  });
132
127
  }
@@ -184,10 +179,9 @@ export class RemoteSshExecutionEnv {
184
179
  const verdict = this.homeSeat.settle(probe);
185
180
  if (verdict === undefined)
186
181
  return resolve(ok(undefined));
187
- const error = new ExecutionError("shell_unavailable", verdict.refusal);
188
182
  if (verdict.answered)
189
- this.homeRefusal = error;
190
- resolve({ ok: false, error });
183
+ return resolve({ ok: false, error: this.refusalOnExecFace(this.seat.answer(connectRefusal("home_unconfirmed", verdict.refusal))) });
184
+ resolve({ ok: false, error: new ExecutionError("shell_unavailable", verdict.refusal) });
191
185
  };
192
186
  const timer = setTimeout(() => settle({ ok: false, reason: `no answer within ${this.cfg.readyTimeoutMs}ms` }), this.cfg.readyTimeoutMs);
193
187
  try {
@@ -221,33 +215,14 @@ export class RemoteSshExecutionEnv {
221
215
  return { ok: false, error: new ExecutionError("shell_unavailable", "SSH env destroyed — no lazy reconnect after teardown") };
222
216
  if (signal?.aborted)
223
217
  return { ok: false, error: new ExecutionError("aborted", "aborted before SSH lazy-connect") };
224
- if (this.conn)
225
- return ok(undefined);
226
- const shared = this.startConnect("establish");
227
- let r;
228
- if (signal) {
229
- let onAbort;
230
- const aborted = new Promise((res) => {
231
- onAbort = () => res("aborted");
232
- signal.addEventListener("abort", onAbort, { once: true });
233
- });
234
- const raced = await Promise.race([shared, aborted]);
235
- if (onAbort)
236
- signal.removeEventListener("abort", onAbort);
237
- if (raced === "aborted")
238
- return { ok: false, error: new ExecutionError("aborted", "aborted while waiting for SSH lazy-connect") };
239
- r = raced;
240
- }
241
- else {
242
- r = await shared;
243
- }
218
+ const r = await this.seat.connect(signal ? { abortSignal: signal } : undefined);
244
219
  if (r.ok) {
245
220
  if (this.conn)
246
221
  return ok(undefined);
247
222
  return { ok: false, error: new ExecutionError("shell_unavailable", "SSH connection dropped right after connect") };
248
223
  }
249
224
  if (r.error.code === "aborted")
250
- return { ok: false, error: new ExecutionError("aborted", r.error.message, r.error) };
225
+ return { ok: false, error: new ExecutionError("aborted", signal?.aborted ? "aborted while waiting for SSH lazy-connect" : r.error.message, r.error) };
251
226
  return { ok: false, error: new ExecutionError("shell_unavailable", `SSH lazy-connect failed: ${r.error.message}`, r.error) };
252
227
  }
253
228
  connectOnce(signal, opEpoch) {
@@ -272,7 +247,7 @@ export class RemoteSshExecutionEnv {
272
247
  hostVerifier: (key) => {
273
248
  const verdict = verifyHostKey(key);
274
249
  if (!verdict.ok) {
275
- hostKeyRejection = verdict.reason;
250
+ hostKeyRejection = { reason: verdict.reason, standing: verdict.standing };
276
251
  return false;
277
252
  }
278
253
  return true;
@@ -302,10 +277,9 @@ export class RemoteSshExecutionEnv {
302
277
  }
303
278
  catch {
304
279
  }
305
- resolve({
306
- ok: false,
307
- error: hostKeyRejection !== undefined ? new RemoteExecutionError("connect_failed", hostKeyRejection, e) : this.connErr(e),
308
- });
280
+ if (hostKeyRejection === undefined)
281
+ return resolve({ ok: false, error: this.connErr(e) });
282
+ resolve(hostKeyRejection.standing ? this.seat.answer(connectRefusal("host_key_rejected", hostKeyRejection.reason, e)) : { ok: false, error: new RemoteExecutionError("connect_failed", hostKeyRejection.reason, e) });
309
283
  });
310
284
  try {
311
285
  c.connect(opts);
@@ -327,6 +301,9 @@ export class RemoteSshExecutionEnv {
327
301
  async reconnect(_sessionToken) {
328
302
  if (this.destroyed)
329
303
  return { ok: false, error: new RemoteExecutionError("connect_failed", "SSH env destroyed — create a fresh env instead of reviving") };
304
+ const held = this.seat.held;
305
+ if (held)
306
+ return held;
330
307
  if (this.inFlight && this.inFlight.epoch === this.connEpoch && this.inFlight.kind === "replace")
331
308
  return this.inFlight.promise;
332
309
  this.connEpoch++;
@@ -355,7 +332,33 @@ export class RemoteSshExecutionEnv {
355
332
  fsExec(command, abortSignal) {
356
333
  return this.execCall(createCwdGuardedCall("ssh", command, undefined, undefined), { abortSignal });
357
334
  }
358
- async execCall(call, options) {
335
+ execCall(call, options) {
336
+ const outBuf = new RollingTailBuffer();
337
+ return this.runBuffered(call, options, {
338
+ push: (d) => outBuf.push(d),
339
+ tail: outBuf,
340
+ take: () => {
341
+ const out = outBuf.result();
342
+ return markTruncated(out.text, out.droppedBytes);
343
+ },
344
+ });
345
+ }
346
+ boundExec(cmd, cap, abortSignal) {
347
+ const chunks = [];
348
+ let kept = 0;
349
+ return this.runBuffered(createCwdGuardedCall("ssh", cmd, undefined, undefined), { abortSignal, timeout: this.cfg.dataTimeoutMs / 1000 }, {
350
+ push: (d) => {
351
+ if (kept > cap)
352
+ return;
353
+ const piece = d.length > cap + 1 - kept ? d.subarray(0, cap + 1 - kept) : d;
354
+ chunks.push(piece);
355
+ kept += piece.length;
356
+ },
357
+ tail: undefined,
358
+ take: () => new Uint8Array(Buffer.concat(chunks)),
359
+ });
360
+ }
361
+ async runBuffered(call, options, sink) {
359
362
  const ready = await this.ensureConnected(options?.abortSignal);
360
363
  if (!ready.ok)
361
364
  return ready;
@@ -364,7 +367,6 @@ export class RemoteSshExecutionEnv {
364
367
  return { ok: false, error: new ExecutionError("shell_unavailable", "SSH not connected — call connect() first") };
365
368
  const timeoutMs = options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs;
366
369
  return new Promise((resolve) => {
367
- const outBuf = new RollingTailBuffer();
368
370
  const errBuf = new RollingTailBuffer();
369
371
  const recordStderr = (bytes) => {
370
372
  if (bytes.length > 0)
@@ -380,7 +382,7 @@ export class RemoteSshExecutionEnv {
380
382
  };
381
383
  const captured = () => {
382
384
  recordStderr(call.endStderr());
383
- return buildCapturedOutput(outBuf, errBuf);
385
+ return buildCapturedOutput(sink.tail, errBuf);
384
386
  };
385
387
  let settled = false;
386
388
  let streamRef;
@@ -426,7 +428,7 @@ export class RemoteSshExecutionEnv {
426
428
  }
427
429
  stream
428
430
  .on("data", (d) => {
429
- outBuf.push(d);
431
+ sink.push(d);
430
432
  options?.onStdout?.(d.toString());
431
433
  })
432
434
  .on("close", (code, signal) => {
@@ -437,9 +439,8 @@ export class RemoteSshExecutionEnv {
437
439
  const held = call.endStderr();
438
440
  const won = finish(() => {
439
441
  recordStderr(held);
440
- const out = outBuf.result();
441
442
  const err = errBuf.result();
442
- return call.settle(ok({ stdout: markTruncated(out.text, out.droppedBytes), stderr: markTruncated(err.text, err.droppedBytes), exitCode }));
443
+ return call.settle(ok({ stdout: sink.take(), stderr: markTruncated(err.text, err.droppedBytes), exitCode }));
443
444
  });
444
445
  if (won)
445
446
  emitStderr(held);
@@ -595,7 +596,7 @@ export class RemoteSshExecutionEnv {
595
596
  return ok(this.resolve(p));
596
597
  }
597
598
  async joinPath(parts) {
598
- return ok(path.posix.join(...parts));
599
+ return ok(joinedPath(path.posix, parts));
599
600
  }
600
601
  async readTextFile(p, abortSignal) {
601
602
  const r = await this.readBinaryFile(p, abortSignal);
@@ -674,7 +675,7 @@ export class RemoteSshExecutionEnv {
674
675
  const entries = await new Promise((resolve, reject) => sftp.readdir(abs, (err, list) => (err ? reject(err) : resolve(list))));
675
676
  return ok(entries.map((e) => {
676
677
  const a = e.attrs;
677
- return { name: e.filename, path: path.posix.join(abs, e.filename), kind: kindFromMode(a.mode), size: a.size ?? 0, mtimeMs: (a.mtime ?? 0) * 1000 };
678
+ return { name: e.filename, path: anchorUnder(path.posix, abs, e.filename), kind: kindFromMode(a.mode), size: a.size ?? 0, mtimeMs: (a.mtime ?? 0) * 1000 };
678
679
  }));
679
680
  }
680
681
  catch (e) {
@@ -687,6 +688,7 @@ export class RemoteSshExecutionEnv {
687
688
  run: (cmd, abortSignal) => this.fsExec(cmd, abortSignal),
688
689
  resolve: (p) => this.resolve(p),
689
690
  tempDir: () => null,
691
+ boundLane: { run: (cmd, cap, abortSignal) => this.boundExec(cmd, cap, abortSignal), payload: "base64", boundSec: this.cfg.dataTimeoutMs / 1000 },
690
692
  }));
691
693
  }
692
694
  async readLink(p, abortSignal) {
@@ -2,7 +2,7 @@
2
2
  * Shared shell/path helpers for the remote `RemoteExecutionEnv` adapters (ssh / adb / k8s / local-docker, and e2b where it
3
3
  * shells out). Kept in ONE place so the security-sensitive quoting can't drift between adapters (council design/#4, #5).
4
4
  */
5
- import { ExecutionError, FileError, type FileInfo, type RemoteExecutionError, type Result, type SymlinkChain } from "@sema-agent/core";
5
+ import { ExecutionError, FileError, type BoundFileObject, type FileInfo, type RemoteExecutionError, type Result, type SymlinkChain } from "@sema-agent/core";
6
6
  import type { ChildProcess } from "node:child_process";
7
7
  /**
8
8
  * D5 pipe-destroy grace (parity with core's vendored `EXEC_FORCE_SETTLE_GRACE_MS`, harness/env/nodejs.js):
@@ -60,7 +60,9 @@ export type CwdGuardFace = "settled" | "raw";
60
60
  * 🔴 [ref](7.103.1)—— 三条远端 shell 腿(ssh / k8s / adb)交给远端 `sh -c` 的那一串**唯一**的组装:进工作目录 →(settled 面)打 started 行 →
61
61
  * 导出任务 env → 跑命令。各腿只决定 `dir`(自己的缺省 cwd 规则 + 路径绝对化),串的形状只在这里。
62
62
  *
63
- * 形:`cd '<dir>' || { echo '<marker>: <dir> (cd failed; command not run)' >&2; exit 125; }; echo '<started>' >&2; export K='v'; … <command>`
63
+ * 形:`cd -P '<dir>' || { echo '<marker>: <dir> (cd failed; command not run)' >&2; exit 125; }; echo '<started>' >&2; export K='v'; … <command>`
64
+ * · `-P`(物理):工作目录按内核解 —— 执行缝路径律下 `<dir>` 是未折的拼法(`<base>/bridge/../next`),文件面由内核解 `..`;缺省的逻辑 `cd` 会先词法折成 `<base>/next`,
65
+ * 命令与文件动作就各在一只目录。`-P` 是 POSIX `cd` 的选项(dash / bash / busybox ash / mksh 都认)。
64
66
  * (raw 面没有 `echo '<started>' >&2;` 那一段)
65
67
  * · cd 失败(目录不存在 / 无权限 / 不是目录)⇒ 命令**不跑**,整条退 {@link CWD_UNAVAILABLE_EXIT};stderr 先是 shell 自己的 cd 报错,再是守卫行。
66
68
  * 修前形 `cd '<dir>' && export K='v'; <command>`:`&&` 只绑到第一只 `export`,`;` 之后的命令照样在**登录目录**里跑,还少了那只 env ——
@@ -204,6 +206,103 @@ export type ShellChainRun = (cmd: string) => Promise<Result<{
204
206
  * 三条远端腿的文件面探针不带 cwd(车PL 验修 ③)⇒ 这条命令不会因为某个工作目录不可用而不跑。
205
207
  */
206
208
  export declare function runSymlinkChain(absPath: string, run: ShellChainRun): Promise<Result<SymlinkChain, FileError>>;
209
+ /**
210
+ * 绑定读脚本的退出码闭集(与 shell / 工具自己的 1 / 2 / 124–127 / 128+ 不撞)。读法只在 {@link boundReadRefusal}(穷举 switch,无 default 臂)。
211
+ * · `link` —— 预检:叶是链接(没有 `O_NOFOLLOW` 可用,先查);
212
+ * · `moved` —— 打开之后的那一次观测不在规范键上(叶 / 父段打开时是链接、打开后被改名 / 删掉),或身份 / 类型不等于开答时;
213
+ * · `kind` —— 既非普通文件也非目录(预检或打开之后);
214
+ * · `over` —— 这一答量到的大小超过 `max`(先量后读:超了一个字节都不读);
215
+ * · `platform` —— 给不出那一次观测或缺工具(见上段)⇒ `not_supported`;
216
+ * · `name` —— 列目录时有一个名字带换行:一行一条的答装不下它而不生歧义(名字里的换行能伪造出一条假条目),整答拒、不列一半。
217
+ */
218
+ export declare const BOUND_READ_EXITS: {
219
+ readonly link: 90;
220
+ readonly moved: 91;
221
+ readonly kind: 92;
222
+ readonly over: 93;
223
+ readonly platform: 94;
224
+ readonly name: 95;
225
+ };
226
+ export type BoundReadExit = keyof typeof BOUND_READ_EXITS;
227
+ /** 一答的操作闭集(工厂的四个成员各一答;`open` 是 `openBound` 那一答)。 */
228
+ export type BoundReadOp = "open" | "size" | "readBytes" | "list";
229
+ /**
230
+ * 绑定读的 POSIX sh 脚本 —— **常量**(不拼任何值;路径与参数经位置参数 `$1…$6` 进来):五条远端腿发的是逐字节同一串。
231
+ * 位置参数:`$1` 规范键 · `$2` 操作 · `$3` 开答时的身份 · `$4` 开答时的类型 · `$5` `max` · `$6` 载荷编码(`raw` / `base64`)。
232
+ *
233
+ * 步骤(每一答都整走一遍):
234
+ * ① 平台与工具:`/proc/self/fd` 不在、或缺工具 ⇒ `platform`,**在打开任何东西之前**;
235
+ * ② 预检:叶是链接 ⇒ `link`;在场却既非普通文件也非目录(FIFO / 设备 / 套接字)⇒ `kind` —— 预检只缩小窗口(shell 没有 `O_NOFOLLOW` / `O_NONBLOCK`),关不上它;
236
+ * ③ 一次打开、整条答共用:复合命令重定向 `{ …; } < "$f"`(**不用** `exec 3<`:ksh93 给 `exec` 开的 > 2 号描述符置 close-on-exec,子进程读不到它;
237
+ * 复合命令重定向的 0 号描述符照常继承 —— 外部命令、命令替换、shell 自己都看见同一只打开);打开失败 ⇒ shell 自己的报错 + 退 1(读法归 stderr 分类);
238
+ * ④ 那一次观测:`readlink /proc/self/fd/0`(内核给出「这只打开的对象此刻叫什么」;`/proc/self` 在外部命令里就是它自己,它继承的 0 号描述符就是这次打开)
239
+ * 须逐字等于规范键,否则 `moved` —— 命令替换会吞掉**全部**尾随换行,所以读数后面垫一个哨兵字符、只剥 `readlink` 自己那一个换行(名字以换行结尾的对象
240
+ * 与规范键不等);身份与类型 `stat -L -c '%d:%i %s %F' /proc/self/fd/0`(跟的是魔法链接:这只打开的对象本身,不是按路径再查;`%F` 是人读的词,
241
+ * GNU 按区域设置翻译 ⇒ 整只脚本在 `LC_ALL=C` 下跑,工具的报错句也因此是共享分类器认得的英文);
242
+ * 类型须是普通文件或目录,否则 `kind`;带了开答身份的答(size / read / list)⇒ 身份**与类型**须都等于开答时,否则 `moved`;
243
+ * ⑤ 作答,只经这一次打开:`open` 一行 `<身份> <file|directory>`;`size` 一行十进制;`readBytes` 先量(> `max` ⇒ 打出量到的大小、`over`,一个字节不读),
244
+ * 一行十进制大小 + 载荷 —— 大小 > 0 读恰好那么多(与 core 读口同读:开读那一刻的大小),= 0(procfs 一类)读到流尽、至多 `max + 1`;
245
+ * base64 腿的 `head | base64` 管道里 `head` 自己的退出码经 4 号描述符带出来(复合命令重定向,不用 `exec`):读失败 ⇒ 退 1,不交 `base64` 的 0;
246
+ * `list` 经 `cd -P /proc/self/fd/0` 进**这只打开的目录**,逐条 lstat 语义一行 `<十六进制模式> <大小> <mtime 秒> <名>`;名字带换行 ⇒ `name`。
247
+ * 全程不用 `!`(某些登录 shell 的历史展开)、不用 `pipefail`(dash 没有);读的截短另由读法按大小对拍兜住({@link createShellRebindingAnswers})。
248
+ */
249
+ export declare const BOUND_READ_SCRIPT: string;
250
+ /** 一答的入参(开答之后的三答带开答时的身份与类型)。 */
251
+ export interface BoundReadArgs {
252
+ readonly bound?: BoundPlacement;
253
+ readonly max?: number;
254
+ }
255
+ /**
256
+ * 一答发给目标的整串(**纯数据** ⇒ `build*`):先判 `timeout(1)` 在不在(不在 ⇒ `platform`,有界给不出就不答),再以 `timeout <boundSec 向下取整,至少 1> sh -c <脚本>` 跑 ——
257
+ * 显式套一层 `sh`,把方言钉在目标的 POSIX `/bin/sh` 上(容器的命令 shell 可能是 bash、e2b 起的是登录 bash、ssh 交给远端登录 shell)。
258
+ * 整条包在一个 `{ …; }` 里,只有一个退出状态(与 {@link buildSymlinkChainCommand} 同一条理由)。值全经 {@link shellQuote} 进位置参数,脚本串里不拼值。
259
+ */
260
+ export declare function buildBoundReadCommand(abs: string, op: BoundReadOp, args: BoundReadArgs, payload: BoundReadLane["payload"], boundSec: number): string;
261
+ /**
262
+ * 一条远端腿交给绑定读的那一条传输(各腿只造这一只闭包;读法与归码不在腿上)。
263
+ * 🔴 字节回程律:`stdout` **逐字节**(任何丢弃 / 截尾的收集器都不许接在这里 —— ssh 的命令面收集器是 8 MiB 滚动尾,所以 ssh 另造一只按本答上界封顶的);
264
+ * 收集器有上界的腿保留至多 `stdoutCap + 1` 字节,读法见到比上界长的答即拒(不解码一段尾巴)。
265
+ */
266
+ export interface BoundReadLane {
267
+ run(cmd: string, stdoutCap: number, abortSignal?: AbortSignal): Promise<Result<{
268
+ stdout: Uint8Array;
269
+ stderr: string;
270
+ exitCode: number;
271
+ }, ExecutionError | RemoteExecutionError>>;
272
+ /** 载荷在这条腿的 stdout 上怎么走:`raw`(二进制收集器:local-docker / adb)或 `base64`(文本面:k8s 的 WS 文本帧 / e2b SDK 的字符串 / ssh)。 */
273
+ readonly payload: "raw" | "base64";
274
+ /** 一答在目标上的墙钟(整秒,`timeout(1)`)= 该腿的数据界。 */
275
+ readonly boundSec: number;
276
+ }
277
+ /** 开答定下的那只对象(工厂与答者之间的唯一凭据):身份 `<dev>:<inode>` + 类型。 */
278
+ export interface BoundPlacement {
279
+ readonly identity: string;
280
+ readonly kind: "file" | "directory";
281
+ }
282
+ /** 一只目录条目(没有 `path`:工厂把它锚在规范键之下,答者给的拼法不作数)。 */
283
+ export type BoundListEntry = Omit<FileInfo, "path">;
284
+ /**
285
+ * 逐答重绑的答者(传输那一半):每一答都是一次完整的重绑 —— 打开、核对它就是 `bound`、经这一次打开作答。shell 腿 = {@link createShellRebindingAnswers};
286
+ * 设备腿 = 一条 `openBound` 协议指令。
287
+ */
288
+ export interface RebindingAnswers {
289
+ open(abortSignal?: AbortSignal): Promise<Result<BoundPlacement, FileError>>;
290
+ size(bound: BoundPlacement): Promise<Result<number, FileError>>;
291
+ readBytes(bound: BoundPlacement, max: number, abortSignal?: AbortSignal): Promise<Result<Uint8Array, FileError>>;
292
+ list(bound: BoundPlacement, abortSignal?: AbortSignal): Promise<Result<BoundListEntry[], FileError>>;
293
+ }
294
+ /** 五条远端腿的答者:四个成员各一答,每一答 = {@link BOUND_READ_SCRIPT} 的一次往返(`createShellRebindingAnswers` 返回活对象 ⇒ `create*`)。 */
295
+ export declare function createShellRebindingAnswers(abs: string, lane: BoundReadLane): RebindingAnswers;
296
+ /**
297
+ * 逐答重绑的 `BoundFileObject`(活对象 ⇒ `create*`):每个成员一答;`close()` 不发往返(远端什么都不持有)。
298
+ * 工厂自己的三道闸(不信任答者):读的不是文件 / 列的不是目录 ⇒ 不发往返、照 core 句拒;`max` 不是非负安全整数 ⇒ `invalid`;
299
+ * 答回的字节比 `max` 多 ⇒ `invalid`(二道闸,同 {@link symlinkChainHopsFromStdout} 的「再切一次」);条目名不是单段 ⇒ 整答拒,条目锚在 `abs` 之下(执行缝路径律)。
300
+ */
301
+ export declare function createRebindingBoundFile(abs: string, bound: BoundPlacement, answers: RebindingAnswers): BoundFileObject;
302
+ /** `openBound` 的整只半场(开答 → 工厂)。答者给的身份与类型就是工厂之后每一答对拍的凭据。 */
303
+ export declare function openRebinding(abs: string, answers: RebindingAnswers, abortSignal?: AbortSignal): Promise<Result<BoundFileObject, FileError>>;
304
+ /** 远端腿 `openBound` 的一行调用(经基座的腿与 e2b 直调;本版 ssh 缓装、不暴露):同一只脚本、同一只读法、同一只工厂。 */
305
+ export declare function openBoundOverShell(abs: string, lane: BoundReadLane, abortSignal?: AbortSignal): Promise<Result<BoundFileObject, FileError>>;
207
306
  /**
208
307
  * 脚本删一只私有目录没删掉时写到 **stdout** 的那一条记录的头:`<头> <目录路径>\0`(NUL 收尾 —— 路径里可以有换行)。
209
308
  * 残留记录走 stdout、错误走 stderr:两条通道不混,分类器读到的 stderr 里没有路径文本。记录不改脚本的退出码:写照它自己的结局答,残留另行留痕。
@@ -1,5 +1,9 @@
1
1
  import { ExecutionError, FileError, SYMLINK_CHAIN_MAX_HOPS } from "@sema-agent/core";
2
+ import { posix } from "node:path";
3
+ import { anchorUnder } from "./exec-path-law.js";
4
+ import { isCanonicalBase64 } from "../device-ws-protocol.js";
2
5
  import { NAME_MAX_BYTES, classifyFsStderr, fileErrorFromExec } from "./remote-env-file-error.js";
6
+ import { echoCallerText } from "../caller-text-echo.js";
3
7
  import { recordFailOpen } from "../observability/fail-open.js";
4
8
  import { createLogger } from "../observability/logger.js";
5
9
  import { seatText } from "../trace/exit-seat.js";
@@ -30,7 +34,7 @@ export function buildCwdEnvCommand(command, dir, env, face) {
30
34
  const nonce = randomBytes(8).toString("hex");
31
35
  cwdGuard = { dir, marker: `${CWD_UNAVAILABLE_LINE} [${nonce}]`, started: startedLineFor(face, nonce) };
32
36
  const line = `${cwdGuard.marker}: ${dir} (cd failed; command not run)`;
33
- parts.push(`cd ${shellQuote(dir)} || { echo ${shellQuote(line)} >&2; exit ${CWD_UNAVAILABLE_EXIT}; };`);
37
+ parts.push(`cd -P ${shellQuote(dir)} || { echo ${shellQuote(line)} >&2; exit ${CWD_UNAVAILABLE_EXIT}; };`);
34
38
  if (cwdGuard.started !== undefined)
35
39
  parts.push(`echo ${shellQuote(cwdGuard.started)} >&2;`);
36
40
  }
@@ -156,6 +160,220 @@ export async function runSymlinkChain(absPath, run) {
156
160
  return { ok: false, error: classifyFsStderr(r.value.stderr, absPath, "unknown") };
157
161
  return { ok: true, value: { hops: symlinkChainHopsFromStdout(r.value.stdout, absPath) } };
158
162
  }
163
+ export const BOUND_READ_EXITS = { link: 90, moved: 91, kind: 92, over: 93, platform: 94, name: 95 };
164
+ const BOUND_READ_TIMEOUT_EXITS = [124, 137, 143];
165
+ export const BOUND_READ_SCRIPT = [
166
+ "f=$1; op=$2; want=$3; wk=$4; max=$5; enc=$6; LC_ALL=C; export LC_ALL; nl=$(printf '\\n.'); nl=${nl%.}",
167
+ `[ -d /proc/self/fd ] || exit ${BOUND_READ_EXITS.platform}`,
168
+ `for t in readlink stat head; do command -v "$t" >/dev/null 2>&1 || exit ${BOUND_READ_EXITS.platform}; done`,
169
+ `if [ "$enc" = base64 ]; then command -v base64 >/dev/null 2>&1 || exit ${BOUND_READ_EXITS.platform}; fi`,
170
+ `if [ -L "$f" ]; then exit ${BOUND_READ_EXITS.link}; fi`,
171
+ `if [ -e "$f" ]; then if [ -f "$f" ] || [ -d "$f" ]; then :; else exit ${BOUND_READ_EXITS.kind}; fi; fi`,
172
+ "{",
173
+ ` p=$(readlink /proc/self/fd/0 && printf .) || exit ${BOUND_READ_EXITS.platform}`,
174
+ ' p=${p%.}; p=${p%"$nl"}',
175
+ ` s=$(stat -L -c '%d:%i %s %F' /proc/self/fd/0) || exit ${BOUND_READ_EXITS.platform}`,
176
+ ` [ "$p" = "$f" ] || exit ${BOUND_READ_EXITS.moved}`,
177
+ ' id=${s%% *}; r=${s#* }; sz=${r%% *}; t=${r#* }',
178
+ ` case $t in "regular file"|"regular empty file") k=file ;; directory) k=directory ;; *) exit ${BOUND_READ_EXITS.kind} ;; esac`,
179
+ ` if [ "$op" = open ]; then :; elif [ "$id" = "$want" ] && [ "$k" = "$wk" ]; then :; else exit ${BOUND_READ_EXITS.moved}; fi`,
180
+ " case $op in",
181
+ ` open) printf '%s %s\\n' "$id" "$k" ;;`,
182
+ ` size) printf '%s\\n' "$sz" ;;`,
183
+ ` readBytes) if [ "$sz" -le "$max" ]; then :; else printf '%s\\n' "$sz"; exit ${BOUND_READ_EXITS.over}; fi`,
184
+ ' n=$sz; [ "$sz" -gt 0 ] || n=$((max + 1))',
185
+ ` printf '%s\\n' "$sz"`,
186
+ ' if [ "$enc" = base64 ]; then { rc=$( { { head -c "$n"; echo "$?" >&4; } | base64 >&3; } 4>&1 ); } 3>&1; [ "$rc" = 0 ] || exit 1; else head -c "$n"; fi ;;',
187
+ " list) cd -P /proc/self/fd/0 || exit 1",
188
+ " set --",
189
+ ` for n in * .[!.]* ..?*; do case $n in *"$nl"*) exit ${BOUND_READ_EXITS.name} ;; esac; if [ -e "$n" ] || [ -L "$n" ]; then set -- "$@" "$n"; fi; done`,
190
+ ` if [ "$#" -gt 0 ]; then stat -c '%f %s %Y %n' -- "$@"; fi ;;`,
191
+ " *) exit 1 ;;",
192
+ " esac",
193
+ " exit",
194
+ '} < "$f"',
195
+ "exit 1",
196
+ ].join("\n");
197
+ export function buildBoundReadCommand(abs, op, args, payload, boundSec) {
198
+ const argv = [abs, op, args.bound?.identity ?? "-", args.bound?.kind ?? "-", String(args.max ?? 0), payload].map(shellQuote).join(" ");
199
+ return `{ command -v timeout >/dev/null 2>&1 || exit ${BOUND_READ_EXITS.platform}; exec timeout ${Math.max(1, Math.floor(boundSec))} sh -c ${shellQuote(BOUND_READ_SCRIPT)} sema-bound-read ${argv}; }`;
200
+ }
201
+ const BOUND_SMALL_ANSWER_BYTES = 4096;
202
+ const BOUND_LIST_ANSWER_BYTES = 16 * 1024 * 1024;
203
+ const BOUND_IDENTITY_RE = /^\d+:\d+$/;
204
+ const BOUND_DECIMAL_RE = /^\d+$/;
205
+ function overCapError(abs, max, bytes) {
206
+ return new FileError("invalid", `The held file is over the ${max}-byte read cap (${bytes} bytes or more); it was not read`, abs);
207
+ }
208
+ function malformedAnswer(abs, what) {
209
+ return new FileError("unknown", `the target's bound-read answer was malformed (${what}); nothing was read`, abs);
210
+ }
211
+ function boundReadRefusal(word, abs, max, stdout) {
212
+ switch (word) {
213
+ case "link":
214
+ return new FileError("precondition_failed", "The path is now a link, not the object that was checked", abs);
215
+ case "moved":
216
+ return new FileError("precondition_failed", "The path named another object than the one opened", abs);
217
+ case "kind":
218
+ return new FileError("invalid", "Neither a regular file nor a directory", abs);
219
+ case "over":
220
+ return overCapError(abs, max, BOUND_DECIMAL_RE.test(stdout.trim()) ? stdout.trim() : "more than the cap");
221
+ case "platform":
222
+ return new FileError("not_supported", "This target cannot bind a read: it gives no /proc/self/fd observation of an opened file (not Linux, or /proc is not mounted), or it lacks timeout / readlink / stat / head / base64", abs);
223
+ case "name":
224
+ return new FileError("invalid", "A name in this directory contains a newline, which this listing cannot carry unambiguously; the directory was not listed", abs);
225
+ }
226
+ }
227
+ function boundReadExitOf(code) {
228
+ for (const word of Object.keys(BOUND_READ_EXITS))
229
+ if (BOUND_READ_EXITS[word] === code)
230
+ return word;
231
+ return undefined;
232
+ }
233
+ function answerCap(op, max, payload) {
234
+ switch (op) {
235
+ case "open":
236
+ case "size":
237
+ return BOUND_SMALL_ANSWER_BYTES;
238
+ case "list":
239
+ return BOUND_LIST_ANSWER_BYTES;
240
+ case "readBytes": {
241
+ const body = payload === "base64" ? Math.ceil((max + 1) / 3) * 4 : max + 1;
242
+ return BOUND_SMALL_ANSWER_BYTES + body + Math.ceil(body / 76) + 1;
243
+ }
244
+ }
245
+ }
246
+ async function runBoundOp(abs, op, args, lane, abortSignal) {
247
+ const max = args.max ?? 0;
248
+ const cap = answerCap(op, max, lane.payload);
249
+ const r = await lane.run(buildBoundReadCommand(abs, op, args, lane.payload, lane.boundSec), cap, abortSignal);
250
+ if (!r.ok)
251
+ return { ok: false, error: r.error.code === "timeout" ? new FileError("timeout", `the bound read ran past the lane's bound: ${r.error.message}`, abs, r.error) : fileErrorFromExec(r.error, abs) };
252
+ const { exitCode, stderr } = r.value;
253
+ const stdout = Buffer.from(r.value.stdout);
254
+ if (exitCode !== 0) {
255
+ const word = boundReadExitOf(exitCode);
256
+ if (word !== undefined)
257
+ return { ok: false, error: boundReadRefusal(word, abs, max, stdout.subarray(0, 64).toString("latin1")) };
258
+ if (BOUND_READ_TIMEOUT_EXITS.includes(exitCode))
259
+ return { ok: false, error: new FileError("timeout", `the bound read on the target ran past its ${lane.boundSec}s bound and was killed; nothing was read`, abs) };
260
+ return { ok: false, error: classifyFsStderr(stderr, abs, "unknown") };
261
+ }
262
+ if (stdout.length > cap)
263
+ return { ok: false, error: malformedAnswer(abs, `over the ${cap}-byte bound of a ${op} answer`) };
264
+ return { ok: true, value: stdout };
265
+ }
266
+ function bytesOfReadAnswer(abs, out, payload) {
267
+ const nl = out.indexOf(0x0a);
268
+ const head = nl < 0 ? "" : out.subarray(0, nl).toString("latin1");
269
+ if (!BOUND_DECIMAL_RE.test(head))
270
+ return { ok: false, error: malformedAnswer(abs, "no size line") };
271
+ const size = Number(head);
272
+ const rest = out.subarray(nl + 1);
273
+ let bytes;
274
+ if (payload === "base64") {
275
+ const text = rest.toString("latin1").replace(/\s+/g, "");
276
+ if (!isCanonicalBase64(text))
277
+ return { ok: false, error: malformedAnswer(abs, "the payload is not canonical base64") };
278
+ bytes = Buffer.from(text, "base64");
279
+ }
280
+ else {
281
+ bytes = rest;
282
+ }
283
+ if (size > 0 && bytes.length !== size) {
284
+ return { ok: false, error: new FileError("unknown", `the file was ${size} bytes when the read started but ${bytes.length} arrived (it changed while it was read, or the transfer was cut); nothing was read`, abs) };
285
+ }
286
+ return { ok: true, value: new Uint8Array(bytes) };
287
+ }
288
+ function entriesOfListAnswer(abs, out) {
289
+ const entries = [];
290
+ for (const line of out.toString("utf8").split("\n")) {
291
+ if (line === "")
292
+ continue;
293
+ const m = /^([0-9a-fA-F]+) (\d+) (\d+) (.+)$/.exec(line);
294
+ if (m === null)
295
+ return { ok: false, error: malformedAnswer(abs, "a listing line is not `<mode> <size> <mtime> <name>`") };
296
+ const type = parseInt(m[1], 16) & 0o170000;
297
+ const kind = type === 0o100000 ? "file" : type === 0o040000 ? "directory" : type === 0o120000 ? "symlink" : undefined;
298
+ if (kind === undefined)
299
+ continue;
300
+ entries.push({ name: m[4], kind, size: Number(m[2]), mtimeMs: Number(m[3]) * 1000 });
301
+ }
302
+ return { ok: true, value: entries };
303
+ }
304
+ export function createShellRebindingAnswers(abs, lane) {
305
+ return {
306
+ async open(abortSignal) {
307
+ const r = await runBoundOp(abs, "open", {}, lane, abortSignal);
308
+ if (!r.ok)
309
+ return r;
310
+ const [identity, kind, ...more] = r.value.toString("latin1").trim().split(" ");
311
+ if (identity === undefined || !BOUND_IDENTITY_RE.test(identity) || (kind !== "file" && kind !== "directory") || more.length > 0)
312
+ return { ok: false, error: malformedAnswer(abs, "the open answer is not `<dev>:<inode> <file|directory>`") };
313
+ return { ok: true, value: { identity, kind } };
314
+ },
315
+ async size(bound) {
316
+ const r = await runBoundOp(abs, "size", { bound }, lane);
317
+ if (!r.ok)
318
+ return r;
319
+ const text = r.value.toString("latin1").trim();
320
+ if (!BOUND_DECIMAL_RE.test(text))
321
+ return { ok: false, error: malformedAnswer(abs, "the size answer is not a decimal") };
322
+ return { ok: true, value: Number(text) };
323
+ },
324
+ async readBytes(bound, max, abortSignal) {
325
+ const r = await runBoundOp(abs, "readBytes", { bound, max }, lane, abortSignal);
326
+ return r.ok ? bytesOfReadAnswer(abs, r.value, lane.payload) : r;
327
+ },
328
+ async list(bound, abortSignal) {
329
+ const r = await runBoundOp(abs, "list", { bound }, lane, abortSignal);
330
+ return r.ok ? entriesOfListAnswer(abs, r.value) : r;
331
+ },
332
+ };
333
+ }
334
+ export function createRebindingBoundFile(abs, bound, answers) {
335
+ return {
336
+ identity: bound.identity,
337
+ kind: bound.kind,
338
+ size: () => answers.size(bound),
339
+ async readBytes(max, abortSignal) {
340
+ if (bound.kind !== "file")
341
+ return { ok: false, error: new FileError("is_directory", "The held object is a directory", abs) };
342
+ if (!Number.isSafeInteger(max) || max < 0)
343
+ return { ok: false, error: new FileError("invalid", `the read cap must be a non-negative integer (got ${String(max)})`, abs) };
344
+ const r = await answers.readBytes(bound, max, abortSignal);
345
+ if (!r.ok)
346
+ return r;
347
+ if (r.value.length > max)
348
+ return { ok: false, error: overCapError(abs, max, r.value.length) };
349
+ return r;
350
+ },
351
+ async list(abortSignal) {
352
+ if (bound.kind !== "directory")
353
+ return { ok: false, error: new FileError("not_directory", "The held object is a file", abs) };
354
+ const r = await answers.list(bound, abortSignal);
355
+ if (!r.ok)
356
+ return r;
357
+ const out = [];
358
+ for (const e of r.value) {
359
+ if (e.name === "" || e.name === "." || e.name === ".." || e.name.includes("/"))
360
+ return { ok: false, error: malformedAnswer(abs, `a listed name is not one path segment: ${JSON.stringify(echoCallerText(e.name, { maxChars: 80 }))}`) };
361
+ out.push({ ...e, path: anchorUnder(posix, abs, e.name) });
362
+ }
363
+ return { ok: true, value: out };
364
+ },
365
+ close: async () => undefined,
366
+ };
367
+ }
368
+ export async function openRebinding(abs, answers, abortSignal) {
369
+ const opened = await answers.open(abortSignal);
370
+ if (!opened.ok)
371
+ return opened;
372
+ return { ok: true, value: createRebindingBoundFile(abs, opened.value, answers) };
373
+ }
374
+ export function openBoundOverShell(abs, lane, abortSignal) {
375
+ return openRebinding(abs, createShellRebindingAnswers(abs, lane), abortSignal);
376
+ }
159
377
  const STAGED_FILE = "data";
160
378
  const STAGE_DIR_REFUSED_EXIT = 3;
161
379
  const STAGE_DIR_UNSEALED_EXIT = 4;
@@ -184,11 +402,11 @@ function hardenPrivate(dir) {
184
402
  return `( chmod -- 700 ${dir} 2>/dev/null && ${readModeOwner(dir)} && case "$1" in drw[xs]--[-S]--[-T]*) [ "$3" = "$(id -u)" ];; *) false;; esac && ${aclCleared(dir)} )`;
185
403
  }
186
404
  function aclCleared(dir) {
187
- const marker = `ls_out=$(cd ${dir} && ls -ld . 2>&1) && [ -z "$(printf '%s\\n' "$ls_out" | sed -n 2p)" ] && mode_word=\${ls_out%% *} && ` +
405
+ const marker = `ls_out=$(cd -P ${dir} && ls -ld . 2>&1) && [ -z "$(printf '%s\\n' "$ls_out" | sed -n 2p)" ] && mode_word=\${ls_out%% *} && ` +
188
406
  `case "$mode_word" in d?????????*+*) echo +;; d?????????*) ;; *) false;; esac`;
189
407
  return (`plat=$(uname -s 2>/dev/null) && case "$plat" in ` +
190
408
  `Linux) acl_read() { :; }; acl_strip() { false; };; ` +
191
- `Darwin) acl_read() { ls_out=$(cd ${dir} && ls -led . 2>&1) && case "$ls_out" in d?????????*) ;; *) false;; esac && printf '%s\\n' "$ls_out" | sed 1d; }; acl_strip() { chmod -N ${dir}; };; ` +
409
+ `Darwin) acl_read() { ls_out=$(cd -P ${dir} && ls -led . 2>&1) && case "$ls_out" in d?????????*) ;; *) false;; esac && printf '%s\\n' "$ls_out" | sed 1d; }; acl_strip() { chmod -N ${dir}; };; ` +
192
410
  `FreeBSD) acl_read() { ${marker}; }; acl_strip() { setfacl -b ${dir}; };; ` +
193
411
  `SunOS) acl_read() { ${marker}; }; acl_strip() { chmod A- ${dir}; };; ` +
194
412
  `*) false;; esac && acl=$(acl_read 2>/dev/null) && { [ -z "$acl" ] || { acl_strip 2>/dev/null && acl=$(acl_read 2>/dev/null) && [ -z "$acl" ]; }; }`);