@platless/axom-cli 0.1.0 → 0.3.13

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 (109) hide show
  1. package/README.md +1 -1
  2. package/dist/authScopes.js +19 -0
  3. package/dist/axom-patch/cli/README.md +110 -0
  4. package/dist/axom-patch/cli/pi-steering.json +37 -0
  5. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +34 -0
  6. package/dist/axom-patch/cli/probes/pi-steering.mjs +80 -0
  7. package/dist/axom-patch/cli/probes/qoder-steering.mjs +89 -0
  8. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +49 -0
  9. package/dist/axom-patch/cli/qoder-background-terminal.md +81 -0
  10. package/dist/axom-patch/cli/qoder-foreground-output.md +41 -0
  11. package/dist/axom-patch/cli/qoder-steering.json +52 -0
  12. package/dist/axom-patch/cli/qoder-steering.md +30 -0
  13. package/dist/axom-patch/cli/qoder-subagent-origin.json +73 -0
  14. package/dist/axom-patch/cli/qoder-subagent-subwork.md +73 -0
  15. package/dist/axom-patch/cli/runtime.json +37 -0
  16. package/dist/axom-patch/cli/steering/claude-steering-js.patch +89 -0
  17. package/dist/axom-patch/cli/steering/claude-steering-types.patch +28 -0
  18. package/dist/axom-patch/cli/steering/codex-steering.patch +57 -0
  19. package/dist/axom-patch/cli/steering/pi-steering.js +79 -0
  20. package/dist/cli.js +9 -1
  21. package/dist/commands/auth.js +4 -13
  22. package/dist/commands/execute.js +4 -4
  23. package/dist/commands/record.js +6 -6
  24. package/dist/commands/struct.js +4 -4
  25. package/dist/commands/workstation.js +2 -2
  26. package/dist/credentials.js +2 -2
  27. package/dist/install/claude/commands.js +13 -13
  28. package/dist/mcp.js +32 -14
  29. package/dist/workstation/acp/acpAgents.js +447 -166
  30. package/dist/workstation/acp/acpClient.js +232 -18
  31. package/dist/workstation/acp/acpSession.js +502 -74
  32. package/dist/workstation/acp/activityBusy.js +43 -0
  33. package/dist/workstation/acp/activityReporting.js +54 -0
  34. package/dist/workstation/acp/adoption.js +107 -49
  35. package/dist/workstation/acp/authUrlSniffer.js +36 -0
  36. package/dist/workstation/acp/contract.js +11 -6
  37. package/dist/workstation/acp/delegationMcp.js +24 -0
  38. package/dist/workstation/acp/draftInterceptor.js +9 -0
  39. package/dist/workstation/acp/managedPatches.js +228 -0
  40. package/dist/workstation/acp/patchAssets.js +63 -0
  41. package/dist/workstation/acp/permission.js +300 -34
  42. package/dist/workstation/acp/qoderStartup.js +45 -0
  43. package/dist/workstation/acp/registry/catalog.js +263 -4
  44. package/dist/workstation/acp/registry/detect.js +55 -0
  45. package/dist/workstation/acp/sessionScope.js +19 -0
  46. package/dist/workstation/acp/sessionUpdateMapper.js +39 -21
  47. package/dist/workstation/acp/terminalLogin.js +197 -0
  48. package/dist/workstation/acp/test/fakeAgent.js +185 -9
  49. package/dist/workstation/acp/validate.js +33 -5
  50. package/dist/workstation/agentSessions.js +532 -44
  51. package/dist/workstation/channels/agent.js +7 -1
  52. package/dist/workstation/channels/computer.js +152 -4
  53. package/dist/workstation/channels/management.js +3 -3
  54. package/dist/workstation/config.js +3 -1
  55. package/dist/workstation/connection/encryptedChannel.js +88 -21
  56. package/dist/workstation/daemon.js +25 -3
  57. package/dist/workstation/desktop/binding.js +36 -0
  58. package/dist/workstation/desktop/bridge.js +212 -0
  59. package/dist/workstation/desktop/flow-control.js +52 -0
  60. package/dist/workstation/desktop/focused-text.js +130 -0
  61. package/dist/workstation/desktop/protocol.js +35 -0
  62. package/dist/workstation/fileOps.js +277 -0
  63. package/dist/workstation/filePaths.js +174 -0
  64. package/dist/workstation/fileWorker.js +81 -0
  65. package/dist/workstation/fileWorkerClient.js +121 -0
  66. package/dist/workstation/oauth/deviceAuthorization.js +21 -12
  67. package/dist/workstation/protocol/agent.js +154 -5
  68. package/dist/workstation/protocol/common.js +6 -0
  69. package/dist/workstation/protocol/computer.js +92 -3
  70. package/dist/workstation/qoderAdapter/backgroundProcess.js +106 -0
  71. package/dist/workstation/qoderAdapter/backgroundTerminal.js +262 -0
  72. package/dist/workstation/qoderAdapter/completedContinuation.js +139 -0
  73. package/dist/workstation/qoderAdapter/contract.js +78 -0
  74. package/dist/workstation/qoderAdapter/foregroundOutput.js +30 -0
  75. package/dist/workstation/qoderAdapter/history.js +87 -0
  76. package/dist/workstation/qoderAdapter/identityIndex.js +191 -0
  77. package/dist/workstation/qoderAdapter/inputQueue.js +42 -0
  78. package/dist/workstation/qoderAdapter/main.js +93 -0
  79. package/dist/workstation/qoderAdapter/mapper.js +117 -0
  80. package/dist/workstation/qoderAdapter/observerPlugin.js +245 -0
  81. package/dist/workstation/qoderAdapter/observerReceiver.js +187 -0
  82. package/dist/workstation/qoderAdapter/permissions.js +61 -0
  83. package/dist/workstation/qoderAdapter/profile.js +93 -0
  84. package/dist/workstation/qoderAdapter/sdkDriver.js +49 -0
  85. package/dist/workstation/qoderAdapter/session.js +319 -0
  86. package/dist/workstation/qoderAdapter/subagentMeta.js +28 -0
  87. package/dist/workstation/qoderAdapter/subwork.js +77 -0
  88. package/dist/workstation/qoderAdapter/taskControl.js +38 -0
  89. package/dist/workstation/qoderAdapter/taskEvent.js +26 -0
  90. package/dist/workstation/qoderAdapter/taskLedger.js +163 -0
  91. package/dist/workstation/qoderAdapter/types.js +1 -0
  92. package/dist/workstation/qoderAdapter/usage.js +37 -0
  93. package/dist/workstation/register.js +4 -0
  94. package/dist/workstation/tools/browser.js +394 -0
  95. package/dist/workstation/tools/client.js +269 -0
  96. package/dist/workstation/tools/core.js +50 -0
  97. package/dist/workstation/tools/files.js +525 -0
  98. package/dist/workstation/tools/keeperProcess.js +114 -0
  99. package/dist/workstation/tools/terminal.js +214 -0
  100. package/dist/workstation/tools/validation.js +50 -0
  101. package/dist/workstation/update/coordinator.js +29 -0
  102. package/dist/workstation/update/npmInstaller.js +2 -0
  103. package/dist/workstation/update/ptyPreflight.js +24 -0
  104. package/dist/workstation/update/taskRegistry.js +44 -20
  105. package/dist/workstation/update/watchdog.js +66 -0
  106. package/package.json +14 -4
  107. package/skills/axom/SKILL.md +55 -17
  108. package/skills/axom-definition/SKILL.md +32 -32
  109. package/skills/axom-platform/SKILL.md +39 -39
