@platless/axom-cli 0.1.0 → 0.5.9

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 (281) hide show
  1. package/README.md +1 -1
  2. package/dist/authScopes.js +19 -0
  3. package/dist/axom-patch/cli/README.md +132 -0
  4. package/dist/axom-patch/cli/account-limits/README.md +27 -0
  5. package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
  6. package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
  7. package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
  8. package/dist/axom-patch/cli/account-limits/generate.py +120 -0
  9. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +64 -0
  10. package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
  11. package/dist/axom-patch/cli/claude-account-limits.json +37 -0
  12. package/dist/axom-patch/cli/codex-account-limits.json +37 -0
  13. package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
  14. package/dist/axom-patch/cli/nightly-task.md +152 -0
  15. package/dist/axom-patch/cli/pi-steering.json +37 -0
  16. package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
  17. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +38 -0
  18. package/dist/axom-patch/cli/probes/pi-steering.mjs +80 -0
  19. package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
  20. package/dist/axom-patch/cli/probes/qoder-steering.mjs +89 -0
  21. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +61 -0
  22. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
  23. package/dist/axom-patch/cli/qoder-background-terminal.md +82 -0
  24. package/dist/axom-patch/cli/qoder-foreground-output.md +42 -0
  25. package/dist/axom-patch/cli/qoder-steering.json +52 -0
  26. package/dist/axom-patch/cli/qoder-steering.md +30 -0
  27. package/dist/axom-patch/cli/qoder-subagent-origin.json +73 -0
  28. package/dist/axom-patch/cli/qoder-subagent-subwork.md +73 -0
  29. package/dist/axom-patch/cli/runtime.json +63 -0
  30. package/dist/axom-patch/cli/steering/claude-steering-js.patch +89 -0
  31. package/dist/axom-patch/cli/steering/claude-steering-types.patch +28 -0
  32. package/dist/axom-patch/cli/steering/codex-steering.patch +57 -0
  33. package/dist/axom-patch/cli/steering/pi-steering.js +79 -0
  34. package/dist/axom-patch/cli/zcode-account-limits.json +31 -0
  35. package/dist/axom-patch/cli/zcode-models.json +38 -0
  36. package/dist/axom-patch/cli/zcode-subagent-subwork.json +73 -0
  37. package/dist/cli.js +36 -1
  38. package/dist/commands/auth.js +4 -13
  39. package/dist/commands/execute.js +4 -4
  40. package/dist/commands/record.js +6 -6
  41. package/dist/commands/struct.js +4 -4
  42. package/dist/commands/workstation.js +2 -2
  43. package/dist/credentials.js +2 -2
  44. package/dist/install/claude/commands.js +13 -13
  45. package/dist/keepers/acp/keeper.mjs +3083 -0
  46. package/dist/keepers/acp/manifest.json +9 -0
  47. package/dist/keepers/terminal/keeper.mjs +8469 -0
  48. package/dist/keepers/terminal/manifest.json +9 -0
  49. package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  50. package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  51. package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
  52. package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
  53. package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
  54. package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
  55. package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
  56. package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
  57. package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
  58. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
  59. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
  60. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
  61. package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  62. package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
  63. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  64. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  65. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  66. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  67. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  68. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  69. package/dist/mcp.js +32 -14
  70. package/dist/runtime-deps/qoder-sdk/package-lock.json +1189 -0
  71. package/dist/runtime-deps/qoder-sdk/package.json +8 -0
  72. package/dist/test/keeperKit.js +67 -0
  73. package/dist/workstation/acp/acpAgents.js +2514 -681
  74. package/dist/workstation/acp/acpClient.js +95 -318
  75. package/dist/workstation/acp/activityBusy.js +43 -0
  76. package/dist/workstation/acp/activityReporting.js +54 -0
  77. package/dist/workstation/acp/adoption.js +28 -356
  78. package/dist/workstation/acp/authUrlSniffer.js +36 -0
  79. package/dist/workstation/acp/binaryInstall.js +100 -27
  80. package/dist/workstation/acp/builds/gc.js +245 -0
  81. package/dist/workstation/acp/builds/install/binary.js +28 -0
  82. package/dist/workstation/acp/builds/install/npm.js +235 -0
  83. package/dist/workstation/acp/builds/install/uv.js +254 -0
  84. package/dist/workstation/acp/builds/migrate.js +195 -0
  85. package/dist/workstation/acp/builds/model.js +128 -0
  86. package/dist/workstation/acp/builds/nodeGate.js +100 -0
  87. package/dist/workstation/acp/builds/store.js +352 -0
  88. package/dist/workstation/acp/commandTableProbe.js +68 -0
  89. package/dist/workstation/acp/configOptions.js +103 -1
  90. package/dist/workstation/acp/delegationMcp.js +24 -0
  91. package/dist/workstation/acp/envSecrets.js +27 -0
  92. package/dist/workstation/acp/initializeResponse.js +285 -0
  93. package/dist/workstation/acp/installErrors.js +12 -2
  94. package/dist/workstation/acp/installProbe.js +1 -1
  95. package/dist/workstation/acp/managedPatches.js +144 -0
  96. package/dist/workstation/acp/outbox.js +330 -0
  97. package/dist/workstation/acp/patchAssets.js +80 -0
  98. package/dist/workstation/acp/permission.js +335 -37
  99. package/dist/workstation/acp/processor/checkpoint.js +87 -0
  100. package/dist/workstation/acp/processor/commands.js +35 -0
  101. package/dist/workstation/acp/processor/control.js +355 -0
  102. package/dist/workstation/acp/processor/emit.js +26 -0
  103. package/dist/workstation/acp/processor/jsonrpc.js +172 -0
  104. package/dist/workstation/acp/processor/memoryLog.js +49 -0
  105. package/dist/workstation/acp/processor/origins.js +48 -0
  106. package/dist/workstation/acp/processor/output.js +67 -0
  107. package/dist/workstation/acp/processor/projection.js +42 -0
  108. package/dist/workstation/acp/processor/record.js +128 -0
  109. package/dist/workstation/acp/processor/runtime.js +181 -0
  110. package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
  111. package/dist/workstation/acp/processor/session.js +525 -0
  112. package/dist/workstation/acp/processor/sideChannel.js +80 -0
  113. package/dist/workstation/acp/processor/state.js +74 -0
  114. package/dist/workstation/acp/processor/step.js +459 -0
  115. package/dist/workstation/acp/processor/test/fixtureLog.js +65 -0
  116. package/dist/workstation/acp/processor/test/harness.js +120 -0
  117. package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
  118. package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
  119. package/dist/workstation/acp/processor/transport/generations.js +76 -0
  120. package/dist/workstation/acp/processor/transport/keeper.js +130 -0
  121. package/dist/workstation/acp/processor/transport/local.js +269 -0
  122. package/dist/workstation/acp/processor/transport/types.js +78 -0
  123. package/dist/workstation/acp/processor/updates.js +364 -0
  124. package/dist/workstation/acp/processorSession.js +1512 -0
  125. package/dist/workstation/acp/qoderStartup.js +45 -0
  126. package/dist/workstation/acp/registry/catalog.js +385 -4
  127. package/dist/workstation/acp/registry/detect.js +55 -0
  128. package/dist/workstation/acp/registry/launch.js +61 -119
  129. package/dist/workstation/acp/sessionUpdateMapper.js +56 -27
  130. package/dist/workstation/acp/steering.js +58 -0
  131. package/dist/workstation/acp/switching.js +114 -0
  132. package/dist/workstation/acp/terminalLogin.js +171 -0
  133. package/dist/workstation/acp/terminalLoginSpec.js +41 -0
  134. package/dist/workstation/acp/test/adapterFixtures.js +256 -0
  135. package/dist/workstation/acp/test/daemonLife.js +106 -0
  136. package/dist/workstation/acp/test/fakeAgent.js +185 -9
  137. package/dist/workstation/acp/test/keeperWorld.js +267 -0
  138. package/dist/workstation/acp/test/memoryOutbox.js +55 -0
  139. package/dist/workstation/acp/test/raw/fixtures.js +196 -0
  140. package/dist/workstation/acp/test/raw/qoderCaptures.js +125 -0
  141. package/dist/workstation/acp/test/raw/sanitize.js +344 -0
  142. package/dist/workstation/acp/test/raw/sources.js +249 -0
  143. package/dist/workstation/acp/test/realAdapter.js +377 -0
  144. package/dist/workstation/acp/test/scriptAgent.js +206 -0
  145. package/dist/workstation/acp/test/sessionFrames.js +42 -0
  146. package/dist/workstation/acp/timedCommand.js +4 -2
  147. package/dist/workstation/acp/uvxInstall.js +72 -122
  148. package/dist/workstation/acp/validate.js +33 -5
  149. package/dist/workstation/agentFlow.js +103 -0
  150. package/dist/workstation/agentSessions.js +1686 -348
  151. package/dist/workstation/channels/agent.js +14 -6
  152. package/dist/workstation/channels/computer.js +203 -7
  153. package/dist/workstation/channels/computerExecs.js +200 -0
  154. package/dist/workstation/channels/management.js +86 -6
  155. package/dist/workstation/config.js +3 -1
  156. package/dist/workstation/connection/encryptedChannel.js +88 -21
  157. package/dist/workstation/daemon.js +309 -6
  158. package/dist/workstation/daemonctl.js +28 -2
  159. package/dist/workstation/desktop/binding.js +36 -0
  160. package/dist/workstation/desktop/bridge.js +212 -0
  161. package/dist/workstation/desktop/flow-control.js +52 -0
  162. package/dist/workstation/desktop/focused-text.js +130 -0
  163. package/dist/workstation/desktop/protocol.js +35 -0
  164. package/dist/workstation/exec/attach.js +126 -0
  165. package/dist/workstation/exec/command.js +40 -0
  166. package/dist/workstation/exec/durable.js +517 -0
  167. package/dist/workstation/exec/fallback.js +106 -0
  168. package/dist/workstation/exec/policy.js +837 -0
  169. package/dist/workstation/exec/shaping.js +211 -0
  170. package/dist/workstation/exec.js +3 -112
  171. package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
  172. package/dist/workstation/files/adapters/toolRequests.js +121 -0
  173. package/dist/workstation/files/client.js +455 -0
  174. package/dist/workstation/files/errors.js +28 -0
  175. package/dist/workstation/files/fence.js +350 -0
  176. package/dist/workstation/files/find/fileIndex.js +153 -0
  177. package/dist/workstation/files/find/fuzzy.js +155 -0
  178. package/dist/workstation/files/host.js +122 -0
  179. package/dist/workstation/files/hostProtocol.js +2 -0
  180. package/dist/workstation/files/media/cache.js +620 -0
  181. package/dist/workstation/files/media/encoding.js +308 -0
  182. package/dist/workstation/files/media/estimator.js +100 -0
  183. package/dist/workstation/files/media/media.js +325 -0
  184. package/dist/workstation/files/media/packedAudio.js +23 -0
  185. package/dist/workstation/files/media/scheduler.js +551 -0
  186. package/dist/workstation/files/media/types.js +5 -0
  187. package/dist/workstation/files/scope.js +45 -0
  188. package/dist/workstation/files/structured.js +284 -0
  189. package/dist/workstation/files/text.js +356 -0
  190. package/dist/workstation/files/uploads.js +351 -0
  191. package/dist/workstation/journal.js +65 -236
  192. package/dist/workstation/keepers/acp/client.js +101 -0
  193. package/dist/workstation/keepers/acp/envelope.js +62 -0
  194. package/dist/workstation/keepers/acp/host.js +419 -0
  195. package/dist/workstation/keepers/acp/main.js +18 -0
  196. package/dist/workstation/keepers/acp/memoryStream.js +121 -0
  197. package/dist/workstation/keepers/acp/params.js +137 -0
  198. package/dist/workstation/keepers/acp/protocol.js +66 -0
  199. package/dist/workstation/keepers/acp/recover.js +120 -0
  200. package/dist/workstation/keepers/acp/session.js +584 -0
  201. package/dist/workstation/keepers/acp/stderr.js +154 -0
  202. package/dist/workstation/keepers/kit/activity.js +103 -0
  203. package/dist/workstation/keepers/kit/bootstrap.js +164 -0
  204. package/dist/workstation/keepers/kit/buildVersion.js +51 -0
  205. package/dist/workstation/keepers/kit/bundle.js +120 -0
  206. package/dist/workstation/keepers/kit/client.js +432 -0
  207. package/dist/workstation/keepers/kit/identity.js +21 -0
  208. package/dist/workstation/keepers/kit/ipc.js +112 -0
  209. package/dist/workstation/keepers/kit/launchConfig.js +8 -0
  210. package/dist/workstation/keepers/kit/launcher.js +249 -0
  211. package/dist/workstation/keepers/kit/lock.js +123 -0
  212. package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
  213. package/dist/workstation/keepers/kit/paths.js +75 -0
  214. package/dist/workstation/keepers/kit/protocol.js +43 -0
  215. package/dist/workstation/keepers/kit/server.js +518 -0
  216. package/dist/workstation/keepers/kit/streamLog.js +697 -0
  217. package/dist/workstation/keepers/legacy.js +296 -0
  218. package/dist/workstation/keepers/policy.js +576 -0
  219. package/dist/workstation/keepers/stop.js +34 -0
  220. package/dist/workstation/keepers/terminal/client.js +213 -0
  221. package/dist/workstation/keepers/terminal/exec.js +981 -0
  222. package/dist/workstation/keepers/terminal/generations.js +129 -0
  223. package/dist/workstation/keepers/terminal/keeper.js +62 -0
  224. package/dist/workstation/keepers/terminal/main.js +43 -0
  225. package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
  226. package/dist/workstation/keepers/terminal/params.js +27 -0
  227. package/dist/workstation/keepers/terminal/protocol.js +56 -0
  228. package/dist/workstation/keepers/terminal/pty.js +330 -0
  229. package/dist/workstation/oauth/deviceAuthorization.js +21 -12
  230. package/dist/workstation/protocol/accountLimits.js +47 -0
  231. package/dist/workstation/protocol/agent.js +201 -8
  232. package/dist/workstation/protocol/common.js +36 -0
  233. package/dist/workstation/protocol/computer.js +107 -7
  234. package/dist/workstation/protocol/management.js +50 -2
  235. package/dist/workstation/qoderAdapter/backgroundProcess.js +118 -0
  236. package/dist/workstation/qoderAdapter/backgroundTerminal.js +193 -0
  237. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +372 -0
  238. package/dist/workstation/qoderAdapter/callbackContext.js +76 -0
  239. package/dist/workstation/qoderAdapter/completedContinuation.js +139 -0
  240. package/dist/workstation/qoderAdapter/contract.js +78 -0
  241. package/dist/workstation/qoderAdapter/foregroundOutput.js +36 -0
  242. package/dist/workstation/qoderAdapter/history.js +85 -0
  243. package/dist/workstation/qoderAdapter/identityIndex.js +191 -0
  244. package/dist/workstation/qoderAdapter/inputQueue.js +42 -0
  245. package/dist/workstation/qoderAdapter/main.js +95 -0
  246. package/dist/workstation/qoderAdapter/mapper.js +117 -0
  247. package/dist/workstation/qoderAdapter/observerPlugin.js +245 -0
  248. package/dist/workstation/qoderAdapter/observerReceiver.js +187 -0
  249. package/dist/workstation/qoderAdapter/permissions.js +61 -0
  250. package/dist/workstation/qoderAdapter/profile.js +93 -0
  251. package/dist/workstation/qoderAdapter/sdkDriver.js +48 -0
  252. package/dist/workstation/qoderAdapter/sdkRuntime.js +93 -0
  253. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +145 -0
  254. package/dist/workstation/qoderAdapter/session.js +319 -0
  255. package/dist/workstation/qoderAdapter/subagentMeta.js +28 -0
  256. package/dist/workstation/qoderAdapter/subwork.js +85 -0
  257. package/dist/workstation/qoderAdapter/taskControl.js +38 -0
  258. package/dist/workstation/qoderAdapter/taskLedger.js +163 -0
  259. package/dist/workstation/qoderAdapter/types.js +1 -0
  260. package/dist/workstation/qoderAdapter/usage.js +37 -0
  261. package/dist/workstation/register.js +4 -0
  262. package/dist/workstation/systemMetrics.js +110 -1
  263. package/dist/workstation/tools/browser.js +394 -0
  264. package/dist/workstation/tools/client.js +277 -0
  265. package/dist/workstation/tools/info.js +50 -0
  266. package/dist/workstation/tools/keeperProcess.js +124 -0
  267. package/dist/workstation/tools/router.js +55 -0
  268. package/dist/workstation/tools/terminalRouter.js +222 -0
  269. package/dist/workstation/tools/validation.js +86 -0
  270. package/dist/workstation/update/coordinator.js +32 -0
  271. package/dist/workstation/update/npmInstaller.js +2 -0
  272. package/dist/workstation/update/ptyPreflight.js +45 -0
  273. package/dist/workstation/update/taskRegistry.js +141 -23
  274. package/dist/workstation/update/watchdog.js +66 -0
  275. package/package.json +20 -7
  276. package/skills/axom/SKILL.md +55 -17
  277. package/skills/axom-definition/SKILL.md +32 -32
  278. package/skills/axom-platform/SKILL.md +39 -39
  279. package/dist/workstation/acp/acpSession.js +0 -1047
  280. package/dist/workstation/acp/contract.js +0 -494
  281. package/dist/workstation/acp/draftInterceptor.js +0 -154
