@platless/axom-cli 0.3.13 → 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 (235) hide show
  1. package/dist/axom-patch/cli/README.md +30 -8
  2. package/dist/axom-patch/cli/account-limits/README.md +27 -0
  3. package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
  4. package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
  5. package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
  6. package/dist/axom-patch/cli/account-limits/generate.py +120 -0
  7. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +64 -0
  8. package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
  9. package/dist/axom-patch/cli/claude-account-limits.json +37 -0
  10. package/dist/axom-patch/cli/codex-account-limits.json +37 -0
  11. package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
  12. package/dist/axom-patch/cli/nightly-task.md +152 -0
  13. package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
  14. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +5 -1
  15. package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
  16. package/dist/axom-patch/cli/probes/qoder-steering.mjs +1 -1
  17. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +20 -8
  18. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
  19. package/dist/axom-patch/cli/qoder-background-terminal.md +2 -1
  20. package/dist/axom-patch/cli/qoder-foreground-output.md +4 -3
  21. package/dist/axom-patch/cli/qoder-steering.json +15 -15
  22. package/dist/axom-patch/cli/qoder-subagent-origin.json +21 -21
  23. package/dist/axom-patch/cli/runtime.json +34 -8
  24. package/dist/axom-patch/cli/zcode-account-limits.json +31 -0
  25. package/dist/axom-patch/cli/zcode-models.json +38 -0
  26. package/dist/axom-patch/cli/zcode-subagent-subwork.json +73 -0
  27. package/dist/cli.js +33 -6
  28. package/dist/keepers/acp/keeper.mjs +3083 -0
  29. package/dist/keepers/acp/manifest.json +9 -0
  30. package/dist/keepers/terminal/keeper.mjs +8469 -0
  31. package/dist/keepers/terminal/manifest.json +9 -0
  32. package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  33. package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  34. package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
  35. package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
  36. package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
  37. package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
  38. package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
  39. package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
  40. package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
  41. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
  42. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
  43. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
  44. package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  45. package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
  46. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  47. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  48. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  49. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  50. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  51. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  52. package/dist/runtime-deps/qoder-sdk/package-lock.json +1189 -0
  53. package/dist/runtime-deps/qoder-sdk/package.json +8 -0
  54. package/dist/test/keeperKit.js +67 -0
  55. package/dist/workstation/acp/acpAgents.js +2163 -611
  56. package/dist/workstation/acp/acpClient.js +60 -497
  57. package/dist/workstation/acp/adoption.js +10 -396
  58. package/dist/workstation/acp/binaryInstall.js +100 -27
  59. package/dist/workstation/acp/builds/gc.js +245 -0
  60. package/dist/workstation/acp/builds/install/binary.js +28 -0
  61. package/dist/workstation/acp/builds/install/npm.js +235 -0
  62. package/dist/workstation/acp/builds/install/uv.js +254 -0
  63. package/dist/workstation/acp/builds/migrate.js +195 -0
  64. package/dist/workstation/acp/builds/model.js +128 -0
  65. package/dist/workstation/acp/builds/nodeGate.js +100 -0
  66. package/dist/workstation/acp/builds/store.js +352 -0
  67. package/dist/workstation/acp/commandTableProbe.js +68 -0
  68. package/dist/workstation/acp/configOptions.js +103 -1
  69. package/dist/workstation/acp/envSecrets.js +27 -0
  70. package/dist/workstation/acp/initializeResponse.js +285 -0
  71. package/dist/workstation/acp/installErrors.js +12 -2
  72. package/dist/workstation/acp/installProbe.js +1 -1
  73. package/dist/workstation/acp/managedPatches.js +76 -160
  74. package/dist/workstation/acp/outbox.js +330 -0
  75. package/dist/workstation/acp/patchAssets.js +20 -3
  76. package/dist/workstation/acp/permission.js +42 -10
  77. package/dist/workstation/acp/processor/checkpoint.js +87 -0
  78. package/dist/workstation/acp/processor/commands.js +35 -0
  79. package/dist/workstation/acp/processor/control.js +355 -0
  80. package/dist/workstation/acp/processor/emit.js +26 -0
  81. package/dist/workstation/acp/processor/jsonrpc.js +172 -0
  82. package/dist/workstation/acp/processor/memoryLog.js +49 -0
  83. package/dist/workstation/acp/processor/origins.js +48 -0
  84. package/dist/workstation/acp/processor/output.js +67 -0
  85. package/dist/workstation/acp/processor/projection.js +42 -0
  86. package/dist/workstation/acp/processor/record.js +128 -0
  87. package/dist/workstation/acp/processor/runtime.js +181 -0
  88. package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
  89. package/dist/workstation/acp/processor/session.js +525 -0
  90. package/dist/workstation/acp/processor/sideChannel.js +80 -0
  91. package/dist/workstation/acp/processor/state.js +74 -0
  92. package/dist/workstation/acp/processor/step.js +459 -0
  93. package/dist/workstation/acp/processor/test/fixtureLog.js +65 -0
  94. package/dist/workstation/acp/processor/test/harness.js +120 -0
  95. package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
  96. package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
  97. package/dist/workstation/acp/processor/transport/generations.js +76 -0
  98. package/dist/workstation/acp/processor/transport/keeper.js +130 -0
  99. package/dist/workstation/acp/processor/transport/local.js +269 -0
  100. package/dist/workstation/acp/processor/transport/types.js +78 -0
  101. package/dist/workstation/acp/processor/updates.js +364 -0
  102. package/dist/workstation/acp/processorSession.js +1512 -0
  103. package/dist/workstation/acp/qoderStartup.js +1 -1
  104. package/dist/workstation/acp/registry/catalog.js +154 -32
  105. package/dist/workstation/acp/registry/launch.js +61 -119
  106. package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
  107. package/dist/workstation/acp/steering.js +58 -0
  108. package/dist/workstation/acp/switching.js +114 -0
  109. package/dist/workstation/acp/terminalLogin.js +16 -42
  110. package/dist/workstation/acp/terminalLoginSpec.js +41 -0
  111. package/dist/workstation/acp/test/adapterFixtures.js +256 -0
  112. package/dist/workstation/acp/test/daemonLife.js +106 -0
  113. package/dist/workstation/acp/test/keeperWorld.js +267 -0
  114. package/dist/workstation/acp/test/memoryOutbox.js +55 -0
  115. package/dist/workstation/acp/test/raw/fixtures.js +196 -0
  116. package/dist/workstation/acp/test/raw/qoderCaptures.js +125 -0
  117. package/dist/workstation/acp/test/raw/sanitize.js +344 -0
  118. package/dist/workstation/acp/test/raw/sources.js +249 -0
  119. package/dist/workstation/acp/test/realAdapter.js +377 -0
  120. package/dist/workstation/acp/test/scriptAgent.js +206 -0
  121. package/dist/workstation/acp/test/sessionFrames.js +42 -0
  122. package/dist/workstation/acp/timedCommand.js +4 -2
  123. package/dist/workstation/acp/uvxInstall.js +72 -122
  124. package/dist/workstation/agentFlow.js +103 -0
  125. package/dist/workstation/agentSessions.js +1324 -474
  126. package/dist/workstation/channels/agent.js +7 -5
  127. package/dist/workstation/channels/computer.js +69 -21
  128. package/dist/workstation/channels/computerExecs.js +200 -0
  129. package/dist/workstation/channels/management.js +84 -4
  130. package/dist/workstation/daemon.js +288 -7
  131. package/dist/workstation/daemonctl.js +28 -2
  132. package/dist/workstation/exec/attach.js +126 -0
  133. package/dist/workstation/exec/command.js +40 -0
  134. package/dist/workstation/exec/durable.js +517 -0
  135. package/dist/workstation/exec/fallback.js +106 -0
  136. package/dist/workstation/exec/policy.js +837 -0
  137. package/dist/workstation/exec/shaping.js +211 -0
  138. package/dist/workstation/exec.js +3 -112
  139. package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
  140. package/dist/workstation/files/adapters/toolRequests.js +121 -0
  141. package/dist/workstation/files/client.js +455 -0
  142. package/dist/workstation/files/errors.js +28 -0
  143. package/dist/workstation/files/fence.js +350 -0
  144. package/dist/workstation/files/find/fileIndex.js +153 -0
  145. package/dist/workstation/files/find/fuzzy.js +155 -0
  146. package/dist/workstation/files/host.js +122 -0
  147. package/dist/workstation/files/hostProtocol.js +2 -0
  148. package/dist/workstation/files/media/cache.js +620 -0
  149. package/dist/workstation/files/media/encoding.js +308 -0
  150. package/dist/workstation/files/media/estimator.js +100 -0
  151. package/dist/workstation/files/media/media.js +325 -0
  152. package/dist/workstation/files/media/packedAudio.js +23 -0
  153. package/dist/workstation/files/media/scheduler.js +551 -0
  154. package/dist/workstation/files/media/types.js +5 -0
  155. package/dist/workstation/files/scope.js +45 -0
  156. package/dist/workstation/files/structured.js +284 -0
  157. package/dist/workstation/{fileOps.js → files/text.js} +117 -38
  158. package/dist/workstation/files/uploads.js +351 -0
  159. package/dist/workstation/journal.js +65 -236
  160. package/dist/workstation/keepers/acp/client.js +101 -0
  161. package/dist/workstation/keepers/acp/envelope.js +62 -0
  162. package/dist/workstation/keepers/acp/host.js +419 -0
  163. package/dist/workstation/keepers/acp/main.js +18 -0
  164. package/dist/workstation/keepers/acp/memoryStream.js +121 -0
  165. package/dist/workstation/keepers/acp/params.js +137 -0
  166. package/dist/workstation/keepers/acp/protocol.js +66 -0
  167. package/dist/workstation/keepers/acp/recover.js +120 -0
  168. package/dist/workstation/keepers/acp/session.js +584 -0
  169. package/dist/workstation/keepers/acp/stderr.js +154 -0
  170. package/dist/workstation/keepers/kit/activity.js +103 -0
  171. package/dist/workstation/keepers/kit/bootstrap.js +164 -0
  172. package/dist/workstation/keepers/kit/buildVersion.js +51 -0
  173. package/dist/workstation/keepers/kit/bundle.js +120 -0
  174. package/dist/workstation/keepers/kit/client.js +432 -0
  175. package/dist/workstation/keepers/kit/identity.js +21 -0
  176. package/dist/workstation/keepers/kit/ipc.js +112 -0
  177. package/dist/workstation/keepers/kit/launchConfig.js +8 -0
  178. package/dist/workstation/keepers/kit/launcher.js +249 -0
  179. package/dist/workstation/keepers/kit/lock.js +123 -0
  180. package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
  181. package/dist/workstation/keepers/kit/paths.js +75 -0
  182. package/dist/workstation/keepers/kit/protocol.js +43 -0
  183. package/dist/workstation/keepers/kit/server.js +518 -0
  184. package/dist/workstation/keepers/kit/streamLog.js +697 -0
  185. package/dist/workstation/keepers/legacy.js +296 -0
  186. package/dist/workstation/keepers/policy.js +576 -0
  187. package/dist/workstation/keepers/stop.js +34 -0
  188. package/dist/workstation/keepers/terminal/client.js +213 -0
  189. package/dist/workstation/keepers/terminal/exec.js +981 -0
  190. package/dist/workstation/keepers/terminal/generations.js +129 -0
  191. package/dist/workstation/keepers/terminal/keeper.js +62 -0
  192. package/dist/workstation/keepers/terminal/main.js +43 -0
  193. package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
  194. package/dist/workstation/keepers/terminal/params.js +27 -0
  195. package/dist/workstation/keepers/terminal/protocol.js +56 -0
  196. package/dist/workstation/keepers/terminal/pty.js +330 -0
  197. package/dist/workstation/protocol/accountLimits.js +47 -0
  198. package/dist/workstation/protocol/agent.js +48 -4
  199. package/dist/workstation/protocol/common.js +30 -0
  200. package/dist/workstation/protocol/computer.js +15 -4
  201. package/dist/workstation/protocol/management.js +50 -2
  202. package/dist/workstation/qoderAdapter/backgroundProcess.js +18 -6
  203. package/dist/workstation/qoderAdapter/backgroundTerminal.js +78 -147
  204. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +372 -0
  205. package/dist/workstation/qoderAdapter/callbackContext.js +76 -0
  206. package/dist/workstation/qoderAdapter/foregroundOutput.js +15 -9
  207. package/dist/workstation/qoderAdapter/history.js +1 -3
  208. package/dist/workstation/qoderAdapter/main.js +4 -2
  209. package/dist/workstation/qoderAdapter/sdkDriver.js +1 -2
  210. package/dist/workstation/qoderAdapter/sdkRuntime.js +93 -0
  211. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +145 -0
  212. package/dist/workstation/qoderAdapter/session.js +1 -1
  213. package/dist/workstation/qoderAdapter/subwork.js +15 -7
  214. package/dist/workstation/systemMetrics.js +110 -1
  215. package/dist/workstation/tools/client.js +9 -1
  216. package/dist/workstation/tools/info.js +50 -0
  217. package/dist/workstation/tools/keeperProcess.js +15 -5
  218. package/dist/workstation/tools/router.js +55 -0
  219. package/dist/workstation/tools/terminalRouter.js +222 -0
  220. package/dist/workstation/tools/validation.js +37 -1
  221. package/dist/workstation/update/coordinator.js +3 -0
  222. package/dist/workstation/update/ptyPreflight.js +21 -0
  223. package/dist/workstation/update/taskRegistry.js +105 -11
  224. package/package.json +10 -7
  225. package/dist/workstation/acp/acpSession.js +0 -1475
  226. package/dist/workstation/acp/contract.js +0 -499
  227. package/dist/workstation/acp/draftInterceptor.js +0 -163
  228. package/dist/workstation/acp/sessionScope.js +0 -19
  229. package/dist/workstation/filePaths.js +0 -174
  230. package/dist/workstation/fileWorker.js +0 -81
  231. package/dist/workstation/fileWorkerClient.js +0 -121
  232. package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
  233. package/dist/workstation/tools/core.js +0 -50
  234. package/dist/workstation/tools/files.js +0 -525
  235. package/dist/workstation/tools/terminal.js +0 -214