@@ -1,18 +1,208 @@
1
+ /**
2
+ * Axom 认证过的 agent 清单 —— 取代 `officialCli.ts`。
3
+ *
4
+ * ## 它是什么:Axom 对官方 registry 的本地扩展层
5
+ *
6
+ * 一个 agent 在本清单里,就是我们真机验证过(roster 的 `curated`),设置页把它放进展开区;其余官方
7
+ * 条目进折叠的 "Unverified Agents"。本清单能做的事,各有一节在下面:
8
+ *
9
+ * - **推荐**:`recommended` 是静态的,只给愿意向任何一台新机器推荐的(claude、codex、pi)。
10
+ * - **本地检测**(`detect`,见 `detect.ts`):PATH 上的命令或固定文件位置,只看文件在不在、不起进程。
11
+ * 检测到 = 这台机器的用户自己在用它,于是它也进推荐位,原因写"本机在用"。
12
+ * - **前置依赖**(`prerequisite`):适配器之外、它起得来所必需的本体(Pi 的 CLI、ZCode 桌面应用),
13
+ * 同样靠 `detect` 判断在不在;有 `install` 就由 Install 先装,没有就只告诉用户怎么办。
14
+ * - **补充、取代与钉版本**:`supplement` / `supersedes` / `distribution`,以及 `launchEnv`。
15
+ *
16
+ * ## 托管与检测是两件事
17
+ *
18
+ * Axom 启动的**一律是 Axom 托管的那份**——npx 缓存、`uv tool dir`、`<config dir>/acp-adapters` 里我们
19
+ * 自己装的、钉了版本的副本,与用户自己的安装隔离、独立升级。`adapterInstalled` 只回答这一份在不在。
20
+ * 用户 PATH 上的 `kimi`、`command-code` **不算** Axom 已安装,也从不被拿来启动:它只是 `detect` 的
21
+ * 推荐信号。两份共用数据目录(`~/.kimi-code`、`~/.commandcode`),所以登录是共享的。
22
+ *
23
+ * ## 它与被它取代的那张表不是一回事
24
+ *
25
+ * `officialCli.ts` 断言的是"这个 agent 的官方 CLI 在不在 PATH",而那个信号既不决定能否启动
26
+ * (npx 适配器多数自带引擎),也不决定能否使用(凭据才是),于是它撑起来的 `Installed` 徽章
27
+ * 是一句没人能负责的话。本清单不做任何这类断言:它只提供**我们自己认证过**的三样东西 ——
28
+ * 用户友好的默认名字、这个 agent 除适配器之外还需要什么、以及它是否进推荐位;本地检测也只回答
29
+ * "用户自己装了没有",从不替 Axom 托管的那份作答。
30
+ *
31
+ * ## 为什么不为全部 39 个 agent 建表
32
+ *
33
+ * 见 spec §2.3:适配器需不需要本机 vendor CLI 是**版本级**属性,只能靠扒依赖树猜,而且完全
34
+ * 没有必要 —— 试跑失败时 agent 自己那句话(`Pi needs to be installed. Install it via …`)
35
+ * 带着确切的安装命令,且永远与它自身版本同步。本清单只覆盖我们真机验证过的少数几个。
36
+ *
37
+ * ## `prerequisite.install` 是安全边界
38
+ *
39
+ * 它会在工作站上**直接执行**,不弹二次确认。所以它只能是本文件里的字面量参数数组,永远不接受
40
+ * 来自 registry、agent 响应、网络内容或用户输入的字符串;以 argv 形式交给 execFile,不经 shell。
41
+ * `catalog.test.ts` 有一条机械守卫盯着这件事。
42
+ *
43
+ * ## `distribution` 覆盖 registry 的安装来源
44
+ *
45
+ * 这里写下的来源**整个替换** registry 的 `version` 与 `distribution`,id 与展示名仍是 registry 的,
46
+ * 于是已有的配置条目、roster、安装、启动、更新都不用知道发生过替换(见 {@link withCatalogOverrides})。
47
+ * 本文件里的是长期的(Qoder:registry 落后于验证过的原生版本);临时的钉版本放在
48
+ * axom-patch/cli/runtime.json,按 registry 版本挑选(`when_registry_version` / `until_registry_version`,
49
+ * 见 `patchAssets.ts`),registry 一到就自己让位。两者与上面的 `install` 是同一条安全边界:只能是
50
+ * 随 CLI 发布、钉死版本、由真机验证过的可信配置。
51
+ *
52
+ * ## `supplement` 与 `supersedes`:registry 缺的条目、registry 不肯撤的条目
53
+ *
54
+ * registry 的 `kimi` 跟的是 `MoonshotAI/kimi-cli`,那个仓库已归档,停在 1.52.0;继任者 Kimi Code
55
+ * (`MoonshotAI/kimi-code`)的条目 `kimi-code` 还在 registry#458 里等审核。Command Code 与社区的
56
+ * ZCode 适配器(registry#566、#650)也都还没进 registry。registry 没有「已停止维护」这样的字段
57
+ * (agent.schema.json 只有 `preview` 渠道),所以由本清单声明:
58
+ *
59
+ * - `supplement`:registry 里**没有**这个 id 时由我们补上的整条条目,钉死版本。registry 一收录这个 id
60
+ * 就用 registry 的,之后的版本由 registry 的机器人每小时跟进,补充条目不再生效。
61
+ * - `supersedes`:被这个条目取代的 registry id,从列表里隐藏。Kimi Code 装上后会把旧版的数据目录
62
+ * 迁走,旧适配器从此看不见用户的登录(2026-09-25 实测),两个并列只会让人选错。
63
+ *
64
+ * ## `launchEnv`:Axom 启动适配器时带的环境变量
65
+ *
66
+ * 经 ACP 适配器拉起的 agent 跑在无头/SDK 模式,它们的默认工具集与用户在终端里交互使用时**不同**,
67
+ * 而差出来的恰好是我们要的东西。2026-09-30 实测:claude-agent-acp 0.84.0 拉起的 Claude Code 没有
68
+ * TaskCreate/TaskUpdate/TodoWrite(ToolSearch 答 "No matching deferred tools found"),codex-acp
69
+ * 2.0.1 拉起的 Codex 0.159.2 没有 `update_plan`(问它自己答 "NO")——于是这两个 agent 从不发 ACP
70
+ * `plan`,生产上三天 218 个外部会话一张计划卡都没有。各自打开一个开关后同一段提示词分别发出 14 次与
71
+ * 4 次 `plan`。
72
+ *
73
+ * 这里写下的变量并进 registry 条目的 `distribution.*.env`,所以它是**最底下的一层**:registry
74
+ * 自己声明的 env、工作站配置、用户按 agent 配的 `agentEnv` 都能覆盖它(见 `connectAgent`)。一个
75
+ * 用户自己配了 `CODEX_CONFIG` 的 Codex 因此会整份替换掉这里的值——那是显式配置,按它来;要保留计划
76
+ * 工具,把 `tools.update_plan.enabled` 写进自己那份里。
77
+ *
78
+ * 这些开关不是上游文档化的稳定接口,适配器升级时要复核仍然生效(上面那段实测的做法:直接用 ACP
79
+ * 客户端拉起适配器,问它有没有计划工具)。
80
+ */
81
+ import { patchOverridesFor } from '../patchAssets.js';
82
+ // Temporary distribution/environment overrides are bundled from axom-patch/cli/runtime.json.
1
83
  export const AXOM_AGENT_CATALOG = [
2
- { registryAgent: 'claude-acp', defaultName: 'claude-agent', defaultLabel: 'Claude Agent' },
3
- { registryAgent: 'codex-acp', defaultName: 'codex', defaultLabel: 'Codex' },
84
+ {
85
+ registryAgent: 'claude-acp',
86
+ defaultName: 'claude-agent',
87
+ defaultLabel: 'Claude Agent',
88
+ recommended: true,
89
+ detect: { commands: ['claude'] },
90
+ },
91
+ { registryAgent: 'codex-acp', defaultName: 'codex', defaultLabel: 'Codex', recommended: true, detect: { commands: ['codex'] } },
92
+ {
93
+ registryAgent: 'qoder',
94
+ defaultName: 'qoder',
95
+ defaultLabel: 'Qoder',
96
+ 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'] } },
101
+ },
4
102
  // `gemini` was here and is deliberately gone. `@google/gemini-cli --acp` now refuses `session/new`
5
103
  // outright ("This client is no longer supported for Gemini Code Assist for individuals … migrate
6
104
  // to the Antigravity suite"), so recommending it puts an agent in the popover's top tier that
7
105
  // cannot open a session for whoever takes the recommendation. It stays in the ACP registry and can
8
106
  // still be added by hand from the collapsed section; what is withdrawn is Axom's own endorsement,
9
107
  // which is the only thing this file publishes.
108
+ {
109
+ registryAgent: 'kimi-code',
110
+ defaultName: 'kimi-code',
111
+ defaultLabel: 'Kimi Code',
112
+ // Kimi Code 发布独立二进制(GitHub release,6 平台 tar.gz,各带 `.sha256`;归档根目录就是 `kimi`,
113
+ // Windows 是 `kimi.exe`),不依赖本机 Node —— npm 包 `@moonshot-ai/kimi-code` 要 Node >= 22.19,
114
+ // 会把 Node 版本变成又一个"装好了却起不来"的理由。`acp` 子命令是它的 ACP 服务端;凭据在
115
+ // `~/.kimi-code`,与用户自己装的那份共用——实测把二进制挪到别的目录照样免登录开会话。
116
+ // 其余字段照 registry#458 的条目写。
117
+ supplement: {
118
+ name: 'Kimi Code',
119
+ version: '2.1.1',
120
+ description: "Moonshot AI's next-gen coding agent",
121
+ repository: 'https://github.com/MoonshotAI/kimi-code',
122
+ website: 'https://moonshotai.github.io/kimi-code/',
123
+ authors: ['Moonshot AI'],
124
+ license: 'MIT',
125
+ icon: 'https://cdn.agentclientprotocol.com/registry/v1/latest/kimi.svg',
126
+ distribution: {
127
+ binary: kimiCodeBinary('2.1.1', {
128
+ 'darwin-aarch64': ['darwin-arm64', '8c3bad99571b16abd113ab5d511a98a36b852c0d5493991842a67b8c2bc4ecd0'],
129
+ 'darwin-x86_64': ['darwin-x64', 'f6dc0289112c24119d070f9203d44bcd53580e380819db25b58be65643b75bd4'],
130
+ 'linux-aarch64': ['linux-arm64', '5c68d74b139b47fb3457f3805a159cdfa942a6a98a159ab158a1baf93b024bbd'],
131
+ 'linux-x86_64': ['linux-x64', 'b66cc3787bc9dfa6a06761305dc8878f98e7a24f79fa4494c00714229a1486e8'],
132
+ 'windows-aarch64': ['win32-arm64', '72bb17fef0d165a5f61905ede7e57c552791905d605a778967d054fe702873d4'],
133
+ 'windows-x86_64': ['win32-x64', '87756a53a9bb0915842d884eaf43f69a06b2e404bcbb05d41c519c12d4b410cb'],
134
+ }),
135
+ },
136
+ },
137
+ supersedes: ['kimi'],
138
+ // 官方安装器把二进制放在 `~/.kimi-code/bin`,不一定进了 daemon 看得见的 PATH。
139
+ detect: {
140
+ commands: ['kimi'],
141
+ locations: everyPlatform(['~/.kimi-code/bin/kimi'], ['~/.kimi-code/bin/kimi.exe']),
142
+ },
143
+ },
144
+ {
145
+ registryAgent: 'command-code',
146
+ defaultName: 'command-code',
147
+ defaultLabel: 'Command Code',
148
+ // registry 没有它。1.66.0 还没有 `acp` 子命令,1.74.3 起有、真机试跑通过。包里有 `cmd`、`cmdc`、
149
+ // `commandcode`、`command-code` 四个 bin,npx 挑与包名同名的那个。没有 ACP 登录方法,只有一句
150
+ // 说明(终端里跑 `cmd login`);凭据在 `~/.commandcode`,与用户自己装的那份共用。npm 上的许可证
151
+ // 写的是 UNLICENSED(专有)。
152
+ supplement: {
153
+ name: 'Command Code',
154
+ version: '1.74.3',
155
+ description: 'Command Code, coding agent that continuously learns your coding taste.',
156
+ website: 'https://commandcode.ai',
157
+ authors: ['Command Code'],
158
+ license: 'UNLICENSED',
159
+ icon: 'https://commandcode.ai/favicon.ico',
160
+ distribution: { npx: { package: 'command-code@1.74.3', args: ['acp'] } },
161
+ },
162
+ detect: { commands: ['command-code', 'commandcode'] },
163
+ },
164
+ {
165
+ registryAgent: 'zcode-acp',
166
+ defaultName: 'zcode',
167
+ 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`,
171
+ // 所以 ZCode 本体是前置依赖,而且没有一条我们能替用户跑的安装命令 —— 只能告诉他去装。
172
+ // 凭据在 `~/.zcode/v2/config.json`,由 ZCode 自己登录写下。Linux 上本体没有固定路径,只认 PATH。
173
+ supplement: {
174
+ name: 'ZCode',
175
+ version: '0.63.0',
176
+ description: 'Community bridge that drives the real ZCode app-server (GLM) over ACP.',
177
+ repository: 'https://github.com/william0wang/zcode-acp',
178
+ website: 'https://github.com/william0wang/zcode-acp',
179
+ authors: ['William Wang'],
180
+ license: 'Apache-2.0',
181
+ icon: 'https://raw.githubusercontent.com/william0wang/zcode-acp/main/registry/zcode-acp/icon.svg',
182
+ distribution: { npx: { package: 'zcode-acp-server@0.63.0' } },
183
+ },
184
+ prerequisite: {
185
+ label: 'ZCode',
186
+ detect: {
187
+ commands: ['zcode'],
188
+ locations: {
189
+ 'darwin-aarch64': ['/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs'],
190
+ 'darwin-x86_64': ['/Applications/ZCode.app/Contents/Resources/glm/zcode.cjs'],
191
+ 'windows-aarch64': ['%LOCALAPPDATA%/Programs/ZCode/resources/glm/zcode.cjs'],
192
+ 'windows-x86_64': ['%LOCALAPPDATA%/Programs/ZCode/resources/glm/zcode.cjs'],
193
+ },
194
+ },
195
+ hint: 'Install the ZCode desktop app (https://zcode.z.ai) and sign in once.',
196
+ },
197
+ },
10
198
  {
11
199
  registryAgent: 'pi-acp',
12
200
  defaultName: 'pi',
13
201
  defaultLabel: 'Pi',
14
- dependency: {
15
- check: 'pi',
202
+ recommended: true,
203
+ prerequisite: {
204
+ label: 'Pi',
205
+ detect: { commands: ['pi'] },
16
206
  // 不用 `curl -fsSL https://pi.dev/install.sh | sh`:读过那个脚本,它最终跑的就是这一条
17
207
  // (`npm install -g --ignore-scripts @earendil-works/pi-coding-agent`),装出来的是同一个
18
208
  // npm 包 —— pi 没有原生分发。脚本唯一的增值是 Node 不够新时帮你装 Node,而那一步是交互式的
@@ -22,8 +212,77 @@ export const AXOM_AGENT_CATALOG = [
22
212
  requiresNode: '>=22.19.0',
23
213
  },
24
214
  },
215
+ // registry 自己的条目(binary .par),我们只补名字与本机检测;`agy` 是 Antigravity 装好后放进 PATH 的命令。
216
+ { registryAgent: 'antigravity-acp', defaultName: 'google-antigravity', defaultLabel: 'Google Antigravity', detect: { commands: ['agy'] } },
25
217
  ];