@@ -1,19 +1,16 @@
1
1
  /**
2
- * The `uv tool` counterpart to `installProbe.ts`: uvx-distributed adapters (`fast-agent`,
3
- * `minion-code` — see the ACP registry's `distribution.uvx`) are, from this task on, installed by
4
- * us with `uv tool install` rather than launched with `uvx <pkg>`. That switch is the whole point
5
- * of this module.
2
+ * `uv tool` plumbing for uvx-distributed adapters (`fast-agent-acp`, `minion-code` — the ACP
3
+ * registry's `distribution.uvx`). Since Task 15b these are installed as daemon-owned, per-version
4
+ * builds (`builds/install/uv.ts`): every `uv` call here runs with `UV_TOOL_DIR`/`UV_TOOL_BIN_DIR`
5
+ * pointed into one build's own directory, never at the user's `uv tool dir` or `~/.local/bin`, and only
6
+ * at install time — a launch reads the entry recorded in `builds.json` and never starts `uv`.
6
7
  *
7
- * ## Why the launch path had to change, not just the probe
8
+ * ## Why not `uvx <pkg>`
8
9
  *
9
10
  * `uvx <pkg>` resolves through uv's own ephemeral-environment cache — directories named by a hash
10
11
  * of the resolved environment, a layout this codebase has never verified and has no reason to
11
- * trust. "Is it installed, and which version" is unanswerable against that cache the same way it
12
- * would be unanswerable against npm's `_npx/<hash>` before `installProbe.ts` existed. `uv tool
13
- * install` instead puts the tool under `uv tool dir` — one stable path per machine that `uv tool
14
- * list` describes completely — which is the same guarantee `readNpxCache`/`findOnPath` give for the
15
- * npx and binary paths. Everything here exists to read that directory the way `installProbe.ts`
16
- * reads npm's.
12
+ * trust. `uv tool install` instead puts the tool under `UV_TOOL_DIR` — a path we choose — and `uv
13
+ * tool list` (run with the same env) describes it completely.
17
14
  *
18
15
  * ## Measured evidence (2026-09-05, uv 0.11.5, macOS aarch64) — this is what the parsing below is
19
16
  * built on, not a guess
@@ -68,48 +65,41 @@
68
65
  * (true for `httpie` among its three); otherwise **neither** — this refuses rather than guesses.
69
66
  * uv's listing order is stable on one machine but not a documented contract, this path has never
70
67
  * been exercised against either real registry package, and a wrong guess here launches the wrong
71
- * binary silently, which is worse than a refusal a caller can act on. See {@link uvToolEntrypoint}
68
+ * binary silently, which is worse than a refusal a caller can act on. See {@link uvToolEntrypointFrom}
72
69
  * for the resulting three-outcome contract.
70
+ *
71
+ * ## Re-measured for per-build dirs (2026-10-11, uv 0.12.23, macOS aarch64, local wheel)
72
+ *
73
+ * With `UV_TOOL_DIR=<b>/tools UV_TOOL_BIN_DIR=<b>/bin`, `uv tool install` writes only under `<b>`
74
+ * (plus uv's download cache): the entry script's shebang is `<b>/tools/<pkg>/bin/python` (absolute),
75
+ * `<b>/bin/<exe>` is an absolute symlink to it, and `uv-receipt.toml` records both. A tree renamed
76
+ * after install therefore does not run — which is why uv builds are installed in place under a
77
+ * completion marker rather than staged and renamed (`builds/model.ts`).
73
78
  */
