@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
@@ -16,11 +16,20 @@ Workstation Docker 同样携带这份策略;普通 CLI 安装/Server 下发更
16
16
  | 都不写 | 一直生效,直到删除 | 不允许 |
17
17
 
18
18
  when 用于「这个版本有问题、已有修复版」,registry 一换版本就不再生效;until 用于「等上游发某个版本」,
19
- 钉住的版本必须排在界限之前。两种都不需要为让位再发 CLI。
19
+ 钉住的版本不得高于界限:低于界限可用于等待正式修复版,等于界限可用于等待 registry 收录已发布版本。
20
+ 两种都不需要为让位再发 CLI。
20
21
 
21
22
  用户按正常界面安装/更新适配器即可:补充条目(Kimi Code、Command Code、ZCode)、分发源覆盖和启动环境变量通过 catalog 应用,
22
23
  roster、Install、Update、启动统一读取同一来源。
23
- 发布包代码补丁由 `managedPatches.ts` 在 Install/Update 和首次真实启动时应用。
24
+
25
+ **有补丁的条目必须钉版本**(2026-10-14 起的硬规则,`catalog.test.ts` 的 `patched agents stay pinned`
26
+ 机械守卫):配方只核验过列出的版本,不钉的话 registry/npm 一发新版,Install/Update 就装上一个配方
27
+ 不生效的包——账号配额、steering、子代理归一静默消失。钉住后 roster 上报的 `version`/`registryVersion`
28
+ 就是生效(钉住)版本,设置页的 Update 比对(`installedVersion != registryVersion`)因此永远对着
29
+ catalog 的钉版本,**不对着官方 registry 比**;上游新版本要等
30
+ [`nightly-task.md`](nightly-task.md) 的周期任务核验移植配方后再 bump,届时
31
+ `reconcileCertifiedBuilds` 把已安装的工作站自动前移。
32
+ 发布包补丁由 `managedPatches.ts` 在 Install/Update 和首次真实启动时应用。
24
33
  历史安装也会补齐,配置显式 command 的用户自有启动程序按其配置执行。
25
34
 
26
35
  发布包补丁步骤:
@@ -53,17 +62,25 @@ catalog 是 Axom 对官方 ACP registry 的本地扩展层,随 CLI 发布:
53
62
 
54
63
  | 台账 ID | 状态/应用入口 | AI 核验与下线重点 |
55
64
  |---|---|---|
65
+ | qoder-registry-catch-up | active;registry 低于 1.1.71(或版本不可解析)时安装已验证的 `@qoder-ai/qodercli@1.1.71 --acp` | registry 达到 1.1.71 或更高时自动让位;核实上游条目可安装后移除本 override。仅移除安装来源覆盖,不同时删除 subagent/插话的版本限定补丁 |
56
66
  | kimi-code-distribution | active;catalog.ts 补充条目 `kimi-code`(Kimi Code 2.1.1 官方二进制,六平台 tar.gz + sha256),隐藏已归档的 `kimi` | registry#458 合入后补充条目自动让位、版本由 registry 跟进;验证 Install/Update、复用登录后删除 supplement |