218
+ /**
219
+ * Kimi Code 的六个平台归档:GitHub release `@moonshot-ai/kimi-code@<version>` 下的
220
+ * `kimi-code-<target>.tar.gz`,`sha256` 抄自同名 `.sha256` 资产。版本升级时三样一起换。
221
+ */
222
+ function kimiCodeBinary(version, targets) {
223
+ const release = `https://github.com/MoonshotAI/kimi-code/releases/download/@moonshot-ai/kimi-code@${version}`;
224
+ return Object.fromEntries(Object.entries(targets).map(([triple, [target, sha256]]) => [triple, {
225
+ archive: `${release}/kimi-code-${target}.tar.gz`,
226
+ sha256,
227
+ cmd: triple.startsWith('windows-') ? './kimi.exe' : './kimi',
228
+ args: ['acp'],
229
+ }]));
230
+ }
231
+ /** 同一组位置用于全部平台(Windows 三元组用 `windows` 那组)。 */
232
+ function everyPlatform(unix, windows) {
233
+ return {
234
+ 'darwin-aarch64': unix, 'darwin-x86_64': unix, 'linux-aarch64': unix, 'linux-x86_64': unix,
235
+ 'windows-aarch64': windows, 'windows-x86_64': windows,
236
+ };
237
+ }
26
238
  const BY_ID = new Map(AXOM_AGENT_CATALOG.map((entry) => [entry.registryAgent, entry]));