74
79
  import { runTimedCommand } from './timedCommand.js';
75
- /**
76
- * Wall-clock budget for one `uv tool list` / `uv tool dir` read — a local, no-network directory
77
- * probe, not a download. "A few seconds is generous" for that: 10s leaves ample headroom above
78
- * every measured run (low tens of milliseconds — see this module's own doc comment) while still
79
- * keeping a genuinely stuck `uv` (a broken shim, a filesystem wedged mid-fsck) from parking
80
- * `AcpAgents.roster` or a launch indefinitely.
81
- */
80
+ /** Wall-clock budget for one `uv tool list` — a local, no-network directory read, not a download. */
82
81
  export const UV_PROBE_TIMEOUT_MS = 10_000;
83
82
  /**
84
- * Wall-clock budget for one `uv tool install <spec>` — a real package download, not a probe.
85
- *
86
- * Matches `installProbe.ts`'s `ADAPTER_INSTALL_TIMEOUT_MS` and `binaryInstall.ts`'s
87
- * `ARCHIVE_DOWNLOAD_TIMEOUT_MS` deliberately, for the same reason `binaryInstall.ts`'s own constant
88
- * gives for matching the first of those: this is the third of three siblings (npx, binary archive,
89
- * uvx tool) fetching a whole ACP adapter over the network, and there is no reason the one added
90
- * last should time out sooner than the other two just because it arrived later.
83
+ * Wall-clock budget for one `uv tool install <spec>` — a real package download, not a probe. Matches
84
+ * the npm and binary adapter install budgets: there is no reason the third sibling should time out sooner.
91
85
  */