@@ -35,7 +35,7 @@ export async function prepareQoderStartup(launch, lease, cwd, env, agentEnv) {
35
35
  '--setting-sources', '', '--settings', '{"autoMemoryEnabled":false}',
36
36
  '--disallowed-tools', 'Agent', '--cwd', cwd,
37
37
  '--append-system-prompt', 'Use the axom-delegation MCP tools for delegation. Native Agent execution is disabled in this session.'] },
38
- secrets: [authorization, authorization.slice('Bearer '.length)], dispose,
38
+ secrets: [authorization, authorization.slice('Bearer '.length)], dir, dispose,
39
39
  };
40
40
  }
41
41
  catch (error) {
@@ -6,7 +6,7 @@
6
6
  * 一个 agent 在本清单里,就是我们真机验证过(roster 的 `curated`),设置页把它放进展开区;其余官方
7
7
  * 条目进折叠的 "Unverified Agents"。本清单能做的事,各有一节在下面:
8
8
  *
9
- * - **推荐**:`recommended` 是静态的,只给愿意向任何一台新机器推荐的(claude、codex、pi)。
9
+ * - **推荐**:`recommended` 是静态的,只给愿意向任何一台新机器推荐的(pi)。
10
10
  * - **本地检测**(`detect`,见 `detect.ts`):PATH 上的命令或固定文件位置,只看文件在不在、不起进程。
11
11
  * 检测到 = 这台机器的用户自己在用它,于是它也进推荐位,原因写"本机在用"。
12
12
  * - **前置依赖**(`prerequisite`):适配器之外、它起得来所必需的本体(Pi 的 CLI、ZCode 桌面应用),
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * ## 托管与检测是两件事
17
17
  *
18
- * Axom 启动的**一律是 Axom 托管的那份**——npx 缓存、`uv tool dir`、`<config dir>/acp-adapters` 里我们
18
+ * Axom 启动的**一律是 Axom 托管的那份**——`<config dir>/acp-adapters` 里我们(npm、uv、binary 各按版本)
19
19
  * 自己装的、钉了版本的副本,与用户自己的安装隔离、独立升级。`adapterInstalled` 只回答这一份在不在。
20
20
  * 用户 PATH 上的 `kimi`、`command-code` **不算** Axom 已安装,也从不被拿来启动:它只是 `detect` 的
21
21
  * 推荐信号。两份共用数据目录(`~/.kimi-code`、`~/.commandcode`),所以登录是共享的。
@@ -44,7 +44,7 @@
44
44
  *
45
45
  * 这里写下的来源**整个替换** registry 的 `version` 与 `distribution`,id 与展示名仍是 registry 的,
46
46
  * 于是已有的配置条目、roster、安装、启动、更新都不用知道发生过替换(见 {@link withCatalogOverrides})。
47
- * 本文件里的是长期的(Qoder:registry 落后于验证过的原生版本);临时的钉版本放在
47
+ * 本文件的 distribution 用于长期策略;Qoder 等临时的钉版本放在
48
48
  * axom-patch/cli/runtime.json,按 registry 版本挑选(`when_registry_version` / `until_registry_version`,
49
49
  * 见 `patchAssets.ts`),registry 一到就自己让位。两者与上面的 `install` 是同一条安全边界:只能是
50
50
  * 随 CLI 发布、钉死版本、由真机验证过的可信配置。
@@ -85,19 +85,47 @@ export const AXOM_AGENT_CATALOG = [
85
85
  registryAgent: 'claude-acp',
86
86
  defaultName: 'claude-agent',
87
87
  defaultLabel: 'Claude Agent',
88
- recommended: true,
89
88
  detect: { commands: ['claude'] },
89
+ // 钉版本的唯一理由是补丁:claude-account-limits 的配方只核验过 0.90.0(runtime.json 的
90
+ // package_patches)。不钉的话 registry 一发新版,Install/Update 就装上一个配方不覆盖的包,
91
+ // 账号配额扩展静默消失——2026-10-14 的 antigravity(registry 已到 1.4.0、native extension
92
+ // 只认 1.3.0)就是这个缺口的实例。钉住后 scan 的 Update 比对(installedVersion vs 本清单的
93
+ // version)只在落后于钉住版本时才成立,上游新版要等 nightly 核验移植配方后再 bump。
94
+ // 周期核验步骤见 axom-patch/cli/nightly-task.md。
95
+ distribution: { version: '0.90.0', npx: { package: '@agentclientprotocol/claude-agent-acp@0.90.0' } },
96
+ launchEnv: {
97
+ // Axom 拉起的 Claude Code——会话进程、配额读的 /usage、auth status 探针、terminalLogin 代跑的
98
+ // `--cli auth login`——一律使用独立的 OAuth 凭据槽,与用户终端 `claude login` 写进默认槽
99
+ // (macOS keychain `Claude Code-credentials`)的那份互不轮换。Claude 的 refresh token 单次
100
+ // 轮换,多进程共享一份凭据时并发刷新会互相作废、进而被 Claude Code 清除(全员登出;
101
+ // anthropics/claude-code#54443,2026-10 本机实测每日复现)。
102
+ //
103
+ // CLAUDE_SECURESTORAGE_CONFIG_DIR 只改槽位命名,不把存储切到目录:macOS 上 keychain 条目变为
104
+ // `Claude Code-credentials-<sha256(路径)前8位>`,明文回退文件在 <路径>/.credentials.json(仅当
105
+ // keychain 写入失败时才落)。前导 `~` 由 `registry/launch.ts` 在 launch env 边界展开为绝对路径——
106
+ // Claude 把这个值按字面路径使用,不展开 `~`。
107
+ //
108
+ // **共享/独立凭据的开关收敛在这一个键上**:删除它(或将来的用户开关通过 workstation 配置的
109
+ // 同名变量覆盖)即回到与终端 CLI 共享默认槽的旧行为,判定点只此一处。路径一旦发布永不改名——
110
+ // 换字符串等于强制所有人重新登录。
111
+ CLAUDE_SECURESTORAGE_CONFIG_DIR: '~/.axom/auth/secures/claude',
112
+ },
113
+ },
114
+ {
115
+ registryAgent: 'codex-acp',
116
+ defaultName: 'codex',
117
+ defaultLabel: 'Codex',
118
+ detect: { commands: ['codex'] },
119
+ // 与 claude-acp 同一条规则:codex-account-limits 只核验过 2.2.2,钉住直到配方随新版本移植。
120
+ distribution: { version: '2.2.2', npx: { package: '@agentclientprotocol/codex-acp@2.2.2' } },
90
121
  },
91
- { registryAgent: 'codex-acp', defaultName: 'codex', defaultLabel: 'Codex', recommended: true, detect: { commands: ['codex'] } },
92
122
  {
93
123
  registryAgent: 'qoder',
94
124
  defaultName: 'qoder',
95
125
  defaultLabel: 'Qoder',
96
126
  detect: { commands: ['qodercli'] },
97
- // The registry still launches 0.2.14, which only reports legacy `models`. The tested native
98
- // release reports ACP configOptions for model/mode and accepts session/set_config_option.
99
- // Keep registry installs on the same verified runtime as the native production baseline.
100
- distribution: { version: '1.1.61', npx: { package: '@qoder-ai/qodercli@1.1.61', args: ['--acp'] } },
127
+ // The temporary registry catch-up pin lives in axom-patch/cli/runtime.json so a newer
128
+ // upstream release can take over without another Axom release.
101
129
  },
102
130
  // `gemini` was here and is deliberately gone. `@google/gemini-cli --acp` now refuses `session/new`
103
131
  // outright ("This client is no longer supported for Gemini Code Assist for individuals … migrate
@@ -145,19 +173,21 @@ export const AXOM_AGENT_CATALOG = [
145
173
  registryAgent: 'command-code',
146
174
  defaultName: 'command-code',
147
175
  defaultLabel: 'Command Code',
148
- // registry 没有它。1.66.0 还没有 `acp` 子命令,1.74.3 起有、真机试跑通过。包里有 `cmd`、`cmdc`、
176
+ // registry 没有它。1.66.0 还没有 `acp` 子命令,1.74.3 起有、真机试跑通过;2026-10-14 nightly
177
+ // 升到 1.82.1:`acp` 子命令仍在(四个 bin 不变),commandcode-account-limits 配方已随
178
+ // generate.py 移植(handler map 换了名字 u/dn→m/hn,锚点已跟)。包里有 `cmd`、`cmdc`、
149
179
  // `commandcode`、`command-code` 四个 bin,npx 挑与包名同名的那个。没有 ACP 登录方法,只有一句
150
180
  // 说明(终端里跑 `cmd login`);凭据在 `~/.commandcode`,与用户自己装的那份共用。npm 上的许可证
151
181
  // 写的是 UNLICENSED(专有)。
152
182
  supplement: {
153
183
  name: 'Command Code',
154
- version: '1.74.3',
184
+ version: '1.82.1',
155
185
  description: 'Command Code, coding agent that continuously learns your coding taste.',
156
186
  website: 'https://commandcode.ai',
157
187
  authors: ['Command Code'],
158
188
  license: 'UNLICENSED',
159
189
  icon: 'https://commandcode.ai/favicon.ico',
160
- distribution: { npx: { package: 'command-code@1.74.3', args: ['acp'] } },
190
+ distribution: { npx: { package: 'command-code@1.82.1', args: ['acp'] } },
161
191
  },
162
192
  detect: { commands: ['command-code', 'commandcode'] },
163
193
  },
@@ -165,21 +195,27 @@ export const AXOM_AGENT_CATALOG = [
165
195
  registryAgent: 'zcode-acp',
166
196
  defaultName: 'zcode',
167
197
  defaultLabel: 'ZCode',
168
- unverified: 'zcode-acp 0.63/0.64 与 ZCode 桌面 3.14.x 不兼容:3.14 不写 ~/.zcode/v2/config.json,模型只剩无授权的 ' +
169
- 'builtin:bigmodel-coding-plan\\GLM-5.3,首轮 CONFIGURATION_ERROR "Model creation failed"(2026-10-06 两台机器复现)。',
170
- // 社区适配器(registry#566、#650 都还没合),驱动的是 ZCode 桌面应用自带的 `zcode app-server`,
198
+ // 社区适配器(registry#566、#650 都还没合),驱动的是 ZCode 桌面应用自带的 `zcode app-server`,
171
199
  // 所以 ZCode 本体是前置依赖,而且没有一条我们能替用户跑的安装命令 —— 只能告诉他去装。
172
- // 凭据在 `~/.zcode/v2/config.json`,由 ZCode 自己登录写下。Linux 上本体没有固定路径,只认 PATH。
200
+ // 凭据:legacy `~/.zcode/v2/config.json`,但 ZCode 桌面 3.14.x 起不再写它;适配器 0.68.1+(上游
201
+ // #320)在 config.json 缺席时改读加密凭据库 `~/.zcode/v2/credentials.json`(桌面登录时写下)取
202
+ // individual coding-plan 的 api-key。Linux 上本体没有固定路径,只认 PATH。
203
+ // 2026-10-13 真机验证(ZCode 桌面 3.14.5 + 0.69.3):initialize → session/new → 首轮 prompt 通过,
204
+ // 日志 "serving the coding-plan API key (config.json or store)"。0.63/0.64 那轮不兼容(模型只剩
205
+ // 无授权的 builtin:bigmodel-coding-plan\GLM-5.3,首轮 CONFIGURATION_ERROR "Model creation failed",
206
+ // 2026-10-06 两台机器复现)自 0.68.1 起修复。2026-10-14 nightly 升到 0.69.4:三份配方的目标文件
207
+ // 与 0.69.3 字节一致(配方 versions 同时覆盖两版),subagent 探针在 0.69.4 补丁副本上通过、
208
+ // pristine 证实上游仍未原生发出 subagent/async 变体。
173
209
  supplement: {
174
210
  name: 'ZCode',
175
- version: '0.63.0',
211
+ version: '0.69.4',
176
212
  description: 'Community bridge that drives the real ZCode app-server (GLM) over ACP.',
177
213
  repository: 'https://github.com/william0wang/zcode-acp',
178
214
  website: 'https://github.com/william0wang/zcode-acp',
179
215
  authors: ['William Wang'],
180
216
  license: 'Apache-2.0',
181
217
  icon: 'https://raw.githubusercontent.com/william0wang/zcode-acp/main/registry/zcode-acp/icon.svg',
182
- distribution: { npx: { package: 'zcode-acp-server@0.63.0' } },
218
+ distribution: { npx: { package: 'zcode-acp-server@0.69.4' } },
183
219
  },
184
220
  prerequisite: {
185
221
  label: 'ZCode',
@@ -200,6 +236,10 @@ export const AXOM_AGENT_CATALOG = [
200
236
  defaultName: 'pi',
201
237
  defaultLabel: 'Pi',
202
238
  recommended: true,
239
+ // pi-steering 与 pi-subagent-subwork 两份配方只核验过 0.0.34。registry 今天也在 0.0.34,
240
+ // 但那是巧合而不是保护:上游一发 0.0.35,未钉版本的 Install 就会装上一个 steering/子代理
241
+ // 归一都不生效的包。钉住后版本只随配方移植走(nightly-task.md)。
242
+ distribution: { version: '0.0.34', npx: { package: 'pi-acp@0.0.34' } },
203
243
  prerequisite: {
204
244
  label: 'Pi',
205
245
  detect: { commands: ['pi'] },
@@ -212,8 +252,62 @@ export const AXOM_AGENT_CATALOG = [
212
252
  requiresNode: '>=22.19.0',
213
253
  },
214
254
  },
255
+ {
256
+ // 推荐位第二名(pi 之后的第一个):真机验证记录见 2026-10-13 genius 站验证(scan/verify/会话/审批
257
+ // 全链路)。hermes 不在公开 ACP registry(41 个 agent 里没有),本条同时是 supplement —— registry
258
+ // 一旦收录这个 id 就整体让位。PyPI 上的 `hermes-agent`(0.19.0,requires_python <3.14)是过时的
259
+ // 次要通道,官方分发是 `curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash`(git
260
+ // clone + pm bootstrap,写 ~/.local/bin/{hermes,hermes-acp}),所以走 pathLaunch 而不是 uvx:
261
+ // 启动用户自己装的那份,本机只有一份,与 pi 的本体同款哲学。`hermes-acp` 是官方 wheel/git 都
262
+ // 声明的 ACP console script;不要用 `hermes-agent`(那是 demo runner)或裸 `hermes`(CLI 主入口,
263
+ // acp 只是它的子命令 —— 两者都不带 ACP 语义的入口保障)。
264
+ registryAgent: 'hermes-acp',
265
+ defaultName: 'hermes',
266
+ defaultLabel: 'Hermes',
267
+ recommended: true,
268
+ detect: {
269
+ commands: ['hermes'],
270
+ locations: everyPlatform(['~/.local/bin/hermes-acp'], ['~/.local/bin/hermes-acp.exe']),
271
+ },
272
+ pathLaunch: { command: 'hermes-acp', hint: 'Install Hermes Agent with the official installer (curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash), then scan again.' },
273
+ supplement: {
274
+ name: 'Hermes',
275
+ version: '0.21.7',
276
+ description: "Nous Research's self-improving coding agent — native ACP server (hermes acp).",
277
+ repository: 'https://github.com/NousResearch/hermes-agent',
278
+ website: 'https://hermes-agent.nousresearch.com',
279
+ authors: ['Nous Research'],
280
+ license: 'Apache-2.0',
281
+ icon: 'https://hermes-agent.nousresearch.com/favicon.ico',
282
+ // supplement 必须给一条 distribution 才能在 registry 类型里站得住;hermes 没有任何可安装产物,
283
+ // 留空 distribution 由 pathLaunch 接管 available(见 acpAgents.ts 的 roster)。
284
+ distribution: {},
285
+ },
286
+ },
215
287
  // registry 自己的条目(binary .par),我们只补名字与本机检测;`agy` 是 Antigravity 装好后放进 PATH 的命令。
216
- { registryAgent: 'antigravity-acp', defaultName: 'google-antigravity', defaultLabel: 'Google Antigravity', detect: { commands: ['agy'] } },
288
+ // distribution 钉住:antigravity 的账号配额靠 native extension(runtime.json
289
+ // native_extensions,account-limits/antigravity-proxy.mjs),proxy 是 stdio sidecar,只在核验过的
290
+ // 版本上声明能力。2026-10-14 nightly 升到 1.4.0:真实 1.4.0 .par 经 proxy 拉起,initialize 透传无损、
291
+ // `axom/accountRateLimits` 能力注入成功(本地实测;配额 READ 属于用户凭据路径,留给真机冒烟)。
292
+ // 归档 URL 抄自 registry 的 1.4.0 条目;registry 本身不给这个条目发 sha256,这里同样不发,校验语义与
293
+ // registry 直装一致(binaryInstall:无 sha256 即不校验)。上游发新版时按 nightly-task.md 核验后同升。
294
+ {
295
+ registryAgent: 'antigravity-acp',
296
+ defaultName: 'google-antigravity',
297
+ defaultLabel: 'Google Antigravity',
298
+ detect: { commands: ['agy'] },
299
+ distribution: {
300
+ version: '1.4.0',
301
+ binary: {
302
+ 'darwin-aarch64': { archive: 'https://dl.google.com/agy-extensions/releases/macos/agy-acp-server-1.4.0-darwin-arm64.zip', cmd: './agy_acp_server.par' },
303
+ 'darwin-x86_64': { archive: 'https://dl.google.com/agy-extensions/releases/macos/agy-acp-server-1.4.0-darwin-x86_64.zip', cmd: './agy_acp_server.par' },
304
+ 'linux-x86_64': { archive: 'https://dl.google.com/agy-extensions/releases/linux/agy-acp-server-1.4.0-linux-x86_64.zip', cmd: './agy_acp_server.par', args: ['--uid='] },
305
+ 'linux-aarch64': { archive: 'https://dl.google.com/agy-extensions/releases/linux/agy-acp-server-1.4.0-linux-arm64.zip', cmd: './agy_acp_server.par', args: ['--uid='] },
306
+ 'windows-x86_64': { archive: 'https://dl.google.com/agy-extensions/releases/windows/agy-acp-server-1.4.0-windows-x86_64.zip', cmd: './agy_acp_server.exe' },
307
+ 'windows-aarch64': { archive: 'https://dl.google.com/agy-extensions/releases/windows/agy-acp-server-1.4.0-windows-arm64.zip', cmd: './agy_acp_server.exe' },
308
+ },
309
+ },
310
+ },
217
311
  ];
218
312
  /**
219
313
  * Kimi Code 的六个平台归档:GitHub release `@moonshot-ai/kimi-code@<version>` 下的
@@ -236,30 +330,58 @@ function everyPlatform(unix, windows) {
236
330
  };
237
331
  }
238
332
  const BY_ID = new Map(AXOM_AGENT_CATALOG.map((entry) => [entry.registryAgent, entry]));
333
+ /**
334
+ * 静态推荐条目的展示序(目录数组序):推荐层的排序依据,用户可指定(2026-10-13:pi 第一、hermes
335
+ * 第二)。名册序做不到这件事 —— supplement 追加在 registry 之后,字母序又与本意相反(H < P),
336
+ * 所以显式带一个序号,roster 经 `recommendedRank` 下发、推荐层按它排。非静态推荐的 detected 卡
337
+ * ("本机在用")没有序号,排在静态推荐之后、按 label 字母序。
338
+ */
339
+ export const RECOMMENDED_RANK = new Map(AXOM_AGENT_CATALOG
340
+ .map((entry, index) => [entry.registryAgent, index])
341
+ .filter(([id]) => catalogEntryFor(id)?.recommended === true)
342
+ .map(([id], rank) => [id, rank]));
239
343
  export function catalogEntryFor(registryAgent) {
240
344
  return BY_ID.get(registryAgent);
241
345
  }
242
- /**
243
- * The registry entry as Axom will actually install and launch it: unchanged unless something
244
- * overrides it. A distribution replaces the registry's `version` and `distribution` — a runtime.json
245
- * override that applies at this registry version first, else this catalog's own. Launch env is merged
246
- * under every distribution's own `env` (the registry's declared variables win over ours). Identity
247
- * and the registry's own words never move (see the file header).
248
- */
249
- export function applyCatalogOverrides(agent) {
346
+ /** The CLI-owned launch environment for one agent at one registry version: catalog `launchEnv`, then runtime.json `env`. */
347
+ export function catalogLaunchEnv(agentId, registryVersion) {
348
+ return { ...catalogEntryFor(agentId)?.launchEnv, ...patchOverridesFor(agentId, registryVersion).env };
349
+ }
350
+ export function effectiveEntry(agent, supplemented = false) {
250
351
  const entry = catalogEntryFor(agent.id);
251
352
  const patch = patchOverridesFor(agent.id, agent.version);
252
353
  const pinned = patch.distribution ?? entry?.distribution;
253
- const launchEnv = { ...entry?.launchEnv, ...patch.env };
354
+ const pinnedBy = patch.distribution ? 'runtime-override'
355
+ : entry?.distribution || supplemented ? 'catalog' : undefined;
254
356
  let effective = agent;
255
357
  if (pinned) {
256
358
  const { version, ...distribution } = pinned;
257
359
  effective = { ...effective, version, distribution };
258
360
  }
259
- if (Object.keys(launchEnv).length) {
260
- effective = { ...effective, distribution: withLaunchEnv(effective.distribution, launchEnv) };
261
- }
262
- return effective;
361
+ return {
362
+ agent: effective,
363
+ registryVersion: agent.version,
364
+ launchEnv: { ...entry?.launchEnv, ...patch.env },
365
+ ...(pinnedBy ? { pinnedBy } : {}),
366
+ };
367
+ }
368
+ /** {@link withCatalogEntries} then {@link effectiveEntry} over a whole registry document, supplements marked as catalog pins. */
369
+ export function effectiveEntries(agents) {
370
+ const published = new Set(agents.map((agent) => agent.id));
371
+ return withCatalogEntries(agents).map((agent) => effectiveEntry(agent, !published.has(agent.id)));
372
+ }
373
+ /**
374
+ * The registry entry as Axom will actually install and launch it: unchanged unless something
375
+ * overrides it. A distribution replaces the registry's `version` and `distribution` — a runtime.json
376
+ * override that applies at this registry version first, else this catalog's own. Launch env is merged
377
+ * under every distribution's own `env` (the registry's declared variables win over ours). Identity
378
+ * and the registry's own words never move (see the file header).
379
+ */
380
+ export function applyCatalogOverrides(agent) {
381
+ const { agent: effective, launchEnv } = effectiveEntry(agent);
382
+ if (!Object.keys(launchEnv).length)
383
+ return effective;
384
+ return { ...effective, distribution: withLaunchEnv(effective.distribution, launchEnv) };
263
385
  }
264
386
  /** The registry's list with this catalog's supplements added where the registry lacks them and superseded ids removed. */
265
387
  export function withCatalogEntries(agents) {
@@ -1,128 +1,70 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
1
3
  import path from 'node:path';
2
- import { binaryTargetFor, installedBinaryVersion, platformTriple } from '../binaryInstall.js';
3
- import { npmPackageName } from '../installProbe.js';
4
- import { uvPackageName, uvToolEntrypoint, uvToolEntrypointFrom } from '../uvxInstall.js';
5
- export class UnsupportedDistributionError extends Error {
6
- }
7
- /** `npx --yes <specifier> [dist args]`,两个调用方共用的一处构造:钉版本的和不钉版本的。 */
8
- function npxLaunch(specifier, npx, platform) {
9
- return {
10
- command: platform === 'win32' ? 'npx.cmd' : 'npx',
11
- // `--yes` 让首次拉包不停在交互确认上。
12
- args: ['--yes', specifier, ...(npx.args ?? [])],
13
- env: { ...(npx.env ?? {}) },
14
- };
15
- }
4
+ import { fileURLToPath } from 'node:url';
5
+ import { nativeExtensionFor } from '../patchAssets.js';
6
+ import { verifyPatchedFiles } from '../managedPatches.js';
7
+ import { AdapterNotInstalledError, inside, labelFor } from '../builds/model.js';
8
+ import { assertBuildRunsOn } from '../builds/nodeGate.js';
16
9
  /**
17
- * registry 只给原料,不给命令行 —— 没有 `command` 字段,启动方式要从 `distribution` 推导。
18
- *
19
- * 三种分发方式现在都支持:`npx`/`uvx` 走各自安装侧维护的缓存/工具目录(见 `installProbe.ts`、
20
- * `uvxInstall.ts`),`binary` 走 `binaryInstall.ts`(Task 8)—— 按六个平台三元组挑一个目标、
21
- * 从 `adapterRoot` 找已经装好的版本。三条路径共同的规则是:**从不猜**,找不到就抛
22
- * `UnsupportedDistributionError`,理由写清楚是"这个平台没有发行版"还是"这台机器还没装",而不是
23
- * 笼统的一句"不支持"。
10
+ * `env` with `prefix` first on its `PATH` — unless `PATH` already starts with exactly that prefix (the
11
+ * spec's own env survived the merge), so applying it twice is harmless.
24
12
  */
25
- export async function resolveLaunch(agent, platform,
26
- // 六个平台三元组里的一半;只有 binary 分支用到。
27
- arch,
28
- // `<workstation config dir>/acp-adapters` —— `binaryInstall.ts` 解包落地的根目录,只有 binary
29
- // 分支用到。npx 走 npm 自己的 `_npx` 缓存,uvx 走 `uv tool dir`,都不需要这个根目录。
30
- adapterRoot,
31
- // 仅测试注入:真实调用永远走 `uvxInstall.ts` 的默认 `uv` 实现。签名上留成可选,两个真实调用点
32
- // (`acpAgents.ts`)不用跟着改。下面的 `uvTools` 给出时,这个参数被忽略 —— 见它的说明。
33
- run,
34
- // 已经读好的 `uv tool list` 快照(`listUvTools`)。给了就直接查表,不再自己起 `uv` 子进程 ——
35
- // `AcpAgents.roster` 为整份 39 条 registry 只读一次,把结果传进这里,而不是让每个 uvx agent
36
- // 在这条路径上各自再起一次 `uv`。真正的启动路径(`resolveLaunchFor`)不传这个参数,继续用
37
- // `run`/默认实现现读一次,这里的现读对它而言正是期望行为。
38
- //
39
- // 这个函数整体是 async 的唯一原因:`uv` 现在是非阻塞调用(`RunUv` 返回 `Promise`,见
40
- // `uvxInstall.ts`),没有预取快照时这里必须 `await` 它。给了 `uvTools` 的那条路径
41
- // (`AcpAgents.resolvable`)完全同步、不等待任何东西,包成 `async` 只是让两条路径共用同一个
42
- // 签名——`Promise.resolve` 包一层同步返回值的开销,跟每次 roster 扫描本来就要付的成本比可以
43
- // 忽略不计。
44
- uvTools) {
45
- const { npx, uvx, binary } = agent.distribution;
46
- // specifier 里已内联精确版本,不会追 latest —— 除非 registry 钉的那个版本 npm 根本没有,
47
- // 见 {@link unpinnedFallbackFor}。
48
- if (npx)
49
- return npxLaunch(npx.package, npx, platform);
50
- if (uvx) {
51
- // 不走 `uvx <pkg>`:那条路用 uv 自己的临时环境缓存,目录名带哈希,"装没装"读不可靠。
52
- // 改成我们自己 `uv tool install`、启动时直接跑装出来的入口点 —— 与 binary 一样,
53
- // 落到"我们装的、我们知道装在哪"这一条统一规则上。
54
- const outcome = uvTools
55
- ? uvToolEntrypointFrom(uvPackageName(uvx.package), uvTools)
56
- : await uvToolEntrypoint(uvPackageName(uvx.package), run);
57
- if (outcome.ok) {
58
- return { command: outcome.path, args: [...(uvx.args ?? [])], env: { ...(uvx.env ?? {}) } };
59
- }
60
- if (outcome.reason === 'ambiguous') {
61
- // 装了,但不敢猜:多个可执行文件里没有一个跟包名同名,uv 的列出顺序又不是文档化契约——
62
- // 静默启动错的那个,换来的是一次没人能追回这里的 ACP 握手挂死或失败,比拒绝启动更糟。
63
- throw new UnsupportedDistributionError(`Agent '${agent.id}' is distributed through uvx and its tool is installed, but this build ` +
64
- `cannot tell which of its executables is the ACP adapter: ${outcome.candidates.join(', ')}.`);
65
- }
66
- throw new UnsupportedDistributionError(`Agent '${agent.id}' is distributed through uvx but its tool is not installed on this machine.`);
67
- }
68
- if (binary) {
69
- // 六个平台三元组里挑一个;`windows-aarch64` 在实测的 registry 快照上只有 9/18 的覆盖,
70
- // "这个平台没有发行版"是常态,不是异常——`installBinary`(`binaryInstall.ts`)在下载前就
71
- // 用同一个函数做同一个判断,这里只是启动路径上再问一次同一件事,答案必须一致。
72
- const triple = platformTriple(platform, arch);
73
- const target = triple ? binaryTargetFor(agent, triple) : undefined;
74
- if (!target) {
75
- throw new UnsupportedDistributionError(`Agent '${agent.id}' publishes no archive for ${triple ?? `${platform}-${arch}`}.`);
76
- }
77
- // 装没装是这台机器磁盘上的事实,不是 registry 条目的属性 —— 跟 npx/uvx 分支一样,没装就说
78
- // "没装",不是"不支持"。
79
- //
80
- // `agent.version` 必须传:同一个 agent 的多个版本可以并存(`<root>/<agent>/<version>/`,
81
- // 就是为此设计的),`pickInstalledVersion` 的规则是"钉的版本在就取它,否则取最大"。
82
- // `AcpAgents.roster` 报告 `adapterVersion` 时传了它,这里不传就会在两个版本并存时报告一个、
83
- // 启动另一个 —— 名册说 0.75.0、跑起来是 0.74.0,而且没有任何一处会说出这件事。
84
- const version = installedBinaryVersion(agent.id, adapterRoot, agent.version);
85
- if (!version) {
86
- throw new UnsupportedDistributionError(`Agent '${agent.id}' has no installed adapter on this machine.`);
87
- }
88
- return {
89
- command: path.join(adapterRoot, agent.id, version, target.cmd),
90
- args: [...(target.args ?? [])],
91
- env: { ...(target.env ?? {}) },
92
- };
93
- }
94
- throw new UnsupportedDistributionError(`Agent '${agent.id}' declares no launchable distribution.`);
13
+ export function withPathPrefix(env, prefix) {
14
+ if (!prefix?.length)
15
+ return env;
16
+ const head = prefix.join(path.delimiter);
17
+ const current = env.PATH ?? '';
18
+ if (current === head || current.startsWith(head + path.delimiter))
19
+ return env;
20
+ return { ...env, PATH: current ? head + path.delimiter + current : head };
95
21
  }
96
22
  /**
97
- * 同一个 agent,但把 registry 钉的版本从 specifier 上剥掉 —— 上游钉了一个 npm 上并不存在的版本时,
98
- * 唯一还能把这个 agent 救回来的启动方式。
99
- *
100
- * ## 为什么需要它
101
- *
102
- * 2026-09-04 实测:registry 的 `codex-acp` 条目声明
103
- * `@agentclientprotocol/codex-acp@1.10.0`,而 npm 上这个包的版本停在 `1.9.0`
104
- * (`dist-tags.latest` 也是 `1.9.0`)。于是每一次 codex 启动都是 `npm error code ETARGET`,npx 退出,
105
- * 子进程死掉 —— 两台真实工作站同时失去了 codex。这是上游的缺陷;这里能做的是不让它把 agent 砖掉。
106
- *
107
- * ## 代价,以及为什么仍然值得
23
+ * The spawn spec for an installed build — **no npx, no registry read, no network**: the build's own
24
+ * entry, run with a fixed absolute Node (`nodePath`, the daemon's `process.execPath`) when it is
25
+ * JavaScript, or executed directly when it is native.
108
26
  *
109
- * 不钉版本装回来的,是 npm 此刻叫 `latest` 的那一个 —— **registry 从来没有指定过它**。它可能比钉的
110
- * 版本新(常见,上游多半正是发版发歪了),也可能是一个协议行为不同、甚至跟这个客户端谈不拢
111
- * `initialize` 的版本。所以这条路只在 ETARGET 上走(见 `acpClient.ts` 的 `isMissingPackageVersion`),
112
- * 只走一次,而且两条 warn 日志会把「跑的不是 registry 要的那个版本」说穿。
113
- *
114
- * ## 三种「没有回退可言」的情况,都返回 undefined
27
+ * `launchEnv` is the running CLI's catalog/runtime.json launch environment for this agent, evaluated
28
+ * at the registry version the build was installed from. It goes underneath the build's own
29
+ * (distribution-declared) env, the same order `registry/catalog.ts`'s `withLaunchEnv` always used.
30
+ * A patched build's files are re-hashed first (the tamper check that used to run on every launch), and
31
+ * a JavaScript build must pass the Node gate (`builds/nodeGate.ts`) against `nodePath`.
32
+ */
33
+ /**
34
+ * Expand a leading `~` / `~/` in every env value against the user's home directory.
115
35
  *
116
- * 没有 npx 分发(`uvx` 走的是 uv 自己的缓存,`binary` 这个版本压根不支持)、specifier 本来就不带版本
117
- * (剥无可剥,npm 已经在追 latest 了)。剥版本用 `npmPackageName`,它按最后一个 `@` 切,所以
118
- * `@scope/name@1.10.0` → `@scope/name`,而前导的 `@` 不会被当成版本分隔符。
36
+ * Why here and nowhere else: the CLI-owned `launchEnv` (catalog / runtime.json) ships as a static
37
+ * file, so it cannot spell the per-machine absolute path some consumers need — Claude's
38
+ * `CLAUDE_SECURESTORAGE_CONFIG_DIR` is used as a literal path and never expands `~` itself. And the
39
+ * final merged env is the only point that covers both sources: a build's own distribution env
40
+ * overwrites same-key `launchEnv` values, so expanding the `launchEnv` alone would let an unexpanded
41
+ * build value win. Only a leading `~` is touched; a `~` elsewhere in a value is data, not a path.
119
42
  */
120
- export function unpinnedFallbackFor(agent, platform) {
121
- const npx = agent.distribution.npx;
122
- if (!npx)
123
- return undefined;
124
- const unpinned = npmPackageName(npx.package);
125
- if (unpinned === npx.package)
126
- return undefined;
127
- return { pinned: npx.package, unpinned, launch: npxLaunch(unpinned, npx, platform) };
43
+ function expandHomeInEnv(env) {
44
+ const home = homedir();
45
+ return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, value === '~' ? home : value.startsWith('~/') ? path.join(home, value.slice(2)) : value]));
46
+ }
47
+ export async function launchForBuild(build, root, options) {
48
+ const dir = inside(root, build.dir);
49
+ const entry = inside(dir, build.launch.entry);
50
+ if (build.kind === 'npm-patched' && build.package && build.patchedFiles) {
51
+ await verifyPatchedFiles(inside(path.join(dir, 'node_modules'), build.package), build.patchedFiles);
52
+ }
53
+ if (!existsSync(entry))
54
+ throw new AdapterNotInstalledError(build.agentId, `build ${build.buildId} is missing its executable`);
55
+ // Every build launch passes the Node gate — patched and pristine alike, whatever the args look like.
56
+ await assertBuildRunsOn(build, root, options.nodePath);
57
+ const env = expandHomeInEnv({ ...options.launchEnv, ...build.launch.env });
58
+ const pathPrefix = build.launch.pathDirs?.map((relative) => inside(dir, relative));
59
+ if (pathPrefix?.length) {
60
+ env.PATH = pathPrefix.join(path.delimiter) + path.delimiter + (env.PATH ?? options.basePath ?? process.env.PATH ?? '');
61
+ }
62
+ const extra = pathPrefix?.length ? { pathPrefix } : {};
63
+ const extension = build.launch.runtime !== 'node' ? nativeExtensionFor(build.agentId, build.version) : undefined;
64
+ if (extension) {
65
+ return { command: options.nodePath, args: [fileURLToPath(extension), entry, ...build.launch.args], env, label: labelFor(build), ...extra };
66
+ }
67
+ return build.launch.runtime === 'node'
68
+ ? { command: options.nodePath, args: [entry, ...build.launch.args], env, label: labelFor(build), ...extra }
69
+ : { command: entry, args: [...build.launch.args], env, label: labelFor(build), ...extra };
128
70
  }
@@ -1,4 +1,7 @@
1
1
  import { codexActivityRaw } from './activityReporting.js';
2
+ export function newMapperScopeData() {
3
+ return { byId: {}, lastReportedQueueDepth: 0 };
4
+ }
2
5
  /**
3
6
  * One session's memory, and the reason this file is no longer stateless: **what this session has
4
7
  * already been told**, for the two branches that cannot answer a frame from the frame alone.
@@ -32,7 +35,7 @@ import { codexActivityRaw } from './activityReporting.js';
32
35
  */
33
36
  export class SessionMapperState {
34
37
  onActivityReported;
35
- byId = new Map();
38
+ data;
36
39
  /**
37
40
  * Where an agent-reported activity-state push (today, only `codexActivityRaw`'s extraction of
38
41
  * `session_info_update._meta.codex.threadStatus`) actually goes once {@link reportActivity} is
@@ -44,8 +47,11 @@ export class SessionMapperState {
44
47
  * replay path, which has no live connection to report anything on — keeps compiling and keeps
45
48
  * doing nothing, exactly as it always has.
46
49
  */
47
- constructor(onActivityReported = () => { }) {
50
+ constructor(onActivityReported = () => { },
51
+ /** The memory itself, mutated in place — the processor passes the checkpointed copy it owns. */
52
+ data = newMapperScopeData()) {
48
53
  this.onActivityReported = onActivityReported;
54
+ this.data = data;
49
55
  }
50
56
  /**
51
57
  * The agent queue depth this session has already reported to the server.
@@ -55,7 +61,9 @@ export class SessionMapperState {
55
61
  * reader already assumes. Seeding it to `-1` (or `null`) would let the very first frame through,
56
62
  * which on `pi-acp` is `startTurn`'s, i.e. the defect this field exists to prevent.
57
63
  */
58
- lastReportedQueueDepth = 0;
64
+ get lastReportedQueueDepth() {
65
+ return this.data.lastReportedQueueDepth;
66
+ }
59
67
  /**
60
68
  * Is `depth` news? Records it if so. **This is the whole fix for a defect that shipped in the
61
69
  * previous commit**, so the reasoning is written out rather than summarised.
@@ -110,7 +118,7 @@ export class SessionMapperState {
110
118
  reportsQueueDepth(depth) {
111
119
  if (depth === this.lastReportedQueueDepth)
112
120
  return false;
113
- this.lastReportedQueueDepth = depth;
121
+ this.data.lastReportedQueueDepth = depth;
114
122
  return true;
115
123
  }
116
124
  /**
@@ -126,7 +134,10 @@ export class SessionMapperState {
126
134
  /** Remember what this call is called. Later updates that carry no `name` fall back to it. */
127
135
  record(toolCallId, name) {
128
136
  if (name)
129
- this.byId.set(toolCallId, name);
137
+ this.remember(toolCallId, name);
138
+ }
139
+ remember(toolCallId, name) {
140
+ Object.defineProperty(this.data.byId, toolCallId, { value: name, enumerable: true, writable: true, configurable: true });
130
141
  }
131
142
  /**
132
143
  * The name to report for one update, in ACP's own order of authority: an explicit `name` on the
@@ -138,10 +149,10 @@ export class SessionMapperState {
138
149
  */
139
150
  resolve(toolCallId, name, title) {
140
151
  if (name) {
141
- this.byId.set(toolCallId, name);
152
+ this.remember(toolCallId, name);
142
153
  return name;
143
154
  }
144
- return this.byId.get(toolCallId) ?? title ?? 'unknown';
155
+ return (Object.hasOwn(this.data.byId, toolCallId) ? this.data.byId[toolCallId] : undefined) ?? title ?? 'unknown';
145
156
  }
146
157
  }
147
158
  /**
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The `_session/steering` and `_session/async_task/stop` extension vocabulary of the deterministic processor
3
+ * (`processor/control.ts`; the old live session that also used it was deleted in Task 29). Pure: no I/O,
4
+ * no clock — the processor's import guard walks this file.
5
+ */
6
+ /**
7
+ * The steering extension's JSON-RPC method. **Not an ACP method** — `grep steer` over the pinned
8
+ * SDK's `dist/schema/types.gen.d.ts` returns nothing. It is implemented by `claude-agent-acp`
9
+ * (`acp-agent.js@0.76.0:164`), whose own comment calls it a *"Custom (extension) request method ...
10
+ * Named `_session/steering` per the agreed ACP steering wire protocol"*; the leading underscore is
11
+ * ACP's extension namespace. It is therefore reached through the untyped `request<Response, Params>
12
+ * (method: string, …)` overload rather than `methods.agent.*`, and every caller must check
13
+ * {@link AcpConnection.supportsSteering} first.
14
+ */
15
+ export const STEER_METHOD = '_session/steering';
16
+ /**
17
+ * The adapter's *stop one background task* extension method. **Not an ACP method** either — it is
18
+ * `claude-agent-acp`'s own `ASYNC_TASK_STOP_METHOD` (`acp-agent.js@0.79.0:165`), whose own comment
19
+ * reads *"Stops one Claude background task without cancelling the parent prompt turn."*
20
+ * `codex-acp@1.12.0` registers the same literal name with the same params
21
+ * (`dist/index.js:31608`, `:35126-35129`, `:32395-32406`). Reached through the untyped `request`
22
+ * overload, for the reason {@link STEER_METHOD} documents.
23
+ */
24
+ export const ASYNC_TASK_STOP_METHOD = '_session/async_task/stop';
25
+ /**
26
+ * Narrow a `_session/steering` response onto {@link SteerOutcome}, or throw.
27
+ *
28
+ * The extension answers with one of three outcomes (`acp-agent.js@0.76.0:1737`, `:1751`, `:1779`):
29
+ *
30
+ * - `injected` — a turn was running and the message went into it.
31
+ * - `promptRequired` (with `reason: "noRunningTurn"`) — no turn was running and we opted in, so the
32
+ * agent did nothing at all and the content stays host-owned.
33
+ * - `startedNewTurn` — no turn was running and the client did *not* opt in, so the agent detached a
34
+ * `prompt()` of its own.
35
+ *
36
+ * **`startedNewTurn` throws rather than mapping to anything** (decision D13). We always send
37
+ * `idleBehavior: "promptRequired"` ({@link AcpSession.steer}), so receiving it means the agent
38
+ * ignored the opt-in — and what it handed back is a turn nobody awaits: it never enters our queue,
39
+ * never lands an item, and never produces a `turn_done`, in a system that accounts by turn.
40
+ * Absorbing it as "injected" would mark a message delivered to a turn we are not tracking;
41
+ * absorbing it as "promptRequired" would submit the same message a second time. Neither is a
42
+ * recovery, so this fails where the contract broke, loudly enough to name.
43
+ *
44
+ * An outcome nobody recognises throws for the same reason: silently reading it as `injected` would
45
+ * record a delivery that may never have happened.
46
+ */
47
+ export function mapSteerOutcome(result) {
48
+ const outcome = result?.outcome;
49
+ if (outcome === 'injected')
50
+ return 'injected';
51
+ if (outcome === 'promptRequired')
52
+ return 'promptRequired';
53
+ if (outcome === 'startedNewTurn') {
54
+ throw new Error(`[acp] the agent answered ${STEER_METHOD} with 'startedNewTurn', a turn this client never ` +
55
+ "opted into and does not track: we always send _meta.steering.idleBehavior = 'promptRequired'.");
56
+ }
57
+ throw new Error(`[acp] unrecognised ${STEER_METHOD} outcome: ${JSON.stringify(outcome)}`);
58
+ }