27
239
  export function catalogEntryFor(registryAgent) {
28
240
  return BY_ID.get(registryAgent);
29
241
  }
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) {
250
+ const entry = catalogEntryFor(agent.id);
251
+ const patch = patchOverridesFor(agent.id, agent.version);
252
+ const pinned = patch.distribution ?? entry?.distribution;
253
+ const launchEnv = { ...entry?.launchEnv, ...patch.env };
254
+ let effective = agent;
255
+ if (pinned) {
256
+ const { version, ...distribution } = pinned;
257
+ effective = { ...effective, version, distribution };
258
+ }
259
+ if (Object.keys(launchEnv).length) {
260
+ effective = { ...effective, distribution: withLaunchEnv(effective.distribution, launchEnv) };
261
+ }
262
+ return effective;
263
+ }
264
+ /** The registry's list with this catalog's supplements added where the registry lacks them and superseded ids removed. */
265
+ export function withCatalogEntries(agents) {
266
+ const published = new Set(agents.map((agent) => agent.id));
267
+ const superseded = new Set(AXOM_AGENT_CATALOG.flatMap((entry) => entry.supersedes ?? []));
268
+ const supplements = AXOM_AGENT_CATALOG.flatMap((entry) => entry.supplement && !published.has(entry.registryAgent) ? [{ ...entry.supplement, id: entry.registryAgent }] : []);
269
+ return [...agents.filter((agent) => !superseded.has(agent.id)), ...supplements];
270
+ }
271
+ function withLaunchEnv(distribution, launchEnv) {
272
+ const under = (target) => ({ ...target, env: { ...launchEnv, ...(target.env ?? {}) } });
273
+ return {
274
+ ...distribution,
275
+ ...(distribution.npx && { npx: under(distribution.npx) }),
276
+ ...(distribution.uvx && { uvx: under(distribution.uvx) }),
277
+ ...(distribution.binary && {
278
+ binary: Object.fromEntries(Object.entries(distribution.binary).map(([platform, target]) => [platform, target && under(target)])),
279
+ }),
280
+ };
281
+ }
282
+ /**
283
+ * {@link withCatalogEntries} then {@link applyCatalogOverrides} over a whole registry. `AcpAgents` reads the registry through this
284
+ * and nothing else, so roster, install, launch and update all see the same effective entries.
285
+ */
286
+ export function withCatalogOverrides(registry) {
287
+ return { load: async () => withCatalogEntries(await registry.load()).map(applyCatalogOverrides) };
288
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 本地检测:这台机器上**用户自己**有没有装某个 agent(或它的前置依赖)。
3
+ *
4
+ * ## 它回答的不是"Axom 装好了没有"
5
+ *
6
+ * Axom 启动的一律是自己托管的那份(npx 缓存、`uv tool dir`、`<config dir>/acp-adapters`),
7
+ * 那是 `acpAgents.ts` 的 `adapterInstalled` 的事。这里看的是用户在终端里用的那份:PATH 上的命令、
8
+ * 厂商安装器放下的固定文件。它只有两个用途(见 `catalog.ts` 文件头):
9
+ *
10
+ * - **推荐信号**:检测到 = 这台机器的用户在用它,于是设置页把它排进推荐位,原因写"本机在用"。
11
+ * - **前置依赖**:ZCode、Pi 这类适配器自己不带引擎,检测不到本体就起不来。
12
+ *
13
+ * ## 只看文件在不在,不起进程
14
+ *
15
+ * 与 `installProbe.ts` 的 `findOnPath` 同一条规矩:roster 一次要对 registry 里全部 agent 回答,
16
+ * 一个会执行东西的探针就是每次扫描几十个子进程。`--version` 回答的是另一个更贵的问题。
17
+ */
18
+ import { statSync } from 'node:fs';
19
+ import path from 'node:path';
20
+ import { findOnPath } from '../installProbe.js';
21
+ /**
22
+ * 按 `spec` 在这台机器上找:PATH 上的任一命令,或本机三元组下的任一文件位置。
23
+ *
24
+ * `triple` 是 `binaryInstall.ts` 的 `platformTriple` 算出来的本机三元组;落在 registry 六个三元组
25
+ * 之外的机器传 `undefined`,此时只看 `commands`。
26
+ */
27
+ export function detected(spec, probe, triple) {
28
+ if (!spec)
29
+ return false;
30
+ if (spec.commands?.some((command) => findOnPath(command, probe) !== undefined))
31
+ return true;
32
+ const locations = triple ? spec.locations?.[triple] : undefined;
33
+ return locations?.some((location) => {
34
+ const resolved = resolveLocation(location, probe);
35
+ return resolved !== undefined && isFile(resolved);
36
+ }) ?? false;
37
+ }
38
+ /** 展开 `~/` 与 `%LOCALAPPDATA%/`;对应的目录未知时返回 `undefined`(这条线索查不了,不是命中)。 */
39
+ export function resolveLocation(location, probe) {
40
+ if (location.startsWith('~/'))
41
+ return probe.home ? path.join(probe.home, location.slice(2)) : undefined;
42
+ if (location.startsWith('%LOCALAPPDATA%/')) {
43
+ return probe.localAppData ? path.join(probe.localAppData, location.slice('%LOCALAPPDATA%/'.length)) : undefined;
44
+ }
45
+ return location;
46
+ }
47
+ // `statSync` 而不是 `lstatSync`:与 `findOnPath` 同理,厂商安装器常放符号链接。
48
+ function isFile(candidate) {
49
+ try {
50
+ return statSync(candidate).isFile();
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
@@ -0,0 +1,19 @@
1
+ import { SessionMapperState } from './sessionUpdateMapper.js';
2
+ export function scopeKey(scope) {
3
+ return JSON.stringify([scope.rootRuntimeId, scope.nativeSessionId]);
4
+ }
5
+ export class SessionMapperScopes {
6
+ create;
7
+ states = new Map();
8
+ constructor(create = () => new SessionMapperState()) {
9
+ this.create = create;
10
+ }
11
+ get(scope) {
12
+ const key = scopeKey(scope);
13
+ let state = this.states.get(key);
14
+ if (!state)
15
+ this.states.set(key, state = this.create(scope));
16
+ return state;
17
+ }
18
+ clear() { this.states.clear(); }
19
+ }
@@ -1,3 +1,4 @@
1
+ import { codexActivityRaw } from './activityReporting.js';
1
2
  /**
2
3
  * One session's memory, and the reason this file is no longer stateless: **what this session has
3
4
  * already been told**, for the two branches that cannot answer a frame from the frame alone.
@@ -30,7 +31,22 @@
30
31
  * already (every call's `rawInput`/`rawOutput` is journaled), so there is no eviction here.
31
32
  */
32
33
  export class SessionMapperState {
34
+ onActivityReported;
33
35
  byId = new Map();
36
+ /**
37
+ * Where an agent-reported activity-state push (today, only `codexActivityRaw`'s extraction of
38
+ * `session_info_update._meta.codex.threadStatus`) actually goes once {@link reportActivity} is
39
+ * called — `agentSessions.ts`'s job, not this file's: this module stays "zero IO" (see the file
40
+ * doc comment) by taking the effect as a constructor dependency rather than reaching for a
41
+ * socket itself.
42
+ *
43
+ * Defaults to a no-op so every existing `new SessionMapperState()` — the tests, `adoption.ts`'s
44
+ * replay path, which has no live connection to report anything on — keeps compiling and keeps
45
+ * doing nothing, exactly as it always has.
46
+ */
47
+ constructor(onActivityReported = () => { }) {
48
+ this.onActivityReported = onActivityReported;
49
+ }
34
50
  /**
35
51
  * The agent queue depth this session has already reported to the server.
36
52
  *
@@ -97,6 +113,16 @@ export class SessionMapperState {
97
113
  this.lastReportedQueueDepth = depth;
98
114
  return true;
99
115
  }
116
+ /**
117
+ * One agent-reported activity-state push, vendor-verbatim. Not a `WireEvent` and never routed
118
+ * through {@link mapSessionUpdate}'s return value — activity is a level, not an edge (see the
119
+ * `agent_session_state` frame's own doc comment in `protocol/agent.ts`), so it must not become a
120
+ * durable item, must not be journaled and must not advance any seq. Forwarded to whatever
121
+ * {@link onActivityReported} this instance was constructed with.
122
+ */
123
+ reportActivity(raw) {
124
+ this.onActivityReported(raw);
125
+ }
100
126
  /** Remember what this call is called. Later updates that carry no `name` fall back to it. */
101
127
  record(toolCallId, name) {
102
128
  if (name)
@@ -136,7 +162,7 @@ function compact(value) {
136
162
  return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined));
137
163
  }
138
164
  /**
139
- * `ToolCall.locations` → the wire `tool_call.locations` shape; omitted entirely when there are none.
165
+ * `ToolCall.locations` → the wire `tool_call.locations` shape; [] explicitly clears a snapshot.
140
166
  *
141
167
  * An identity copy of ACP's `ToolCallLocation` (`types.gen.d.ts:581`), which is `{path, line?}` —
142
168
  * `line` is `number | null | undefined` (`:586-589`) and used to be dropped here, which cost the
@@ -145,7 +171,7 @@ function compact(value) {
145
171
  * enclosing payload.
146
172
  */
147
173
  function mapLocations(locations) {
148
- if (!locations || locations.length === 0)
174
+ if (locations == null)
149
175
  return undefined;
150
176
  return locations.map((location) => compact({ path: location.path, line: location.line ?? undefined }));
151
177
  }
@@ -166,7 +192,7 @@ function toolCallCreatedEvent(update, state) {
166
192
  // only field guaranteed to give the card a name.
167
193
  name: update.name ?? update.title,
168
194
  toolCallId: update.toolCallId,
169
- rawInput: update.rawInput,
195
+ rawInput: update.rawInput ?? undefined,
170
196
  kind: mapToolKind(update.kind),
171
197
  title: update.title,
172
198
  locations: mapLocations(update.locations),
@@ -239,16 +265,10 @@ function toolCallUpdateEvent(update, state) {
239
265
  title: update.title ?? undefined,
240
266
  kind: update.kind ?? undefined,
241
267
  status: update.status ?? undefined,
242
- // An empty array is omitted rather than sent, and the behaviour is deliberate now that the
243
- // card's fold *does* honour ACP's "replace the content collection": `applyChunks` treats an
244
- // empty chunk list as "unchanged" at every call site, never as a clear. So an empty array
245
- // carries nothing either way — and the server drops it on the way back out anyway
246
- // (`TimelineEvent.ToolCallUpdated.payload` only writes `content` when it is non-empty), which
247
- // is why sending one could not express a clear even if a consumer wanted to act on it.
248
- content: update.content && update.content.length > 0 ? update.content : undefined,
268
+ content: update.content == null ? undefined : update.content,
249
269
  locations: mapLocations(update.locations),
250
- rawInput: update.rawInput,
251
- rawOutput: update.rawOutput,
270
+ rawInput: update.rawInput ?? undefined,
271
+ rawOutput: update.rawOutput ?? undefined,
252
272
  // See the doc comment above: ACP's extension slot, copied verbatim and read by nothing *here*
253
273
  // (the server's `decodeToolUpdate` does read it, also verbatim).
254
274
  _meta: update._meta ?? undefined,
@@ -257,16 +277,11 @@ function toolCallUpdateEvent(update, state) {
257
277
  }
258
278
  function mapToolCallEvents(update, state) {
259
279
  const events = [toolCallCreatedEvent(update, state)];
260
- // ACP permits a `tool_call` to arrive already finished — the agent reporting a call it both
261
- // started and completed in one notification, with no separate `tool_call_update` at all. A card
262
- // only ever leaves "Running" on an update reporting a non-pending status
263
- // (`WorkTimelineTools.foldToolUpdate`'s outcome branch), and the creation event carries no status,
264
- // so without this second event an already-terminal call would render as permanently "Running".
265
- //
280
+ // Creation carries initial update fields too. Emit them immediately under the same id so pending
281
+ // content and status, as well as an already-terminal result, survive a missing later update.
266
282
  // `ToolCall` is structurally a `ToolCallUpdate` (every field of the latter, with `title` and `kind`
267
- // merely narrower), so the same function emits both — the two cannot drift into disagreeing about
268
- // what a terminal update looks like.
269
- if (update.status === 'completed' || update.status === 'failed') {
283
+ // merely narrower), so the same function emits both.
284
+ if (update.status != null || update.content != null || update.rawOutput != null) {
270
285
  events.push(toolCallUpdateEvent(update, state));
271
286
  }
272
287
  return events;
@@ -476,6 +491,9 @@ export function mapSessionUpdate(update, state) {
476
491
  const queueNotice = agentQueueNotice(update._meta, state);
477
492
  if (queueNotice)
478
493
  events.push(queueNotice);
494
+ const activityRaw = codexActivityRaw(update._meta);
495
+ if (activityRaw)
496
+ state.reportActivity(activityRaw);
479
497
  return events;
480
498
  }
481
499
  case 'usage_update':