92
86
  export const UV_TOOL_INSTALL_TIMEOUT_MS = 300_000;
93
87
  /**
94
- * Default `RunUv`: runs `uv <args>` through {@link runTimedCommand} (never `execFileSync`, see this
95
- * type's own doc comment), with every failure — a non-zero exit, `uv` not being on `PATH` at all
96
- * (`ENOENT`), or the run exceeding `timeoutMs` — folded into `{ ok: false }` rather than thrown. A
97
- * machine without `uv` installed is a normal machine, not an error condition this module gets to
98
- * raise; a `uv` that hangs past its budget is reported with a message naming the command and the
99
- * budget, not silently swallowed.
88
+ * Default `RunUv`: runs `uv <args>` through {@link runTimedCommand}, with every failure — a non-zero
89
+ * exit, `uv` not being on `PATH` at all (`ENOENT`), or the run exceeding `timeoutMs` — folded into
90
+ * `{ ok: false }` rather than thrown. A machine without `uv` is a normal machine.
100
91
  */
101
- async function defaultRunUv(args, timeoutMs) {
102
- const result = await runTimedCommand('uv', args, timeoutMs);
92
+ export async function defaultRunUv(args, timeoutMs, env) {
93
+ const result = await runTimedCommand('uv', args, timeoutMs, env ? { env: { ...process.env, ...env } } : {});
103
94
  if (result.ok)
104
95
  return { ok: true, stdout: result.stdout };
105
- return { ok: false, stdout: '', error: result.message };
96
+ return { ok: false, stdout: '', error: result.message, ...(result.code ? { code: result.code } : {}) };
106
97
  }
107
98
  /**
108
99
  * Parses `uv tool list --show-paths` output (also tolerates the plain `uv tool list` form, with no
109
- * `(<path>)` suffixes, which is what makes the fixtures above double as fixtures for both). A tool
110
- * header line reads `<name> vX.Y.Z` or `<name> vX.Y.Z (<tool dir>)`; each executable line under it
111
- * reads `- <name>` or `- <name> (<absolute path>)`. `No tools installed` (uv's output for an empty
112
- * roster) matches neither pattern and yields no entries, which is the correct answer.
100
+ * `(<path>)` suffixes). A tool header line reads `<name> vX.Y.Z` or `<name> vX.Y.Z (<tool dir>)`; each
101
+ * executable line under it reads `- <name>` or `- <name> (<absolute path>)`. `No tools installed`
102
+ * matches neither pattern and yields no entries, which is the correct answer.
113
103
  */
114
104
  function parseToolList(stdout) {
115
105
  const entries = [];
@@ -132,117 +122,77 @@ function parseToolList(stdout) {
132
122
  return entries;
133
123
  }
134
124
  /**
135
- * One `uv tool list --show-paths` snapshot, parsed. This is the single place that actually spawns
136
- * `uv` for the "what's installed" question — {@link findTool} delegates to it rather than shelling
137
- * out itself, and `AcpAgents.roster` calls it directly, exactly **once per roster build**, so it can
138
- * look every uvx-distributed agent up in one snapshot instead of spawning `uv` again per agent (see
139
- * that call site's own doc comment for why a per-agent spawn was a defect worth hoisting out of).
140
- * `uv` missing, failing, or timing out degrades to `[]` — "nothing installed" — the same as every
141
- * other reader of {@link RunUv} in this module; it never throws. `timeoutMs` defaults to
142
- * {@link UV_PROBE_TIMEOUT_MS} — this is the probe half of `RunUv`'s two budgets, never the
143
- * `uv tool install` one.
125
+ * One `uv tool list --show-paths` snapshot, parsed, of the tool dir `env` names (a build's own; see
126
+ * `builds/install/uv.ts`). `uv` missing, failing, or timing out degrades to `[]` — "nothing
127
+ * installed"; it never throws.
144
128
  */
145
- export async function listUvTools(run = defaultRunUv, timeoutMs = UV_PROBE_TIMEOUT_MS) {
146
- const result = await run(['tool', 'list', '--show-paths'], timeoutMs);
129
+ export async function listUvTools(run = defaultRunUv, timeoutMs = UV_PROBE_TIMEOUT_MS, env) {
130
+ const result = await (env ? run(['tool', 'list', '--show-paths'], timeoutMs, env) : run(['tool', 'list', '--show-paths'], timeoutMs));
147
131
  if (!result.ok)
148
132
  return [];
149
133
  return parseToolList(result.stdout);
150
134
  }
151
- function findToolIn(tools, pkg) {
152
- return tools.find((entry) => entry.name === pkg);
135
+ /** PEP 503 name comparison: `Fast_Agent.ACP` and `fast-agent-acp` are the same distribution. */
136
+ function sameDistribution(a, b) {
137
+ const normal = (name) => name.toLowerCase().replace(/[-_.]+/g, '-');
138
+ return normal(a) === normal(b);
153
139
  }
154
- async function findTool(pkg, run) {
155
- return findToolIn(await listUvTools(run), pkg);
140
+ /** The tool entry for `pkg` in a {@link listUvTools} snapshot, by PEP 503 name. */
141
+ export function findUvTool(tools, pkg) {
142
+ return tools.find((entry) => sameDistribution(entry.name, pkg));
156
143
  }
157
144
  /**
158
- * The version of `pkg` that `uv tool list` reports as installed, or `undefined` when it is not
159
- * installed, or when `uv` itself is missing, fails, or times out.
160
- */
161
- export async function uvToolVersion(pkg, run = defaultRunUv) {
162
- return (await findTool(pkg, run))?.version;
163
- }
164
- /**
165
- * Picks `pkg`'s launchable executable — the one `resolveLaunch`'s uvx branch runs instead of
166
- * shelling out to `uvx <pkg>` — out of an already-parsed `uv tool list --show-paths` snapshot
167
- * ({@link listUvTools}).
168
- *
169
- * Three shapes, in order:
170
- * 1. Exactly one executable declared → that one, whatever it is named. One candidate is not a
171
- * guess.
145
+ * Picks `pkg`'s launchable executable out of an already-parsed `uv tool list --show-paths` snapshot
146
+ * ({@link listUvTools}). Three shapes, in order:
147
+ * 1. Exactly one executable declared → that one, whatever it is named.
172
148
  * 2. Several declared, and one matches the package's own name (true for `httpie` among its three,
173
149
  * measured) → that one.
174
- * 3. Several declared, none matching → **refuse** (`ambiguous`, carrying every candidate name).
175
- * uv's listing order is stable on one machine but is not a documented contract (see the module
176
- * docstring), and this path has never been exercised against a real uvx-distributed agent —
177
- * installing `fast-agent-acp`/`minion-code` to find out was ruled out of scope for this task.
178
- * Guessing the first one and launching it would trade a diagnosable refusal for an ACP handshake
179
- * that hangs or fails for a reason nobody can trace back to this choice, so this reports the
180
- * ambiguity instead of resolving it.
181
- *
182
- * `not-installed` also covers: no such tool in `uv tool list`, `uv` itself missing or failing, a
183
- * matched executable whose recorded path is empty (the plain, no-`--show-paths` form this also
184
- * parses carries no paths at all) — none of these are launchable either, and none of them are
185
- * ambiguous the way case 3 is.
186
- *
187
- * Takes the parsed listing rather than a runner on purpose: a caller that already read
188
- * `uv tool list` once (`AcpAgents.roster`, so 39 registry entries share one spawn) hands that
189
- * result straight in here instead of this function spawning `uv` again per package. {@link
190
- * uvToolEntrypoint} is the thin wrapper for the one real caller (the actual launch path) that has
191
- * no such snapshot yet and needs to read it fresh.
150
+ * 3. Several declared, none matching → **refuse** (`ambiguous`, carrying every candidate name). uv's
151
+ * listing order is not a documented contract, and a wrong guess launches the wrong binary silently.
152
+ *
153
+ * `not-installed` also covers a matched executable whose recorded path is empty (the plain,
154
+ * no-`--show-paths` form carries no paths at all).
192
155
  */
193
156
  export function uvToolEntrypointFrom(pkg, tools) {
194
- const entry = findToolIn(tools, pkg);
157
+ const entry = findUvTool(tools, pkg);
195
158
  if (!entry || entry.executables.length === 0)
196
159
  return { ok: false, reason: 'not-installed' };
197
160
  if (entry.executables.length === 1) {
198
161
  const [only] = entry.executables;
199
162
  return only.path ? { ok: true, path: only.path } : { ok: false, reason: 'not-installed' };
200
163
  }
201
- const match = entry.executables.find((exe) => exe.name === pkg);
164
+ const match = entry.executables.find((exe) => sameDistribution(exe.name, pkg));
202
165
  if (match) {
203
166
  return match.path ? { ok: true, path: match.path } : { ok: false, reason: 'not-installed' };
204
167
  }
205
168
  return { ok: false, reason: 'ambiguous', candidates: entry.executables.map((exe) => exe.name) };
206
169
  }
207
170
  /**
208
- * `uvToolEntrypointFrom`, but reading `uv tool list --show-paths` itself first — the real launch
209
- * path's entry point (`resolveLaunch`'s uvx branch with no pre-fetched listing), where doing its
210
- * own `uv` lookup at connect time is correct and expected, unlike the roster path (see {@link
211
- * uvToolEntrypointFrom}'s doc comment).
171
+ * Parses a registry `uvx.package` specifier. Both pins the live registry uses are understood —
172
+ * `fast-agent-acp==0.10.1` and `minion-code@0.1.44` (`uvx`'s `pkg@version` form). Anything else after
173
+ * the name (a range, a URL) is not a pin this layer can name a build after, and is refused.
212
174
  */
213
- export async function uvToolEntrypoint(pkg, run = defaultRunUv) {
214
- return uvToolEntrypointFrom(pkg, await listUvTools(run));
175
+ export function parseUvxSpec(spec) {
176
+ const match = /^\s*([A-Za-z0-9][A-Za-z0-9._-]*)(\[[A-Za-z0-9._,\s-]*\])?\s*(?:(?:==|@)\s*([A-Za-z0-9][A-Za-z0-9.+_-]*))?\s*$/.exec(spec);
177
+ if (!match)
178
+ throw new Error(`Unsupported uvx package specifier '${spec}'`);
179
+ return { name: match[1], extras: (match[2] ?? '').replace(/\s+/g, ''), ...(match[3] ? { version: match[3] } : {}) };
215
180
  }
216
- /**
217
- * The package name half of a `uv tool install` specifier: `pkg==1.2.3` → `pkg`. Returns the input
218
- * unchanged when it carries no `==` pin — the ACP registry is not required to pin `uvx` specifiers
219
- * the way it pins `npx` ones (see `registryTypes.ts`'s `UvxDistribution`).
220
- */
181
+ /** The package name half of a uvx specifier: `pkg==1.2.3` / `pkg@1.2.3` / `pkg[x]` → `pkg`. Unparseable input is returned unchanged. */
221
182
  export function uvPackageName(spec) {
222
- const at = spec.indexOf('==');
223
- return at > 0 ? spec.slice(0, at) : spec;
183
+ try {
184
+ return parseUvxSpec(spec).name;
185
+ }
186
+ catch {
187
+ return spec;
188
+ }
224
189
  }
225
- /** The version half of a `uv tool install` specifier: `pkg==1.2.3` → `1.2.3`; `undefined` with no pin. */
190
+ /** The version half of a uvx specifier: `pkg==1.2.3` / `pkg@1.2.3` → `1.2.3`; `undefined` with no pin. */
226
191
  export function uvPackageVersion(spec) {
227
- const at = spec.indexOf('==');
228
- return at > 0 ? spec.slice(at + 2) : undefined;
229
- }
230
- /**
231
- * `uv tool install <spec>` — the uvx counterpart to `installNpxPackage`. Short-circuits to
232
- * `alreadyInstalled: true` without spawning `uv tool install` when a quick `uv tool list` probe
233
- * (bounded by {@link UV_PROBE_TIMEOUT_MS}) already reports the pinned version — the same "don't
234
- * repeat work that's already done" rule `installNpxPackage` follows against the npx cache. The
235
- * actual install, when it runs, is a real download and gets {@link UV_TOOL_INSTALL_TIMEOUT_MS}, not
236
- * the probe's budget.
237
- */
238
- export async function installUvTool(spec, run = defaultRunUv) {
239
- const pkg = uvPackageName(spec);
240
- const pinned = uvPackageVersion(spec);
241
- if (pinned && (await uvToolVersion(pkg, run)) === pinned) {
242
- return { ok: true, alreadyInstalled: true };
192
+ try {
193
+ return parseUvxSpec(spec).version;
194
+ }
195
+ catch {
196
+ return undefined;
243
197
  }
244
- const result = await run(['tool', 'install', spec], UV_TOOL_INSTALL_TIMEOUT_MS);
245
- if (result.ok)
246
- return { ok: true, alreadyInstalled: false };
247
- return { ok: false, error: result.error ?? `uv tool install ${spec} failed` };
248
198
  }
@@ -18,10 +18,13 @@
18
18
  * question. Its two wire names behave differently here, and the difference is the suffix, not the
19
19
  * family:
20
20
  *
21
- * - `elicitation_request` (`TimelineEvent.InteractionRequested`) is request-shaped and absent from
22
- * the set, so a bridge emitting it is rejected. That stays true after Plan B minted the name:
23
- * the platform now *has* the vocabulary but still has no bridge decode branch for it, so it still
24
- * cannot answer one. Adding it here would exempt it into a generic item — a card with no button.
21
+ * - `elicitation_request` (`TimelineEvent.ElicitationRequested`) is request-shaped and, since the
22
+ * platform grew a named decode branch for it (`TimelineEventWire.decodeElicitationRequested`,
23
+ * landing a `WorkItemKind.USER_INTERACTION` item that parks the run), is now a member of this
24
+ * set: a bridge emitting it is answerable, not rejected. The reply that unblocks the agent's
25
+ * `elicitation/create` travels back on its own frame — `AgentFrame.AgentElicitationAnswer` — not
26
+ * through `permission_request`'s `agent_answer`, because ACP's `CreateElicitationResponse.content`
27
+ * is a structured object `agent_answer`'s single-string `outcome` cannot carry.
25
28
  * - `elicitation` (`TimelineEvent.UserInteractionRequested`) carries no request suffix, so this gate
26
29
  * never looks at it and a bridge emitting it passes through. That is now correct rather than
27
30
  * merely harmless: `TimelineEventWire.decode` has a named `elicitation` branch that rebuilds the
@@ -32,7 +35,7 @@
32
35
  * run never parks on. Well-formedness is defined in exactly one place, that decoder; this gate
33
36
  * deliberately does not carry a second copy of the shape rules to drift from it.
34
37
  */
35
- export const KNOWN_REQUEST_EVENT_NAMES = new Set(['permission_request']);
38
+ export const KNOWN_REQUEST_EVENT_NAMES = new Set(['permission_request', 'elicitation_request']);
36
39
  /**
37
40
  * Does this name carry the "answer me exactly once" promise?
38
41
  *
@@ -130,5 +133,30 @@ function inspect(event) {
130
133
  }
131
134
  }
132
135
  }
136
+ // `elicitation_request`'s counterpart to the `permission_request` checks above — added by Task 18,
137
+ // which is also this event's first real producer (`permission.ts`'s `handleFormElicitation`).
138
+ // `validate.test.ts` used to pin the opposite — `{requestId: 'r1'}` alone passing — on the explicit
139
+ // reasoning that a shape rule with no producer to catch would only be guarding against a caller
140
+ // that did not exist; that premise no longer holds, so the rule now exists too. `mode`/`message` are
141
+ // the two fields `elicitationRequestFromPayload` (`ElicitationRequest.kt`) requires no matter which
142
+ // mode arrives — a payload missing either is dropped silently server-side (`runCatching { ... }
143
+ // .getOrNull()`), the same "advisory here, silent there" gap the `permission_request` checks above
144
+ // exist to close.
145
+ if (event.eventName === 'elicitation_request') {
146
+ if (!nonEmptyString(payload.requestId)) {
147
+ return { reason: 'invalid elicitation_request', detail: 'requestId must be a non-empty string' };
148
+ }
149
+ const request = payload.request;
150
+ if (request === null || typeof request !== 'object' || Array.isArray(request)) {
151
+ return { reason: 'invalid elicitation_request', detail: 'request must be an object' };
152
+ }
153
+ const requestPayload = request;
154
+ if (!nonEmptyString(requestPayload.mode)) {
155
+ return { reason: 'invalid elicitation_request', detail: 'request.mode must be a non-empty string' };
156
+ }
157
+ if (!nonEmptyString(requestPayload.message)) {
158
+ return { reason: 'invalid elicitation_request', detail: 'request.message must be a non-empty string' };
159
+ }
160
+ }
133
161
  return null;
134
162
  }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The agent channel's global socket gate (Task 20, spec 2026-10-11 §5.6; `desktop/bridge.ts` is the precedent).
3
+ *
4
+ * Session frames are sent only while the encrypted socket's `bufferedAmount` is below {@link SOCKET_HIGH_WATER_BYTES}; once it
5
+ * reaches that, they wait until it has drained to {@link SOCKET_LOW_WATER_BYTES}. The channel's heartbeat is written straight to
6
+ * the same socket by the channel itself, so what this bounds is how much it can find queued ahead of it: about 1 MiB plus one
7
+ * frame instead of everything a talkative agent produced — on a slow link, seconds instead of minutes, and a heartbeat that misses
8
+ * its ack closes the channel.
9
+ *
10
+ * What goes through the gate, and what does not (B §4.3/§5):
11
+ *
12
+ * - **session frames** (`agent_event`, `agent_turn_done`: numbered, recorded in the outbox, acknowledged by the server) wait for
13
+ * the gate **and** their session's window (`acp/outbox.ts`). `agent_turn_done` is one of them, not a control frame: the server
14
+ * moves `lastSeenSeq` on it, so letting it overtake lower-seq events would make the server drop them as duplicates;
15
+ * - **subagent relations** (`agent_event{eventName:'subagent_spawned'}`, the server's `SubagentSpawned`) that resolve a child
16
+ * frame recorded before them bypass the session window — with the frames before them, in seq order — but not the gate: the
17
+ * server caps its acks below the first child frame still waiting for its relation (`ExternalAgentEventApplier.noteApplied`,
18
+ * `nativePendingBefore - 1`), so a window that held the relation back would wait for an ack that cannot come (the server
19
+ * poisons after 30 s and every reopen repeats it). Mechanism and the producing side: `acp/outbox.ts`;
20
+ * - **bulk unnumbered** frames (`agent_backfill_events`) wait for the gate only — they are never acknowledged, so no session
21
+ * window could ever open for them;
22
+ * - **control frames** bypass both and are written at once: RPC replies (`agent_opened`, `agent_steered`,
23
+ * `agent_config_option_set`, `agent_native_sessions`, `agent_native_deleted`, `agent_verified`, `agent_authenticated`,
24
+ * `agent_installed`, `agent_delegation_renewed`, `agent_provider_task_stopped`, `agent_backfill_done`), levels
25
+ * (`agent_session_state`, `agent_runtime_profile`, `agent_provider_task_event`), `agent_session_lost`, `agent_resume_offer`,
26
+ * `agent_auth_url`/`agent_auth_done`, and the channel's own heartbeat. They are small and rare, an answer the server is waiting
27
+ * for (a 10–120 s timeout) must not queue behind a session's backlog, and an answer to a user (a permission card's state) must
28
+ * not wait for the window that answer is about to open. They still count against the gate: they add to `bufferedAmount`, which
29
+ * session frames then wait on.
30
+ */
31
+ /** Session frames stop once the socket holds this much unsent (spec §5.6). */
32
+ export const SOCKET_HIGH_WATER_BYTES = 1024 * 1024;
33
+ /** …and resume once it has drained to this (spec §5.6). */
34
+ export const SOCKET_LOW_WATER_BYTES = 256 * 1024;
35
+ /** How often a closed gate looks at the socket again. */
36
+ export const SOCKET_POLL_MS = 25;
37
+ export class SocketGate {
38
+ bufferedAmount;
39
+ closed = false;
40
+ timer;
41
+ waiters = new Set();
42
+ /** `bufferedAmount` of the bound socket; `undefined`: no socket to measure (the gate stays open). */
43
+ constructor(bufferedAmount = undefined) {
44
+ this.bufferedAmount = bufferedAmount;
45
+ }
46
+ /** A new connection (or none): its own buffer; whatever waited is woken to look again. */
47
+ rebind(bufferedAmount) {
48
+ this.bufferedAmount = bufferedAmount;
49
+ this.closed = false;
50
+ this.wake();
51
+ }
52
+ /** May a session frame be written now? Closes at the high water mark, reopens at the low one. */
53
+ isOpen() {
54
+ const buffered = this.bufferedAmount?.() ?? 0;
55
+ if (this.closed) {
56
+ if (buffered > SOCKET_LOW_WATER_BYTES)
57
+ return false;
58
+ this.closed = false;
59
+ return true;
60
+ }
61
+ if (buffered >= SOCKET_HIGH_WATER_BYTES) {
62
+ this.closed = true;
63
+ return false;
64
+ }
65
+ return true;
66
+ }
67
+ /** Calls `then` once the gate is open again (polling the socket while it is closed). */
68
+ whenOpen(then) {
69
+ this.waiters.add(then);
70
+ this.poll();
71
+ }
72
+ /** Resolves when the gate is open. */
73
+ opened() {
74
+ if (this.isOpen())
75
+ return Promise.resolve();
76
+ return new Promise((resolve) => this.whenOpen(resolve));
77
+ }
78
+ poll() {
79
+ if (this.timer || this.waiters.size === 0)
80
+ return;
81
+ this.timer = setTimeout(() => {
82
+ this.timer = undefined;
83
+ if (this.isOpen())
84
+ this.wake();
85
+ else
86
+ this.poll();
87
+ }, SOCKET_POLL_MS);
88
+ this.timer.unref?.();
89
+ }
90
+ wake() {
91
+ const waiters = [...this.waiters];
92
+ this.waiters.clear();
93
+ for (const waiter of waiters)
94
+ waiter();
95
+ }
96
+ /** Stops polling (daemon shutdown). Waiters are dropped. */
97
+ dispose() {
98
+ if (this.timer)
99
+ clearTimeout(this.timer);
100
+ this.timer = undefined;
101
+ this.waiters.clear();
102
+ }
103
+ }