57
- | command-code-distribution | active;catalog.ts 补充条目 `command-code`(npx `command-code@1.74.3 acp`) | registry 收录后自动让位;确认 registry 版本带 `acp` 子命令后删除 supplement |
58
- | zcode-acp-distribution | active;catalog.ts 补充条目 `zcode-acp`(npx `zcode-acp-server@0.63.0`),前置依赖 ZCode 本体 | registry#566/#650 合入后自动让位;删除 supplement 时保留 prerequisite |
67
+ | command-code-distribution | active;catalog.ts 补充条目 `command-code`(npx `command-code@1.82.1 acp`) | registry 收录后自动让位;确认 registry 版本带 `acp` 子命令后删除 supplement |
68
+ | zcode-acp-distribution | active;catalog.ts 补充条目 `zcode-acp`(npx `zcode-acp-server@0.69.4`),前置依赖 ZCode 本体 | registry#566/#650 合入后自动让位;删除 supplement 时保留 prerequisite |
69
+ | zcode-models | active;fresh-install 模型兑底并入账号目录(`loadAllModels` 兜底读 entitled builtin 表,GLM-5.3-Flash 等不再丢) | 上游 `loadAllModels` 并入账号快照目录后删除 |
70
+ | zcode-subagent-subwork | active;zcode-acp-server 0.69.3/0.69.4(目标文件字节一致)三文件九编辑:`AxomZcodeSubagents` 监听器把后端 `session.updated`(agentId/childSessionId/taskId)转发为 `subagent_spawned`/`subagent_state_update`(RFD #1992)与 `async_task_*`;dispatch 把 `tool_subagent_*` 内部调用重定址到 `sess_subagent_*` 子会话,协商到原生能力时抑制 `_meta.subagent` 标记(防双投影),声明 AIR asyncTasks 时抑制上游 `[后台]` 合成卡(防与 async_task 双重呈现,workflow 折叠不受影响);`adoptStoredTitle` 在 (重)挂载时补发 `session_info_update` 同步后端已生成的标题(titleUpdated 推送会丢在 backend 重启断点上) | 上游 zcode-acp 原生发出 T2/T3 变体并路由子会话、且 load 时同步存量标题后删除配方;验收含并发、失败/取消、后台迟到、重启重放 |
59
71
  | claude-plan-tools | active;启动任务清单工具变量 | 无变量真实 ACP 会话能发布 plan 后下线 |
60
72
  | codex-plan-tools | active;CODEX_CONFIG 的 update_plan 开关 | 无覆盖真实 ACP 会话能发布 plan;保留显式用户配置优先级 |
61
- | claude-async-task-preview | active;registry 为 0.86.0 时钉 0.86.1-preview.2(`when_registry_version`) | 上游 #1249:不再从前台 Bash 文本猜后台任务、已结束 child 的 async 终态照常送达。registry 换成其他版本即不生效,确认正式版含修复后删除本条 |
73
+ | claude-account-limits | active;配方 0.90.0,catalog 钉 0.90.0 | 上游 adapter 暴露标准化账号配额能力且不自动查询后删除;版本升级走 nightly-task.md |
74
+ | codex-account-limits | active;配方 2.2.2,catalog 钉 2.2.2 | 同上 |
75
+ | commandcode-account-limits | active;配方 1.82.1(supplement 同钉) | 同上 |
76
+ | antigravity-account-limits | active;native extension 1.3.0/1.4.0,catalog 钉 1.4.0(真实 .par 经 proxy 实测:透传无损、能力注入成功,2026-10-14) | 官方 adapter 暴露配额读取与通知;上游发新版按 nightly-task.md 核验后同升 |
77
+ | claude-async-task-preview | retired;0.86.0 钉 0.86.1-preview.2 的 override 已删除 | 修复 #1249 自 0.87.0 起入正式版;2026-10-13 无补丁原版 0.90.0 探针 6/6 通过(探针构造器参数改 `"air"`,0.90.0 将 enabled 改名为 form) |
62
78
  | claude-late-async-terminal | retired;0.84.0/0.85.0 配方已删除 | 修复由上游 #1249 带入预览版并被采用;探针在无补丁预览版 6/6 通过 |
63
- | acp-sdk-draft-variants | active;draftInterceptor / draftSubagents | 发布 SDK 接收 subagent/async 变体后,验证真实通知流,再替换对应边界 |
64
- | acp-sdk-foreign-session | active;draftInterceptor | SDK 能路由不是当前客户端主动打开的子会话;仅支持 draft 类型不足以下线 |
79
+ | acp-sdk-draft-variants | active;processor/updates(取代已删除的 draftInterceptor)/ draftSubagents | 发布 SDK 接收 subagent/async 变体后,验证真实通知流,再替换对应边界 |
80
+ | acp-sdk-foreign-session | active;processor/updates(取代已删除的 draftInterceptor) | processor 对子会话 session/update 原样直通(不经 SDK schema);正式 SDK schema 能校验子会话更新且改为先校验不丢通知后才下线;仅支持 draft 类型不足以下线 |
65
81
  | acp-sdk-capability-fields | active;acpClient / draftSubagents | 无兼容桥 initialize 能保留 subagents,且 AIR vendor 能力协商仍有效 |
66
- | qoder-subagent-origin / qoder-subagent-subwork | active;原生 Qoder 1.1.61 包补丁保留 `_meta.qoder`,适配器归一到 subwork | 无补丁版本的并发过程、终态、load/resume/重连全部通过后,删除包配方与边界;core/App 不加 Qoder 分支 |
82
+ | pi-subagent-subwork | active;pi-acp 0.0.34 包补丁(链在 pi-steering 之后,catalog 钉 0.0.34)把 pi-subagents 派发/完成归一为 `subagent_spawned`/`subagent_state_update` | 无补丁 pi-acp 能发 T2 变体并透传 custom message 后移除;axom-cli/服务端零改动,processor 原生消费 |
83
+ | qoder-subagent-origin / qoder-subagent-subwork | active;原生 Qoder 1.1.71 包补丁保留 `_meta.qoder`,适配器归一到 subwork | 无补丁版本的并发过程、终态、load/resume/重连全部通过后,删除包配方与边界;core/App 不加 Qoder 分支 |
67
84
  | acp-registry-etarget | active;版本不存在时单次回退 | 上游版本可安装时自然不触发;删除整个策略要检查 registry 契约 |
68
85
  | claude-steering | draft;steering/ 的两份旧版 diff | 0.76/0.77 草案未经完整运行时验证,不自动应用;重新测 E1–E3 |
69
86
  | codex-steering | draft;steering/ 的旧版 diff | 1.10/1.11 草案不适用于当前新版,不自动应用;重新测 E1–E3 |
@@ -71,6 +88,9 @@ catalog 是 Axom 对官方 ACP registry 的本地扩展层,随 CLI 发布:
71
88
  每条完整路径、版本哈希、上游链接、删除条件与验证项见 [台账](../manifest.json)。
72
89
  steering diff 原样来自历史 notes;维护本目录文件及台账哈希,历史归档保留实验上下文。
73
90
 
91
+ catalog 各 agent 的上游版本核查、补丁必要性判定与钉版本升级,按
92
+ [`nightly-task.md`](nightly-task.md) 的周期任务执行(AI 可直接照步骤运行)。
93
+
74
94
  ## 验证
75
95
 
76
96
  ```bash
@@ -94,6 +114,8 @@ Pi 的发布包运行时探针(使用真实 adapter 类,不启动模型、
94
114
  ```bash
95
115
  node axom-patch/cli/probes/pi-steering.mjs /path/to/pristine/pi-acp --expect-unsupported
96
116
  node axom-patch/cli/probes/pi-steering.mjs /path/to/patched/pi-acp
117
+ node axom-patch/cli/probes/pi-subagent-subwork.mjs /path/to/pristine/pi-acp --expect-no-subagents
118
+ node axom-patch/cli/probes/pi-subagent-subwork.mjs /path/to/patched/pi-acp
97
119
  ```
98
120
 
99
121
  仅已核验的原生 Pi 0.87.1 声明 `supported: true, timings: ["next"]`。其他原生版本不声明能力。
@@ -0,0 +1,27 @@
1
+ # Account quota extension
2
+
3
+ Axom extension, not an upstream ACP standard. ACP extension methods use `_axom/account/rateLimits/read` and `_axom/account/rateLimits/updated`. Capability: `agentCapabilities._meta["axom/accountRateLimits"] = {version: 2, read: true, updated: true}`. A read's result and an update's params have the same snapshot schema.
4
+
5
+ Only the popover footer sends a read. Initialization, authentication, reconnects, session changes, turns, opening UI and reset deadlines do not query quota. Native Codex and Claude quota events are normalized without fetching. Command, Kimi and Antigravity publish updates from explicit reads; they do not synthesize a polling loop. UI SSE streams only database state.
6
+
7
+ Initialization publishes an identity-only unknown observation of adapter support. It does not establish quota display eligibility. Reconnects replay each live session's cached observation with its original timestamp; they never fetch usage. A pre-upgrade hello roster cannot hide a stored capability observation. Version-gated installed native extensions also advertise support in the roster without spawning or querying the provider.
8
+
9
+ Snapshots contain `schemaVersion`, `provider`, `billingMode`, `availability`, `observedAt`, nullable `plan`, independent `limits`, `balances` and bounded `_meta`. Optional `accountKey` must be opaque, never a credential. Modes are `subscription`, `non_subscription`, `unknown`; missing authentication or temporarily unavailable quota is unknown, not proof of API billing. `ExternalAgentAccountDto.supportsQuotaDisplay` is the shared eligibility rule for UI and server refresh: adapter support plus confirmed subscription billing, or an independent quota checker with unknown billing. The server identifies the existing Command Code, ZCode, Kimi and Antigravity readers through `quotaCheckerAvailable`, preserving their first manual discovery action. Non-subscription accounts expose no quota entry, and rejected refreshes do not claim a cooldown or contact the adapter. Codex/Claude authentication notifications establish billing without fetching quota; their unknown accounts remain hidden. Switching authentication publishes a complete identity snapshot to clear previous windows. Their processor also refuses quota reads until subscription authentication is confirmed. Future server-configured quota checkers should use this shared capability rather than add provider-specific UI branches.
10
+
11
+ Windows use integer seconds (monthly calendar periods use `period: "month"`), percentage consumed in 0..100, ISO UTC timestamps and nullable unknown values. Absolute credits or monetary values are decimal strings; money uses currency and exponent. Scoped model quotas remain detailed rows, not duplicated general rings. When only model quotas exist, one ring represents the most used model, with an explicit popover explanation. Vendor fields remain under `_meta._codex`, `_claude`, `_kimi`, `_commandcode`, `_antigravity`. Latest snapshots persist per workstation/configured entry; delayed older updates are ignored. A server transaction admits one refresh per 60 seconds, including failures. Errors preserve the prior snapshot.
12
+
13
+ Verified managed versions: Codex ACP 2.2.2; Claude ACP 0.89.1 (SDK experimental usage control); Command Code 1.74.3; native Kimi Code 2.1.1; ZCode ACP 0.69.3. New versions do not inherit unverified patches. Regenerate the npm recipes with `generate.py --codex <pristine-package-dir> --claude <pristine-package-dir> --commandcode <pristine-package-dir> [--zcode <pristine-package-dir>]`; update inventory artifact hashes after changes. Never patch a user's global installation.
14
+
15
+ Kimi's native shim starts a temporary loopback web process only during an explicit read and calls the SDK-backed local usage route; process cleanup is bounded. Its native provider catalog identifies actual API credentials before querying managed quota. The common launcher selects this shim from `runtime.json.native_extensions`. Claude uses an existing session's query when possible, otherwise a nonpersistent SDK control session. Neither submits a model inference request for quota. Native payload availability can vary with login/provider/version; failed reads are surfaced without overwriting previous observations.
16
+
17
+ `updateMode: "snapshot"` means a complete read or observation; `"delta"` updates only its supplied windows/balances. The server merges deltas transactionally by id (or equivalent window/scope for legacy Claude ids). Null partial measurements retain known values in the same reset period, with one-second timestamp rounding tolerance; a changed period becomes unknown rather than borrowing old utilization. Complete reads remove windows that are absent. Legacy update notifications without an explicit mode are treated as deltas by the CLI. Capability v2 lets explicit reads avoid old v1 session processes. No automatic query was added.
18
+
19
+ Command's month window uses its native `getPlanInfo` allowance and remaining monthly credits; billing `currentPeriodEnd` supplies reset time. Unknown plans have unknown percentage rather than an inferred cap. Purchased/free credits stay separate balances. Claude emits only the affected event window; the weekly snapshot is first acquired through the footer's manual refresh and survives subsequent 5-hour events.
20
+
21
+ ZCode's read resolves the individual coding-plan key through the adapter's own credential chain (`codingPlanRequestAuthFor`: legacy config.json first, then the encrypted `~/.zcode/v2/credentials.json` store #320 — the adapter's own `/quota` and `account/usage_stats` still read config.json only and stay broken on fresh 3.14.x installs; fixed upstream when they adopt the store path). The GLM quota endpoint returns plan `level` plus `limits[]` rows: `TOKENS_LIMIT number=5` is the 5h prompt window (percentage-only, no counters), `number=7` a weekly window when a plan has one, `TIME_LIMIT`/`MCP_LIMIT` the built-in tool allowance with absolute call counts and per-model detail (kept in `_meta._zcode`). The endpoint offers no monthly window on any measured plan; nothing is inferred. The read throws when no entitled individual plan is servable (team keys are deliberately not served by the adapter), which surfaces as a standard refresh failure.
22
+
23
+ Antigravity's native extension is limited to official ACP **1.3.0**. Its bundled discovery document defines `ModelDetails.quotaInfo.remainingFraction` (remaining, not consumed), `resetTime`, and `UserTier.availableCredits`. The sidecar reproduces the native consumer `loadCodeAssist` endpoint selection and reads `fetchAvailableModels` only on an explicit read. It does not perform onboarding, inference, alter settings, or write refreshed OAuth credentials. It uses the same `GEMINI_HOME/antigravity-acp` settings and consumer token store (`gemini` / `antigravity-acp` Keychain on macOS, secure-file fallback; `AGY_ACP_FORCE_FILE_STORAGE` respected). Explicit successful ACP authentication overrides the file's auth type. API key, Agent Platform, enterprise OAuth and gateway are outside this consumer subscription integration.
24
+
25
+ The quota schema supplies no window duration or monthly allowance: keep `windowSeconds: null`, list each model and its reset, and keep AI credits as a balance. Do not infer shared buckets from identical percentages/reset times. Extensions allowlist quota/tier fields and exclude project, tokens and account identity. Official ACP `usage_update` is context-token usage, not an account quota notification; it is never converted into subscription usage. No spontaneous account quota notification is exposed in 1.3.0. Protocol/normalization tests pass against the bundled native schema; a live read on the development machine could not be measured because its Keychain credential read timed out. A locked/inaccessible store surfaces the standard refresh failure and preserves the last snapshot.
26
+
27
+ Antigravity quota capability v3 fixes interactive Keychain authorization: the first explicit read permits up to 120 seconds for the OS dialog, within longer quota-specific CLI/server/client deadlines. A successful credential read and refreshed access token stay only in the reader process's memory; subsequent manual reads do not re-open Keychain. Explicit authentication/logout invalidates that cache, and failed credential loads are retryable. This matters because the native ACP credential writer deletes and recreates the Keychain item, potentially invalidating previous per-item “Always Allow” grants. No ACL, partition list or native credential file is modified by Axom. Older v2 model sessions are bypassed for quota reads without interruption. When necessary, a separate control connection is retained across manual reads, closed on authentication changes and CLI shutdown; it performs no background quota queries. Tests simulate delayed authorization and repeated reads without invoking the real password dialog; live OS authorization remains a user-side verification step.
@@ -0,0 +1,47 @@
1
+ /** Version-gated native ACP sidecar. Never queries on initialize, auth, turns or timers. */
2
+ import { spawn } from 'node:child_process';
3
+ import { createInterface } from 'node:readline';
4
+ import { READ, UPDATED, CAPABILITY } from './normalize.mjs';
5
+ import { readAntigravity, clearAntigravityCredentials } from './antigravity-read.mjs';
6
+ const [binary, ...args] = process.argv.slice(2);
7
+ const child = spawn(binary, args, { stdio: ['pipe', 'pipe', 'inherit'], env: process.env });
8
+ const write = value => process.stdout.write(JSON.stringify(value) + '\n');
9
+ let initializeId, authType, reading = false;
10
+ const authRequests = new Map();
11
+ createInterface({ input: child.stdout }).on('line', line => {
12
+ let message; try { message = JSON.parse(line); } catch { process.stdout.write(line + '\n'); return; }
13
+ if (message.id === initializeId && message.result) {
14
+ message.result.agentCapabilities ??= {};
15
+ message.result.agentCapabilities._meta ??= {};
16
+ message.result.agentCapabilities._meta['axom/accountRateLimits'] = { ...CAPABILITY, version: 5, provider: 'antigravity' };
17
+ }
18
+ if (authRequests.has(message.id)) {
19
+ if (message.result && !message.error) { authType = authRequests.get(message.id); clearAntigravityCredentials(); }
20
+ authRequests.delete(message.id);
21
+ }
22
+ write(message);
23
+ });
24
+ createInterface({ input: process.stdin }).on('line', line => {
25
+ let message; try { message = JSON.parse(line); } catch { child.stdin.write(line + '\n'); return; }
26
+ if (message.method === 'initialize') initializeId = message.id;
27
+ if (message.method === 'authenticate' && message.id !== undefined) authRequests.set(message.id, message.params?.methodId);
28
+ if (message.method === 'logout') { authType = undefined; clearAntigravityCredentials(); }
29
+ if (message.method === READ && message.id !== undefined) {
30
+ if (reading) { write({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: 'A usage refresh is already in progress' } }); return; }
31
+ reading = true;
32
+ void readAntigravity({ authType }).then(result => {
33
+ write({ jsonrpc: '2.0', id: message.id, result });
34
+ write({ jsonrpc: '2.0', method: UPDATED, params: result });
35
+ }, error => {
36
+ if (/^Antigravity (oauth|account|quota) request failed \(HTTP \d{3}\)$/.test(error?.message ?? '')) process.stderr.write(`${error.message}\n`);
37
+ write({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: 'Failed to fetch usage' } });
38
+ })
39
+ .finally(() => { reading = false; });
40
+ return;
41
+ }
42
+ child.stdin.write(line + '\n');
43
+ });
44
+ process.stdin.on('end', () => child.stdin.end());
45
+ for (const signal of ['SIGTERM', 'SIGINT']) process.on(signal, () => child.kill(signal));
46
+ child.on('error', () => { process.exitCode = 1; });
47
+ child.on('exit', code => process.exit(code ?? 1));
@@ -0,0 +1,90 @@
1
+ /** All credential access and network I/O occur only inside an explicit quota read. */
2
+ import { readFile } from 'node:fs/promises';
3
+ import { execFile } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ import { homedir, type, machine } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { antigravity } from './antigravity.mjs';
8
+ const execute = promisify(execFile);
9
+ const PROD = 'https://cloudcode-pa.googleapis.com';
10
+ const DAILY = 'https://daily-cloudcode-pa.googleapis.com';
11
+ // Official ACP 1.3.0 useragent.py: CCPA uses the antigravity token to resolve
12
+ // client-specific entitlements. A generic fetch User-Agent resolves the wrong tier.
13
+ const USER_AGENT = `antigravity/acp/1.3.0 (aidev_client; os_type=${process.platform === 'win32' ? 'windows' : type().toLowerCase()}; arch=${machine().toLowerCase()}; host_path=axom/unknown; proxy_client=antigravity/sdk)`;
14
+ async function jsonFile(path) {
15
+ try { return JSON.parse(await readFile(path, 'utf8')); }
16
+ catch (error) { if (error.code === 'ENOENT') return null; throw new Error('Failed to read Antigravity settings'); }
17
+ }
18
+ export async function loadAntigravityCredentials(root, env, { platform = process.platform, executeKeychain = execute } = {}) {
19
+ if (platform === 'darwin' && !['1', 'true'].includes(env.AGY_ACP_FORCE_FILE_STORAGE)) {
20
+ try {
21
+ // This is an interactive authorization, not a three-second background probe.
22
+ const { stdout } = await executeKeychain('/usr/bin/security', ['find-generic-password', '-s', 'gemini', '-a', 'antigravity-acp', '-w'], { timeout: 120000, maxBuffer: 65536 });
23
+ return JSON.parse(stdout);
24
+ } catch { /* Official store's secure-file fallback; never prompt for login here. */ }
25
+ }
26
+ return jsonFile(join(root, 'acp_token.json'));
27
+ }
28
+ /** Process-local only. A native token rewrite must not make every footer refresh reauthorize. */
29
+ export function createCredentialCache(read) {
30
+ const entries = new Map();
31
+ return {
32
+ clear: () => entries.clear(),
33
+ async load(root, env) {
34
+ const key = `${root}:${env.AGY_ACP_FORCE_FILE_STORAGE ?? ''}`;
35
+ if (!entries.has(key)) {
36
+ const pending = Promise.resolve().then(() => read(root, env));
37
+ entries.set(key, pending);
38
+ void pending.then(value => { if (!value && entries.get(key) === pending) entries.delete(key); }, () => { if (entries.get(key) === pending) entries.delete(key); });
39
+ }
40
+ return entries.get(key);
41
+ },
42
+ };
43
+ }
44
+ const credentialCache = createCredentialCache(loadAntigravityCredentials);
45
+ export const clearAntigravityCredentials = () => credentialCache.clear();
46
+ export async function readAntigravity({ authType, env = process.env, request = fetch, loadCredentials = credentialCache.load } = {}) {
47
+ const root = join(env.GEMINI_HOME?.trim() || join(homedir(), '.gemini'), 'antigravity-acp');
48
+ authType ??= (await jsonFile(join(root, 'settings.json')))?.auth?.type;
49
+ if (authType !== 'oauth-personal') return antigravity({}, authType);
50
+ const saved = await loadCredentials(root, env);
51
+ if (!saved) throw new Error('Antigravity credentials unavailable');
52
+ const signal = AbortSignal.timeout(20000);
53
+ const call = async (url, body, headers = {}) => {
54
+ const response = await request(url, { method: 'POST', headers, body, signal });
55
+ if (!response.ok) {
56
+ if (url === 'https://oauth2.googleapis.com/token' || response.status === 401) credentialCache.clear();
57
+ // No response bodies, project identifiers or credentials in diagnostics.
58
+ const stage = url.endsWith('/token') ? 'oauth' : url.endsWith(':loadCodeAssist') ? 'account' : 'quota';
59
+ throw new Error(`Antigravity ${stage} request failed (HTTP ${response.status})`);
60
+ }
61
+ return response.json();
62
+ };
63
+ let token = saved.token;
64
+ const expiry = Date.parse(saved.expiry ?? '');
65
+ if (!token || !Number.isFinite(expiry) || expiry <= Date.now() + 60000) {
66
+ if (!saved.refresh_token || !saved.client_id || !saved.client_secret) throw new Error('Antigravity credentials expired');
67
+ const refreshed = await call('https://oauth2.googleapis.com/token', new URLSearchParams({
68
+ grant_type: 'refresh_token', refresh_token: saved.refresh_token, client_id: saved.client_id, client_secret: saved.client_secret,
69
+ }), { 'Content-Type': 'application/x-www-form-urlencoded' });
70
+ token = refreshed.access_token;
71
+ if (typeof token === 'string' && token) {
72
+ saved.token = token;
73
+ saved.expiry = typeof refreshed.expires_in === 'number' && Number.isFinite(refreshed.expires_in) && refreshed.expires_in > 0 && refreshed.expires_in <= 86400
74
+ ? new Date(Date.now() + refreshed.expires_in * 1000).toISOString() : undefined;
75
+ }
76
+ // Keep refresh in memory: a quota read must not race the native adapter's credential writes.
77
+ }
78
+ if (typeof token !== 'string' || !token) throw new Error('Antigravity credentials unavailable');
79
+ const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': USER_AGENT };
80
+ const load = await call(`${PROD}/v1internal:loadCodeAssist`, JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }), headers);
81
+ const project = load.cloudaicompanionProject ?? saved.project_id;
82
+ if (typeof project !== 'string' || !project) throw new Error('Antigravity account is not onboarded');
83
+ // Match the official consumer ACP endpoint selection; never perform onboarding or change settings.
84
+ const endpoint = load.paidTier?.usesGcpTos === true ? PROD : DAILY;
85
+ const summary = await call(`${endpoint}/v1internal:retrieveUserQuotaSummary`, JSON.stringify({ project }), headers);
86
+ if (!Array.isArray(summary.groups) && !Array.isArray(summary.buckets)) throw new Error('Invalid Antigravity quota response');
87
+ const result = antigravity({ quotaSummary: summary, currentTier: load.currentTier, paidTier: load.paidTier }, authType);
88
+ if (result.availability === 'unavailable') throw new Error('Antigravity quota unavailable');
89
+ return result;
90
+ }
@@ -0,0 +1,46 @@
1
+ /** Antigravity ACP 1.3.0: schema from its bundled Cloud Code discovery document. */
2
+ import { windowKey } from './normalize.mjs';
3
+ export function antigravity(raw = {}, authType, observedAt = new Date().toISOString()) {
4
+ const billingMode = authType === 'oauth-personal' ? 'subscription'
5
+ : ['gemini-api-key', 'vertex-ai', 'agent-platform', 'oauth-business', 'gateway'].includes(authType) ? 'non_subscription' : 'unknown';
6
+ const tier = raw.paidTier ?? raw.currentTier;
7
+ // Deliberate allowlist: never persist tokens, account email, project or routing metadata.
8
+ const models = Object.fromEntries(Object.entries(raw.models ?? {}).map(([id, m]) => [id, {
9
+ displayName: m?.displayName, quotaInfo: m?.quotaInfo ? { remainingFraction: m.quotaInfo.remainingFraction, resetTime: m.quotaInfo.resetTime } : undefined,
10
+ }]));
11
+ const out = { schemaVersion: 1, updateMode: 'snapshot', provider: 'antigravity', billingMode,
12
+ availability: billingMode === 'subscription' ? 'available' : billingMode === 'unknown' ? 'unknown' : 'not_applicable',
13
+ observedAt, plan: tier?.id ? { id: tier.id, label: tier.name ?? tier.id } : null,
14
+ limits: [], balances: [], _meta: { _antigravity: { authType, models } } };
15
+ if (billingMode !== 'subscription') return out;
16
+ // Use Google's summary buckets, never infer shared pools or periods from model names.
17
+ const summary = raw.quotaSummary ?? {};
18
+ const groups = Array.isArray(summary.groups) ? summary.groups : [];
19
+ const rows = groups.length ? groups.flatMap(g => (g.buckets ?? []).map(bucket => ({ bucket, group: g.displayName })))
20
+ : (summary.buckets ?? []).map(bucket => ({ bucket, group: bucket.bucketId?.startsWith('gemini-') ? 'Gemini Models'
21
+ : bucket.bucketId?.startsWith('3p-') ? 'Claude and GPT models' : bucket.displayName }));
22
+ out._meta._antigravity.quotaSummary = { groups: [] };
23
+ const seen = new Set();
24
+ for (const { bucket: q, group } of rows) {
25
+ if (typeof q.bucketId !== 'string' || !q.bucketId || seen.has(q.bucketId)) continue;
26
+ seen.add(q.bucketId);
27
+ const fraction = q.remainingFraction;
28
+ const valid = typeof fraction === 'number' && Number.isFinite(fraction) && fraction >= 0 && fraction <= 1;
29
+ const usedPercent = valid ? Math.round((1 - fraction) * 10000) / 100 : null;
30
+ const date = typeof q.resetTime === 'string' ? new Date(q.resetTime) : null;
31
+ const windowSeconds = q.window === 'weekly' ? 604800 : q.window === '5h' ? 18000 : null;
32
+ out.limits.push({ id: `antigravity.${q.bucketId}`, label: q.displayName ?? q.bucketId, kind: 'subscription',
33
+ windowSeconds, key: windowKey(windowSeconds), usedPercent, resetsAt: date && Number.isFinite(date.getTime()) ? date.toISOString() : null,
34
+ active: q.disabled !== true,
35
+ status: !valid || q.disabled === true ? 'unknown' : fraction === 0 ? 'blocked' : usedPercent >= 80 ? 'warning' : 'allowed',
36
+ scope: { group: { id: q.bucketId.replace(/-(weekly|5h)$/, ''), display_name: group ?? 'Model quota' } } });
37
+ out._meta._antigravity.quotaSummary.groups.push({ displayName: group, bucketId: q.bucketId,
38
+ window: q.window, remainingFraction: valid ? fraction : null, resetTime: out.limits.at(-1).resetsAt, disabled: q.disabled === true });
39
+ }
40
+ for (const credit of tier?.availableCredits ?? []) {
41
+ const remaining = typeof credit.creditAmount === 'string' && /^\d+$/.test(credit.creditAmount) ? credit.creditAmount : null;
42
+ if (credit.creditType === 'GOOGLE_ONE_AI') out.balances.push({ id: 'antigravity.google-one-ai', label: 'AI credits', remaining, unit: 'credits', unlimited: false });
43
+ }
44
+ if (!out.limits.length && !out.balances.length) out.availability = 'unavailable';
45
+ return out;
46
+ }
@@ -0,0 +1,120 @@
1
+ from pathlib import Path
2
+ import json,hashlib
3
+ import argparse
4
+ parser=argparse.ArgumentParser(description='Regenerate exact-version quota recipes from pristine adapter package directories')
5
+ for name in ('codex','claude','commandcode'): parser.add_argument('--'+name,required=True,type=Path)
6
+ parser.add_argument('--zcode',required=False,type=Path,help='Also regenerate the zcode recipe (zcode-acp-server)')
7
+ options=parser.parse_args()
8
+ root=Path(__file__).resolve().parents[3]
9
+ module=(root/'axom-patch/cli/account-limits/normalize.mjs').read_text().replace('export ','')
10
+ helper='\nconst axomQuota = (() => {\n'+module+'\nreturn {READ,UPDATED,CAPABILITY,codex,codexEvent,claude,claudeEvent,kimi,commandcode,zcode};\n})();\nconst axomQuotaNotify = snapshot => process.stdout.write(JSON.stringify({jsonrpc:"2.0",method:axomQuota.UPDATED,params:snapshot})+"\\n");\n'
11
+ roots={name:getattr(options,name) for name in ('codex','claude','commandcode')}
12
+ if options.zcode: roots['zcode']=options.zcode
13
+ def patch(kind,file,version,pkg,changes,suffix='-account-limits',helper_text=helper,root_dir=None):
14
+ s=((root_dir or roots[kind])/file).read_text(); edits=[]
15
+ # The helper follows imports/shebang safely. Appending declarations executes before asynchronous requests.
16
+ if helper_text is not None:
17
+ changes.append((s[:100], (s.split('\n',1)[0]+'\n'+helper_text+s.split('\n',1)[1][:100-len(s.split('\n',1)[0])-1]) if s.startswith('#!') else helper_text+s[:100]))
18
+ for before,after in changes:
19
+ assert s.count(before)==1,(kind,before[:60],s.count(before))
20
+ n=s.replace(before,after)
21
+ edits.append(dict(file=file,before_sha256=hashlib.sha256(s.encode()).hexdigest(),after_sha256=hashlib.sha256(n.encode()).hexdigest(),before=before,after=after))
22
+ s=n
23
+ recipe=dict(id=kind+suffix,package=pkg,versions=[version],edits=edits)
24
+ (root/f'axom-patch/cli/{kind}{suffix}.json').write_text(json.dumps(recipe,ensure_ascii=False,indent=2)+'\n')
25
+ # No initialization queries: only declare the capability, and forward native events.
26
+ patch('codex','dist/index.js','2.2.2','@agentclientprotocol/codex-acp',[
27
+ ('[AUTH_STATUS_META_KEY]: authStatusCapability()','[AUTH_STATUS_META_KEY]: authStatusCapability(), "axom/accountRateLimits": {...axomQuota.CAPABILITY,provider:"codex",authBillingModes:{account:"subscription",api_key:"non_subscription",external:"non_subscription",gateway:"non_subscription",none:"unknown"}}'),
28
+ ('this.handleRateLimitsUpdated(notification.params);\n return null;', 'this.handleRateLimitsUpdated(notification.params);\n axomQuotaNotify(axomQuota.codexEvent(notification.params, this.sessionState.account?.type ?? "unknown"));\n return null;'),
29
+ ('.onRequest("authentication/status", emptyExtensionParamsParser,', '.onRequest(axomQuota.READ, emptyExtensionParamsParser, async () => { const agent=getAgent(); const account=(await agent.runWithProcessCheck(()=>agent.codexAcpClient.getAccount())).account; if (!account) throw new Error("Not authenticated"); if (account.type !== "chatgpt") return axomQuota.codex({},account.type); const raw=await agent.runWithProcessCheck(()=>agent.codexAcpClient.getRateLimits()); const snapshot=axomQuota.codex(raw,account.type); axomQuotaNotify(snapshot); return snapshot; }).onRequest("authentication/status", emptyExtensionParamsParser,')])
30
+ patch('claude','dist/acp-agent.js','0.90.0','@agentclientprotocol/claude-agent-acp',[
31
+ ('authStatus: authStatusCapability(),','authStatus: authStatusCapability(), "axom/accountRateLimits": {...axomQuota.CAPABILITY,provider:"claude",authBillingModes:{account:"subscription",api_key:"non_subscription",external:"non_subscription",gateway:"non_subscription",none:"unknown"}},'),
32
+ ('case "rate_limit_event": {','case "rate_limit_event": {\n this.axomAccountLimits = axomQuota.claudeEvent(message.rate_limit_info, this.axomAccountLimits);\n axomQuotaNotify(this.axomAccountLimits);'),
33
+ ('.onRequest(methods.agent.session.new,', '.onRequest(axomQuota.READ, {parse: value => value ?? {}}, async ctx => { const session=agent.sessions[ctx.params.sessionId] ?? Object.values(agent.sessions)[0]; const owned=!session?.query; const q=session?.query ?? query({prompt:"/usage",options:{cwd:ctx.params.cwd??process.cwd(),persistSession:false}}); let raw;try{const auth=fromAccountInfo(await q.accountInfo()); if(["api_key","external","gateway"].includes(auth?.kind)){raw={billing_mode:"non_subscription",rate_limits_available:false};}else{raw=await q.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET({skipBehaviors:true});raw.subscription_type??=auth?.account?.plan;raw.billing_mode=auth?.kind==="account"?"subscription":"unknown";}}finally{if(owned)q.close();} agent.axomAccountLimits=axomQuota.claude({subscription_type:raw.subscription_type,rate_limits_available:raw.rate_limits_available,rate_limits:raw.rate_limits,billing_mode:raw.billing_mode}); if(agent.axomAccountLimits.billingMode==="unknown" || agent.axomAccountLimits.availability==="unavailable") throw new Error("Failed to fetch usage"); axomQuotaNotify(agent.axomAccountLimits); return agent.axomAccountLimits; })\n .onRequest(methods.agent.session.new,')])
34
+ patch('commandcode','dist/cli.mjs','1.82.1','command-code',[
35
+ # 1.82.1 renamed the handler map (u/dn -> m/hn) and gave INITIALIZE an explicit arrow body; the
36
+ # read method still goes first in the same map.
37
+ ('const m={[hn.INITIALIZE]:e=>handleInitialize(e),[hn.AUTHENTICATE]','const m={["_axom/account/rateLimits/read"]:async()=>{const raw=await fetchUsageData2(); if(raw.errors.length) throw new Error("Failed to fetch usage"); const snapshot=axomQuota.commandcode({credits:raw.credits,subscription:raw.subscription,orgLimits:raw.whoami?.orgLimits??[],monthly:{limit:raw.subscription?.data?.status==="active"?(getPlanInfo({planId:raw.subscription?.data?.planId??""})?.monthlyCredits??null):null}}); axomQuotaNotify(snapshot); return snapshot;},[hn.INITIALIZE]:e=>handleInitialize(e),[hn.AUTHENTICATE]'),
38
+ ('agentCapabilities:{loadSession:!0,promptCapabilities:', 'agentCapabilities:{_meta:{"axom/accountRateLimits":{...axomQuota.CAPABILITY,provider:"commandcode"}},loadSession:!0,promptCapabilities:')])
39
+ if options.zcode: patch('zcode','dist/index.js','0.69.4','zcode-acp-server',[
40
+ (' .onRequest("account/usage_stats", z.object({}).passthrough(), () => accountUsageStats())',
41
+ ' .onRequest(axomQuota.READ, z.object({}).passthrough(), async () => {\n'
42
+ ' const { buildAccountProviderConfig, codingPlanRequestAuthFor } = await import("./config/account-provider.js");\n'
43
+ ' const { resolveQuotaHost } = await import("./quota/client.js");\n'
44
+ ' let apiKey = null, family = "";\n'
45
+ ' for (const [providerId, provider] of Object.entries(buildAccountProviderConfig()?.providers ?? {})) {\n'
46
+ ' if (!provider?.access?.entitled) continue;\n'
47
+ ' const auth = codingPlanRequestAuthFor(providerId);\n'
48
+ ' if (auth?.apiKey) { apiKey = auth.apiKey; family = providerId; break; }\n'
49
+ ' }\n'
50
+ ' if (!apiKey) throw new Error("No entitled GLM coding plan: sign in once in the ZCode desktop app");\n'
51
+ ' const resp = await fetch(resolveQuotaHost(family.startsWith("account:zai") ? "https://api.z.ai" : "") + "/api/monitor/usage/quota/limit", { headers: { Accept: "application/json, text/plain, */*", Authorization: "Bearer " + apiKey }, signal: AbortSignal.timeout(8000) });\n'
52
+ ' const text = await resp.text();\n'
53
+ ' let json = null; try { json = text ? JSON.parse(text) : null; } catch {}\n'
54
+ ' if (resp.status === 429 || !json || json.success !== true) throw new Error("GLM quota read failed" + (json?.msg ? ": " + json.msg : " (HTTP " + resp.status + ")"));\n'
55
+ ' const snapshot = axomQuota.zcode({ provider: family, level: json.data?.level, limits: json.data?.limits });\n'
56
+ ' axomQuotaNotify(snapshot);\n'
57
+ ' return snapshot;\n'
58
+ ' })\n'
59
+ ' .onRequest("account/usage_stats", z.object({}).passthrough(), () => accountUsageStats())'),
60
+ (' .onRequest("initialize", (ctx) => server.initialize(ctx.params, ctx.client))',
61
+ ' .onRequest("initialize", async (ctx) => { const result = await server.initialize(ctx.params, ctx.client); const caps = result.agentCapabilities ?? {}; result.agentCapabilities = { ...caps, _meta: { ...caps._meta, "axom/accountRateLimits": { ...axomQuota.CAPABILITY, provider: "zcode" } } }; return result; })')])
62
+ models_fn='''function axomFreshInstallModels() {
63
+ const fallback = { providerId: DEFAULT_PROVIDER_ID, providerName: DEFAULT_PROVIDER_NAME, modelId: DEFAULT_MODEL_ID };
64
+ try {
65
+ const out = [];
66
+ for (const [pid, p] of Object.entries(axomAccountProviders()?.providers ?? {})) {
67
+ if (!p?.access?.entitled || !axomPlanAuth(pid)?.apiKey) continue;
68
+ const providerId = configProviderIdFor(pid);
69
+ for (const modelId of p.builtinModelIds ?? []) {
70
+ if (out.some((m) => m.providerId === providerId && m.modelId === modelId)) continue;
71
+ out.push({ providerId, providerName: providerId, modelId });
72
+ }
73
+ }
74
+ if (out.length === 0) return [fallback];
75
+ return out.some((m) => m.providerId === fallback.providerId && m.modelId === fallback.modelId)
76
+ ? [fallback, ...out.filter((m) => !(m.providerId === fallback.providerId && m.modelId === fallback.modelId))]
77
+ : [fallback, ...out];
78
+ }
79
+ catch {
80
+ return [fallback];
81
+ }
82
+ }
83
+ '''
84
+ if options.zcode: patch('zcode-models','dist/config/options.js','0.69.3','zcode-acp-server',[
85
+ ('import { configProviderIdFor } from "./account-provider.js";',
86
+ 'import { buildAccountProviderConfig as axomAccountProviders, codingPlanRequestAuthFor as axomPlanAuth, configProviderIdFor } from "./account-provider.js";'),
87
+ ('/** Read the config.json contents (UTF-8). Throws on read/parse failure. */',
88
+ models_fn+'\n/** Read the config.json contents (UTF-8). Throws on read/parse failure. */'),
89
+ (''' if (out.length === 0 && Object.keys(cfg.provider ?? {}).length === 0) {
90
+ return [
91
+ {
92
+ providerId: DEFAULT_PROVIDER_ID,
93
+ providerName: DEFAULT_PROVIDER_NAME,
94
+ modelId: DEFAULT_MODEL_ID,
95
+ },
96
+ ];
97
+ }''',
98
+ ' if (out.length === 0 && Object.keys(cfg.provider ?? {}).length === 0) {\n return axomFreshInstallModels();\n }'),
99
+ (''' if (out.length > 0)
100
+ return out;
101
+ return [
102
+ {
103
+ providerId: DEFAULT_PROVIDER_ID,
104
+ providerName: DEFAULT_PROVIDER_NAME,
105
+ modelId: DEFAULT_MODEL_ID,
106
+ },
107
+ ];
108
+ }
109
+ }''',
110
+ ' if (out.length > 0)\n return out;\n return axomFreshInstallModels();\n }\n}'),
111
+ ],suffix='',helper_text=None,root_dir=options.zcode)
112
+ runtime=root/'axom-patch/cli/runtime.json'; data=json.loads(runtime.read_text())
113
+ for kind in roots:
114
+ name=kind+'-account-limits.json'
115
+ if name not in data['package_patches']:data['package_patches'].append(name)
116
+ if options.zcode:
117
+ name='zcode-models.json'
118
+ if name not in data['package_patches']:data['package_patches'].append(name)
119
+ runtime.write_text(json.dumps(data,indent=2)+'\n')
120
+ print('Generated exact-version recipes'+(' including zcode' if options.zcode else ''))
@@ -0,0 +1,64 @@
1
+ /** Verified Kimi 2.1.1 ACP extension shim. The web usage route is invoked ONLY for an explicit read. */
2
+ import {spawn} from 'node:child_process';
3
+ import {createInterface} from 'node:readline';
4
+ import {readFile} from 'node:fs/promises';
5
+ import {createServer} from 'node:net';
6
+ import {homedir} from 'node:os';
7
+ import {join} from 'node:path';
8
+ import {READ, UPDATED, CAPABILITY, kimi} from './normalize.mjs';
9
+ const [binary,...args]=process.argv.slice(2);
10
+ const child=spawn(binary,args,{stdio:['pipe','pipe','inherit'],env:process.env});
11
+ const write=value=>process.stdout.write(JSON.stringify(value)+'\n');
12
+ let initializeId; let reading=false; let selectedModel;
13
+ async function usage() {
14
+ if(reading) throw new Error('A usage refresh is already in progress'); reading=true;
15
+ let web;
16
+ try {
17
+ const port=await new Promise((resolve,reject)=>{const server=createServer();server.on('error',reject);server.listen(0,'127.0.0.1',()=>{const port=server.address().port;server.close(()=>resolve(port));});});
18
+ web=spawn(binary,['web','--port',String(port),'--no-open'],{stdio:'ignore',env:process.env});
19
+ let spawnError;web.on('error',error=>{spawnError=error;});
20
+ const controller=new AbortController();const timeout=setTimeout(()=>controller.abort(),20000);
21
+ try {
22
+ for(let attempt=0;attempt<60;attempt++) {
23
+ if(spawnError)throw spawnError;
24
+ if(web.exitCode!==null) throw new Error('The Kimi usage service exited');
25
+ try {
26
+ const token=(await readFile(join(process.env.KIMI_CODE_HOME??join(homedir(),'.kimi-code'),'server.token'),'utf8')).trim();
27
+ const request=async path=>{const response=await fetch(`http://127.0.0.1:${port}/api/v1/${path}`,{headers:{Authorization:`Bearer ${token}`},signal:controller.signal});if(!response.ok)throw new Error('Failed to fetch usage');return response.json();};
28
+ // Native catalog resolves credentials, including environment overrides, without exposing keys.
29
+ const config=(await request('config')).data;
30
+ const providers=(await request('providers')).data?.items??[];
31
+ const model=config?.models?.[selectedModel??config?.default_model];
32
+ const provider=providers.find(p=>p.id===(model?.providerId??model?.provider_id??model?.provider??config?.default_provider));
33
+ if(provider?.has_api_key===true || (provider && provider.id!=='managed:kimi-code'))return {schemaVersion:1,provider:'kimi',billingMode:'non_subscription',availability:'not_applicable',observedAt:new Date().toISOString(),plan:null,limits:[],balances:[],_meta:{_kimi:{credential:'api'}}};
34
+ if(!provider || provider.status!=='connected')throw new Error('Failed to fetch usage');
35
+ const response=await fetch(`http://127.0.0.1:${port}/api/v1/oauth/usage`,{headers:{Authorization:`Bearer ${token}`},signal:controller.signal});
36
+ if(!response.ok) throw new Error('Failed to fetch usage');
37
+ const body=await response.json();
38
+ if(body.data?.kind!=='ok') throw new Error(body.data?.message??'Failed to fetch usage');
39
+ return kimi(body.data.quota);
40
+ } catch(error) {
41
+ if(controller.signal.aborted || (error.message && !['fetch failed'].includes(error.message) && error.code!=='ENOENT')) throw error;
42
+ }
43
+ await new Promise(resolve=>setTimeout(resolve,150));
44
+ }
45
+ throw new Error('The Kimi usage service did not start');
46
+ } finally {clearTimeout(timeout);}
47
+ } finally { reading=false; if(web){web.kill('SIGTERM');setTimeout(()=>{if(web.exitCode===null)web.kill('SIGKILL');},2000).unref();} }
48
+ }
49
+ createInterface({input:child.stdout}).on('line',line=>{
50
+ try {const message=JSON.parse(line);if(message.id===initializeId&&message.result){message.result.agentCapabilities??={};message.result.agentCapabilities._meta??={};message.result.agentCapabilities._meta['axom/accountRateLimits']={...CAPABILITY,provider:'kimi'};write(message);return;}}catch{}
51
+ process.stdout.write(line+'\n');
52
+ });
53
+ createInterface({input:process.stdin}).on('line',line=>{
54
+ let message;try{message=JSON.parse(line);}catch{child.stdin.write(line+'\n');return;}
55
+ if(message.method==='initialize')initializeId=message.id;
56
+ if(message.method==='session/set_config_option' && message.params?.configId==='model')selectedModel=message.params.value;
57
+ if(message.method==='session/set_model')selectedModel=message.params.modelId;
58
+ if(message.method===READ&&message.id!==undefined){void usage().then(result=>{write({jsonrpc:'2.0',id:message.id,result});write({jsonrpc:'2.0',method:UPDATED,params:result});},()=>write({jsonrpc:'2.0',id:message.id,error:{code:-32603,message:'Failed to fetch usage'}}));return;}
59
+ child.stdin.write(line+'\n');
60
+ });
61
+ process.stdin.on('end',()=>child.stdin.end());
62
+ for(const signal of ['SIGTERM','SIGINT'])process.on(signal,()=>child.kill(signal));
63
+ child.on('error',()=>process.exitCode=1);
64
+ child.on('exit',code=>process.exit(code??1));