@platless/axom-cli 0.5.9 → 0.6.36

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 (163) hide show
  1. package/README.md +18 -5
  2. package/dist/axom-patch/cli/README.md +100 -12
  3. package/dist/axom-patch/cli/account-limits/generate.py +15 -12
  4. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
  5. package/dist/axom-patch/cli/claude-account-limits.json +9 -9
  6. package/dist/axom-patch/cli/codex-account-limits.json +11 -11
  7. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  8. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  9. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  10. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  11. package/dist/axom-patch/cli/nightly-task.md +55 -13
  12. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  13. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  14. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
  15. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
  16. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  17. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  18. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  19. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  20. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  21. package/dist/axom-patch/cli/runtime.json +16 -18
  22. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  23. package/dist/axom-patch/cli/zcode-models.json +1 -2
  24. package/dist/axom-patch/cli/zcode-subagent-subwork.json +9 -10
  25. package/dist/cli.js +4 -36
  26. package/dist/commands/knowledge.js +47 -0
  27. package/dist/commands/update.js +65 -3
  28. package/dist/commands/workstation.js +73 -7
  29. package/dist/install/claude/commands.js +13 -13
  30. package/dist/mcp.js +1 -1
  31. package/dist/native/darwin-arm64/axom-quic +0 -0
  32. package/dist/workstation/acp/acpAgents.js +102 -98
  33. package/dist/workstation/acp/acpClient.js +26 -0
  34. package/dist/workstation/acp/builds/install/npm.js +99 -10
  35. package/dist/workstation/acp/initializeResponse.js +6 -1
  36. package/dist/workstation/acp/managedPatches.js +189 -25
  37. package/dist/workstation/acp/managedRuntime.js +314 -0
  38. package/dist/workstation/acp/patchAssets.js +40 -0
  39. package/dist/workstation/acp/processor/checkpoint.js +25 -34
  40. package/dist/workstation/acp/processor/control.js +5 -78
  41. package/dist/workstation/acp/processor/runtime.js +5 -81
  42. package/dist/workstation/acp/processor/session.js +51 -80
  43. package/dist/workstation/acp/processor/state.js +6 -13
  44. package/dist/workstation/acp/processor/step.js +3 -20
  45. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  46. package/dist/workstation/acp/processor/updates.js +131 -105
  47. package/dist/workstation/acp/processorSession.js +8 -172
  48. package/dist/workstation/acp/registry/catalog.js +65 -39
  49. package/dist/workstation/acp/registry/launch.js +16 -1
  50. package/dist/workstation/acp/switching.js +0 -3
  51. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  52. package/dist/workstation/acp/test/keeperWorld.js +28 -0
  53. package/dist/workstation/acp/test/raw/sources.js +16 -130
  54. package/dist/workstation/acp/test/realAdapter.js +1 -47
  55. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  56. package/dist/workstation/agentFlow.js +1 -1
  57. package/dist/workstation/agentSessions.js +38 -143
  58. package/dist/workstation/channels/management.js +95 -11
  59. package/dist/workstation/channels/url.js +1 -1
  60. package/dist/workstation/config.js +16 -4
  61. package/dist/workstation/connection/encryptedChannel.js +4 -1
  62. package/dist/workstation/control/bindingStore.js +200 -0
  63. package/dist/workstation/control/commands.js +124 -0
  64. package/dist/workstation/control/daemonIdentity.js +53 -0
  65. package/dist/workstation/control/enrollment.js +106 -0
  66. package/dist/workstation/control/inspect.js +71 -0
  67. package/dist/workstation/control/locks.js +63 -0
  68. package/dist/workstation/control/protocol.js +40 -0
  69. package/dist/workstation/control/restartGuard.js +46 -0
  70. package/dist/workstation/control/runtimeClient.js +97 -0
  71. package/dist/workstation/control/runtimeHost.js +148 -0
  72. package/dist/workstation/control/serverIdentity.js +35 -0
  73. package/dist/workstation/daemon.js +106 -6
  74. package/dist/workstation/daemonctl.js +77 -14
  75. package/dist/workstation/desktopControl/audio.js +99 -0
  76. package/dist/workstation/desktopControl/capabilities.js +92 -0
  77. package/dist/workstation/desktopControl/captureModes.js +9 -0
  78. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  79. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  80. package/dist/workstation/desktopControl/helperProcess.js +353 -0
  81. package/dist/workstation/desktopControl/lobby.js +129 -0
  82. package/dist/workstation/desktopControl/macProvider.js +155 -0
  83. package/dist/workstation/desktopControl/media.js +331 -0
  84. package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
  85. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  86. package/dist/workstation/desktopControl/overview.js +58 -0
  87. package/dist/workstation/desktopControl/protocol.js +302 -0
  88. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  89. package/dist/workstation/desktopControl/rateController.js +226 -0
  90. package/dist/workstation/desktopControl/supervisor.js +1681 -0
  91. package/dist/workstation/desktopControl/transport.js +27 -0
  92. package/dist/workstation/desktopControl/wallpaper.js +78 -0
  93. package/dist/workstation/files/credentials.js +142 -0
  94. package/dist/workstation/files/text.js +42 -8
  95. package/dist/workstation/keepers/kit/bundle.js +21 -17
  96. package/dist/workstation/keepers/kit/launcher.js +47 -7
  97. package/dist/workstation/keepers/kit/paths.js +2 -1
  98. package/dist/workstation/managedInstall.js +57 -0
  99. package/dist/workstation/protocol/agent.js +2 -15
  100. package/dist/workstation/protocol/management.js +93 -4
  101. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  102. package/dist/workstation/rd/artifact.js +119 -0
  103. package/dist/workstation/rd/control.js +119 -0
  104. package/dist/workstation/rd/display.js +29 -0
  105. package/dist/workstation/rd/provider.js +222 -0
  106. package/dist/workstation/rd/sessions.js +391 -0
  107. package/dist/workstation/rd/state.js +45 -0
  108. package/dist/workstation/rd/update.js +171 -0
  109. package/dist/workstation/rd/upkeep.js +38 -0
  110. package/dist/workstation/register.js +27 -37
  111. package/dist/workstation/service/installer.js +104 -13
  112. package/dist/workstation/service/launchd.js +1 -0
  113. package/dist/workstation/storageMetrics.js +118 -0
  114. package/dist/workstation/systemMetrics.js +44 -92
  115. package/dist/workstation/toolchain.js +94 -0
  116. package/dist/workstation/tools/browser.js +28 -4
  117. package/dist/workstation/update/artifact.js +2 -1
  118. package/dist/workstation/update/globalInstall.js +116 -0
  119. package/dist/workstation/update/npmInstaller.js +49 -23
  120. package/dist/workstation/update/rdRollback.js +145 -0
  121. package/dist/workstation/update/runUpdate.js +3 -2
  122. package/dist/workstation/update/watchdog.js +16790 -334
  123. package/package.json +5 -5
  124. package/skills/axom/SKILL.md +11 -11
  125. package/skills/axom-definition/SKILL.md +3 -3
  126. package/skills/axom-platform/SKILL.md +3 -3
  127. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  128. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  129. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  130. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  131. package/dist/commands/guide.js +0 -38
  132. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  133. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  134. package/dist/workstation/acp/delegationMcp.js +0 -24
  135. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  136. package/dist/workstation/acp/qoderStartup.js +0 -45
  137. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  138. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  139. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  140. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  141. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  142. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  143. package/dist/workstation/qoderAdapter/contract.js +0 -78
  144. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  145. package/dist/workstation/qoderAdapter/history.js +0 -85
  146. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  147. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  148. package/dist/workstation/qoderAdapter/main.js +0 -95
  149. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  150. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  151. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  152. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  153. package/dist/workstation/qoderAdapter/profile.js +0 -93
  154. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  155. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  156. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  157. package/dist/workstation/qoderAdapter/session.js +0 -319
  158. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  159. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  160. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  161. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  162. package/dist/workstation/qoderAdapter/usage.js +0 -37
  163. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @platless/axom-cli
2
2
 
3
- One npm package, one bin: `axom`. An authenticated MCP client for Axomwork (`struct`/`record`/`execute`/`guide`), plus the personal Workstation daemon — registration, encrypted tunnels, self-update, and OS service lifecycle — merged into the same package.
3
+ One npm package, one bin: `axom`. An authenticated MCP client for Axomwork (`struct`/`record`/`execute`/`knowledge`), plus the personal Workstation daemon — registration, encrypted tunnels, self-update, and OS service lifecycle — merged into the same package.
4
4
 
5
5
  ## Install
6
6
 
@@ -18,8 +18,9 @@ curl -s http://<your-axom-server>/download/client/manifest.json
18
18
  ```
19
19
 
20
20
  A plain `-g` install on purpose: it puts `axom` on your `PATH` the way npm puts every other global
21
- binary there. Do **not** add `--prefix` — the CLI's own self-update runs the same `npm install
22
- --global`, and a private prefix leaves the binary somewhere no shell looks.
21
+ binary there. For a manual installation, keep the existing global prefix. Desktop-managed macOS
22
+ installations use `~/.axom/cli` with an explicit Node runtime, installation receipt, terminal wrapper,
23
+ service environment and update/rollback context; do not imitate that by adding a standalone `--prefix` flag.
23
24
 
24
25
  Verify before going further — this is the step that catches a broken install:
25
26
 
@@ -51,9 +52,19 @@ axom execute healthCheck '{}'
51
52
 
52
53
  `axom workstation start` registers this machine (reusing the `axom auth` login) and installs/starts the daemon as an OS service; `stop`/`restart`/`status` control or query it; `install`/`uninstall` manage just the service definition (launchd/systemd) for manual repair.
53
54
 
55
+ On Debian 13 amd64, `axom workstation start --enable-rd` installs native remote desktop support and reuses the user's X11 desktop, or starts a managed Xfce desktop when none exists. `workstation rd status` reports its state; `workstation rd disable` revokes it without removing registration. See [Linux workstation RD](../docs/handbook/linux-workstation-rd.md) for session selection and package delivery.
56
+
57
+ Disk heartbeats discover mounted local data storage independently of configured workspaces.
58
+ Linux partitions and bind mounts are deduplicated by filesystem identity; macOS APFS volumes
59
+ share one entry per physical capacity container. Boot/recovery partitions, memory filesystems,
60
+ network shares and macOS disk images are excluded. Container overlay storage is shown only
61
+ when no persistent local data mount is available. Discovery refreshes every ten minutes;
62
+ capacity is sampled on each heartbeat. Space usage is `1 - available / total`, including
63
+ filesystem reserved space. Other platforms retain workspace-based discovery.
64
+
54
65
  ## Updating
55
66
 
56
- `axom update [--version x.y.z]` upgrades `axom` itself to the release the connected server currently publishes. It only installs the new package and prints a hint — it does not restart the workstation daemon (that would kill every active agent session on the machine), so run `axom workstation restart` afterward to apply it.
67
+ `axom update [--version x.y.z]` upgrades `axom` itself to the release the connected server currently publishes. When a verified daemon is running, the command requests its existing update coordinator, which drains work and preserves keepers during service handoff. When stopped, it only installs the package. A busy or unverifiable daemon is not bypassed by a fresh empty task registry.
57
68
 
58
69
  ## Config
59
70
 
@@ -61,4 +72,6 @@ Non-secret CLI auth state is stored in `~/.axom/config.json` with mode `0600`. T
61
72
 
62
73
  ## Learn more
63
74
 
64
- This README is a front page, not the manual. For the full command reference (arguments, exit codes, examples) and architecture notes, see `docs/guide/modules/cli.md` and `guide/en/agent-integration/axom-cli/` in the Axomwork monorepo this package is built from.
75
+ This README is a front page, not the manual. For the full command reference (arguments, exit codes, examples) and architecture notes, see `docs/guide/modules/cli.md` and `knowledge/manual/en/agent-integration/axom-cli/` in the Axomwork monorepo this package is built from.
76
+
77
+ Desktop on macOS can install and manage this same CLI from personal Workstations. See [Desktop local workstation management](../docs/handbook/desktop-local-workstation.md). The local-management JSON protocol uses stdin for temporary authorization and never persists Desktop refresh tokens.
@@ -27,7 +27,7 @@ roster、Install、Update、启动统一读取同一来源。
27
27
  不生效的包——账号配额、steering、子代理归一静默消失。钉住后 roster 上报的 `version`/`registryVersion`
28
28
  就是生效(钉住)版本,设置页的 Update 比对(`installedVersion != registryVersion`)因此永远对着
29
29
  catalog 的钉版本,**不对着官方 registry 比**;上游新版本要等
30
- [`nightly-task.md`](nightly-task.md) 的周期任务核验移植配方后再 bump,届时
30
+ [`../DAILY_UPGRADE_CHECK.md`](../DAILY_UPGRADE_CHECK.md) 的周期任务核验移植配方后再 bump,届时
31
31
  `reconcileCertifiedBuilds` 把已安装的工作站自动前移。
32
32
  发布包补丁由 `managedPatches.ts` 在 Install/Update 和首次真实启动时应用。
33
33
  历史安装也会补齐,配置显式 command 的用户自有启动程序按其配置执行。
@@ -62,25 +62,27 @@ catalog 是 Axom 对官方 ACP registry 的本地扩展层,随 CLI 发布:
62
62
 
63
63
  | 台账 ID | 状态/应用入口 | AI 核验与下线重点 |
64
64
  |---|---|---|
65
- | qoder-registry-catch-up | active;registry 低于 1.1.71(或版本不可解析)时安装已验证的 `@qoder-ai/qodercli@1.1.71 --acp` | registry 达到 1.1.71 或更高时自动让位;核实上游条目可安装后移除本 override。仅移除安装来源覆盖,不同时删除 subagent/插话的版本限定补丁 |
66
- | kimi-code-distribution | active;catalog.ts 补充条目 `kimi-code`(Kimi Code 2.1.1 官方二进制,六平台 tar.gz + sha256),隐藏已归档的 `kimi` | registry#458 合入后补充条目自动让位、版本由 registry 跟进;验证 Install/Update、复用登录后删除 supplement |
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 |
65
+ | qoder-native-acp | active;Catalog 固定官方 `@qoder-ai/qodercli@1.1.75 --acp`,由 `qoder-native-acp.json` 提供精确版本、资产和原/补丁哈希 | 与其他发布包补丁相同,Install/Update 只按 Catalog 钉版本和配方哈希应用;不读取认证表,也不要求用户提供 Qoder 登录态 |
66
+ | kimi-code-distribution | active;catalog.ts 补充条目 `kimi-code`(Kimi Code 2.3.3 官方二进制,六平台 tar.gz + sha256,2026-10-27 nightly),隐藏已归档的 `kimi` | registry#458 合入后补充条目自动让位、版本由 registry 跟进;验证 Install/Update、复用登录后删除 supplement |
67
+ | command-code-distribution | active;catalog.ts 补充条目 `command-code`(npx `command-code@1.88.0 acp`) | registry 收录后自动让位;确认 registry 版本带 `acp` 子命令后删除 supplement |
68
+ | zcode-acp-distribution | active;catalog.ts 补充条目 `zcode-acp`(npx `zcode-acp-server@0.76.3`),前置依赖 ZCode 本体 | registry#566/#650 合入后自动让位;删除 supplement 时保留 prerequisite |
69
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 时同步存量标题后删除配方;验收含并发、失败/取消、后台迟到、重启重放 |
70
+ | zcode-subagent-subwork | active;zcode-acp-server 0.76.3 四文件九编辑:`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 时同步存量标题后删除配方;验收含并发、失败/取消、后台迟到、重启重放 |
71
71
  | claude-plan-tools | active;启动任务清单工具变量 | 无变量真实 ACP 会话能发布 plan 后下线 |
72
72
  | codex-plan-tools | active;CODEX_CONFIG 的 update_plan 开关 | 无覆盖真实 ACP 会话能发布 plan;保留显式用户配置优先级 |
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 核验后同升 |
73
+ | claude-account-limits | active;配方 0.97.0,catalog 钉 0.97.0 | 上游 adapter 暴露标准化账号配额能力且不自动查询后删除;版本升级走 ../DAILY_UPGRADE_CHECK.md |
74
+ | codex-account-limits | active;配方 2.3.0,catalog 钉 2.3.0 | 同上 |
75
+ | codex-terminal-output | active;2.3.0,链在 codex-account-limits 后;终态保留完整 stdout 与退出码,避免流式输出遗漏首段 | 上游 AIR 命令终态无补丁即可保留完整输出后删除;探针 `probes/codex-terminal-output.mjs` 覆盖流式/非流式与成功/失败 |
76
+ | commandcode-account-limits | active;配方 1.88.0(supplement 同钉) | 同上 |
77
+ | antigravity-account-limits | active;native extension 1.3.0…1.4.3,catalog 钉 1.4.3(真实 .par 经 proxy 实测:透传无损、能力注入成功,最近 2026-10-25) | 官方 adapter 暴露配额读取与通知;上游发新版按 ../DAILY_UPGRADE_CHECK.md 核验后同升 |
77
78
  | 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) |
78
79
  | claude-late-async-terminal | retired;0.84.0/0.85.0 配方已删除 | 修复由上游 #1249 带入预览版并被采用;探针在无补丁预览版 6/6 通过 |
79
80
  | acp-sdk-draft-variants | active;processor/updates(取代已删除的 draftInterceptor)/ draftSubagents | 发布 SDK 接收 subagent/async 变体后,验证真实通知流,再替换对应边界 |
80
81
  | acp-sdk-foreign-session | active;processor/updates(取代已删除的 draftInterceptor) | processor 对子会话 session/update 原样直通(不经 SDK schema);正式 SDK schema 能校验子会话更新且改为先校验不丢通知后才下线;仅支持 draft 类型不足以下线 |
81
82
  | acp-sdk-capability-fields | active;acpClient / draftSubagents | 无兼容桥 initialize 能保留 subagents,且 AIR vendor 能力协商仍有效 |
82
83
  | 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 分支 |
84
+ | kimi-subagent-subwork | active;Kimi native shim 将 Agent / AgentSwarm 转为同一套 T2 子会话事件,子结果进入只读子会话;后台启动回执不算完成,读取该 ACP 会话的原生 task 状态及 output.log 补发迟到终态;load 重放使用确定性 ID,resume 复用已知子会话。不产生原始 Agent 工具卡,不伪造上游没有发送的中间推理/工具流 | 上游原生协商 subagents 时自动让位;无补丁前台/后台/失败/取消/load/resume 实测通过后删除 shim 模块 |
85
+ | qoder-native-acp | active;官方 Qoder 1.1.75 `--acp` 在同一进程内加载兼容 asset,沿用普通 package patch 安装路径 | 上游版本升级时重新核对原文件锚点和行为回归;通过后更新 recipe 的 `versions` 与 Catalog 钉版本 |
84
86
  | acp-registry-etarget | active;版本不存在时单次回退 | 上游版本可安装时自然不触发;删除整个策略要检查 registry 契约 |
85
87
  | claude-steering | draft;steering/ 的两份旧版 diff | 0.76/0.77 草案未经完整运行时验证,不自动应用;重新测 E1–E3 |
86
88
  | codex-steering | draft;steering/ 的旧版 diff | 1.10/1.11 草案不适用于当前新版,不自动应用;重新测 E1–E3 |
@@ -89,7 +91,7 @@ catalog 是 Axom 对官方 ACP registry 的本地扩展层,随 CLI 发布:
89
91
  steering diff 原样来自历史 notes;维护本目录文件及台账哈希,历史归档保留实验上下文。
90
92
 
91
93
  catalog 各 agent 的上游版本核查、补丁必要性判定与钉版本升级,按
92
- [`nightly-task.md`](nightly-task.md) 的周期任务执行(AI 可直接照步骤运行)。
94
+ [`../DAILY_UPGRADE_CHECK.md`](../DAILY_UPGRADE_CHECK.md) 的周期任务执行(AI 可直接照步骤运行).
93
95
 
94
96
  ## 验证
95
97
 
@@ -116,6 +118,7 @@ node axom-patch/cli/probes/pi-steering.mjs /path/to/pristine/pi-acp --expect-uns
116
118
  node axom-patch/cli/probes/pi-steering.mjs /path/to/patched/pi-acp
117
119
  node axom-patch/cli/probes/pi-subagent-subwork.mjs /path/to/pristine/pi-acp --expect-no-subagents
118
120
  node axom-patch/cli/probes/pi-subagent-subwork.mjs /path/to/patched/pi-acp
121
+ node axom-patch/cli/probes/kimi-subagent-subwork.mjs /path/to/managed/kimi
119
122
  ```
120
123
 
121
124
  仅已核验的原生 Pi 0.87.1 声明 `supported: true, timings: ["next"]`。其他原生版本不声明能力。
@@ -130,3 +133,88 @@ node axom-patch/cli/probes/pi-subagent-subwork.mjs /path/to/patched/pi-acp
130
133
  - 新增来源/环境覆盖:修改 runtime.overrides、台账和 catalog 回归,保留 registry/用户显式 env 优先级。
131
134
  - 下线:先按统一证据门槛核验,从 runtime 移除策略或替换 SDK 边界,保留回归;台账标 retired 并记录证据。
132
135
  - CLI 补丁变化必须 bump CLI npm version。相同版本不会触发 Server 自动升级,不能只改 Server 镜像 tag。
136
+
137
+
138
+ ## Qoder native candidate certification (optional authoring evidence)
139
+
140
+ 这套认证脚本只服务于维护者做版本升级和补丁退休判断,不参与 CLI 的安装、启动或版本选择。
141
+ Qoder 的当前运行路径和其他普通发布包补丁一致:Catalog 固定 `1.1.75`,`qoder-native-acp.json`
142
+ 按包版本和文件哈希应用。用户不需要认证表,也不需要先把 Qoder 登录态复制给 Axom;Qoder 自己在真正调用模型时的登录仍由 Qoder 管理。
143
+ 因此缺少某个平台的认证记录不会阻止普通补丁在该平台安装。
144
+ `runtime.json` 的 `install_policies` 为 `@qoder-ai/qodercli` 声明 `ignore_scripts: true`:它的 postinstall 会执行
145
+ `configure-path` 改写用户的 `~/.qoder/entry` 与 PATH,托管安装不能有这种副作用。这条策略与认证无关,只控制 npm lifecycle scripts。
146
+
147
+ `qoder/scripts/certify.mjs --candidate <dir> --evidence <dir> --platform <platform> --arch <arch>`
148
+ consumes the exact generated package plus sibling `.release.json` and `.recipe.json`.
149
+ The command runs a bounded real initialize in a new isolated HOME/cwd. Explicit
150
+ `--auth-home <source>` copies only the auth allowlist and enables the native model/tool
151
+ cases in `src/test/certificationCases.ts`; credentials and isolated HOME contents are
152
+ excluded from evidence artifact manifests. No SDK, relay or extra runtime
153
+ agent is introduced. stderr (including upstream DEP0169) is retained separately from
154
+ unmodified JSON-RPC traffic. Request IDs, native root/child IDs and tool IDs are retained.
155
+ `--collect-only` verifies records in `<evidence>/records/*.json` without model execution.
156
+ The command is directly executable with supported Node 24 from this authoring checkout.
157
+
158
+ Required checks are `initialize`, `ordinary-input`, `permissions`, `images-settings`,
159
+ `parallel-subagents`, `background-reconnect`, `next-append`, `next-button`, `stop`,
160
+ `cached-resume`, `legacy-migration`, `explicit-load`, `replay`, `install-gates`, `platform`.
161
+ Missing, duplicate, unknown, failed, mismatched or synthetic records produce NOT_PASS
162
+ and a nonzero exit. Native tests cannot replace real UI insert-button evidence. The
163
+ background case currently covers completion after the root response followed by process
164
+ reconnect; it does not establish arbitrary mid-flight crash recovery. Migration, replay,
165
+ installer and actual platform/UI evidence remain separate required producers.
166
+
167
+ Digest schema v1 uses canonical JSON: recursively lexicographically sorted plain-object
168
+ keys, array order preserved, JSON scalar encoding, finite numbers only; non-JSON values
169
+ are rejected. `candidateDigest` is SHA256 of canonical
170
+ `{domain:"axom-qoder-candidate-v1",release:<all release fields except certification>,
171
+ suite:{id:<certification.suite>,files:<trusted source manifest>},actualFiles:<map>}`.
172
+ The source manifest is the fixed `suiteFiles` list in `scripts/release.mjs`, read from
173
+ this checkout; `suiteDigest` is SHA256 of canonical suite. `actualFiles` is the complete
174
+ regular main-package map, excluding external `node_modules`, and must exactly equal
175
+ `release.files`. Symlinks and unsafe paths refuse. New runtime fields are automatically
176
+ bound; this includes `historyVersions`, `bin.args`, `runtimeTools`, and `patchDigest`.
177
+ The latter is exactly SHA256(JSON.stringify(orderedRecipeObjects)), matching
178
+ `managedPatches.patchDigest`, including asset records. No alternate recipe hash is used.
179
+
180
+ Each evidence record is `{schema:1,name,status,kind,releaseId,candidateDigest,suiteDigest,
181
+ platform,arch,node:{version,sha256},runtime:{dependencies,tools},artifacts:[{path,sha256}],
182
+ detail?:<JSON>}`. `kind` is `real` or `test`. Artifact paths are relative to the evidence
183
+ root. Node identity is the actual executing binary, not a supplied label. Dependencies
184
+ record resolved direct/optional package versions and package.json hashes; tools record
185
+ exact package/name/version, path, platform/arch, SHA256 and executability. Required
186
+ native tool declarations must be authored before certification. This does not claim a
187
+ complete third-party byte closure: npm integrity installation remains the dependency
188
+ trust boundary. The verifier recomputes candidate, suite, Node, platform, dependencies,
189
+ tools and artifact bytes before and after native work. A changed source manifest or any
190
+ runtime-relevant release field invalidates old evidence. The synthetic reducer fixtures
191
+ may report `schemaValid:true`, but always remain NOT_PASS for release authorization.
192
+
193
+ Every invocation creates a fresh directory and writes result files exclusively.
194
+ `result.sha256` hashes the exact UTF-8 `result.json` bytes (canonical JSON plus newline).
195
+ The result contains records and bindings but no digest of itself. Final release
196
+ `certification.checks/platforms` may therefore be assembled after verified real PASS
197
+ without self-reference: only certification is excluded from the candidate projection,
198
+ and its suite identity remains separately bound. Promotion must rerun `--collect-only`
199
+ on the final bytes, use the verified result digest for its platform and exact record
200
+ bytes/digests for checks, and preserve each platform/Node evidence relationship. Never
201
+ reuse another platform's PASS by editing a label or copy PASS across regenerated bytes.
202
+
203
+ Next evidence observes the original tool's real start artifact and in-progress stream,
204
+ then appends next input and checks original completion plus appended-input files on the
205
+ same root and single ACP prompt. A test-only native root engine wrapper counts actual
206
+ `submitMessage`/`abort` calls; its transformed entry hash and original hash are recorded.
207
+ An uninstrumented final-byte rerun is mandatory. Stop is tested separately, including
208
+ queued-next removal and cancellation while a permission request is pending. Runtime
209
+ instrumentation is never added to the generated release package.
210
+
211
+
212
+ Ordinary-tool acceptance requires distinct original Bash, Read and Grep tool IDs, an
213
+ exact-ID successful terminal update for each, and the unique artifact marker in actual
214
+ Read/Grep result text. A successful Bash file alone cannot hide a missing/failed helper.
215
+ Stop uses a unique queued nonce and a neutral followup. After native writer/process
216
+ closure, the harness copies the full root transcript bytes, verifies root/cwd/version,
217
+ requires the followup's original userMessageId, and rejects any consumed queued nonce.
218
+ Both next runs provide a positive native `queued_command`/`source_uuid` history control.
219
+ These transcript snapshots are hashed artifacts; failed/partial/forced-kill snapshots
220
+ cannot establish removal. Known upstream DEP0169 remains visible in stderr.
@@ -23,21 +23,24 @@ def patch(kind,file,version,pkg,changes,suffix='-account-limits',helper_text=hel
23
23
  recipe=dict(id=kind+suffix,package=pkg,versions=[version],edits=edits)
24
24
  (root/f'axom-patch/cli/{kind}{suffix}.json').write_text(json.dumps(recipe,ensure_ascii=False,indent=2)+'\n')
25
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"}}'),
26
+ patch('codex','dist/index.js','2.3.0','@agentclientprotocol/codex-acp',[
27
+ # 2.3.0 repeats the authStatus line in a second capabilities block; extend through the
28
+ # initialize-only follower (authMethods:) to stay unique.
29
+ ('[AUTH_STATUS_META_KEY]: authStatusCapability()\n }\n },\n authMethods:','[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"}}\n }\n },\n authMethods:'),
28
30
  ('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
31
  ('.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',[
32
+ patch('claude','dist/acp-agent.js','0.97.0','@agentclientprotocol/claude-agent-acp',[
31
33
  ('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
34
  ('case "rate_limit_event": {','case "rate_limit_event": {\n this.axomAccountLimits = axomQuota.claudeEvent(message.rate_limit_info, this.axomAccountLimits);\n axomQuotaNotify(this.axomAccountLimits);'),
33
35
  ('.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]'),
36
+ patch('commandcode','dist/cli.mjs','1.88.0','command-code',[
37
+ # 1.82.1 renamed the handler map (u/dn -> m/hn) and gave INITIALIZE an explicit arrow body; 1.84.0
38
+ # the enum renames every release (hn->vn->bn). The read method still goes first in the same map.
39
+ ('const m={[bn.INITIALIZE]:e=>handleInitialize(e),[bn.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;},[bn.INITIALIZE]:e=>handleInitialize(e),[bn.AUTHENTICATE]'),
38
40
  ('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
+ if options.zcode: patch('zcode','dist/index.js','0.76.3','zcode-acp-server',[
42
+ # 0.72.0 moved the legacy usage_stats body to handlers/account.js as accountUsageStatsViaHub.
43
+ (' .onRequest("account/usage_stats", z.object({}).passthrough(), () => accountUsageStatsViaHub())',
41
44
  ' .onRequest(axomQuota.READ, z.object({}).passthrough(), async () => {\n'
42
45
  ' const { buildAccountProviderConfig, codingPlanRequestAuthFor } = await import("./config/account-provider.js");\n'
43
46
  ' const { resolveQuotaHost } = await import("./quota/client.js");\n'
@@ -55,8 +58,8 @@ if options.zcode: patch('zcode','dist/index.js','0.69.4','zcode-acp-server',[
55
58
  ' const snapshot = axomQuota.zcode({ provider: family, level: json.data?.level, limits: json.data?.limits });\n'
56
59
  ' axomQuotaNotify(snapshot);\n'
57
60
  ' return snapshot;\n'
58
- ' })\n'
59
- ' .onRequest("account/usage_stats", z.object({}).passthrough(), () => accountUsageStats())'),
61
+ ' })\n'
62
+ ' .onRequest("account/usage_stats", z.object({}).passthrough(), () => accountUsageStatsViaHub())'),
60
63
  (' .onRequest("initialize", (ctx) => server.initialize(ctx.params, ctx.client))',
61
64
  ' .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
65
  models_fn='''function axomFreshInstallModels() {
@@ -81,7 +84,7 @@ models_fn='''function axomFreshInstallModels() {
81
84
  }
82
85
  }
83
86
  '''
84
- if options.zcode: patch('zcode-models','dist/config/options.js','0.69.3','zcode-acp-server',[
87
+ if options.zcode: patch('zcode-models','dist/config/options.js','0.76.3','zcode-acp-server',[
85
88
  ('import { configProviderIdFor } from "./account-provider.js";',
86
89
  'import { buildAccountProviderConfig as axomAccountProviders, codingPlanRequestAuthFor as axomPlanAuth, configProviderIdFor } from "./account-provider.js";'),
87
90
  ('/** Read the config.json contents (UTF-8). Throws on read/parse failure. */',
@@ -6,10 +6,15 @@ import {createServer} from 'node:net';
6
6
  import {homedir} from 'node:os';
7
7
  import {join} from 'node:path';
8
8
  import {READ, UPDATED, CAPABILITY, kimi} from './normalize.mjs';
9
+ import {KimiSubagents} from '../kimi-subagents.mjs';
10
+ import {quotaErrorDetail, quotaRpcError} from '../kimi-quota.mjs';
9
11
  const [binary,...args]=process.argv.slice(2);
10
- const child=spawn(binary,args,{stdio:['pipe','pipe','inherit'],env:process.env});
12
+ const child=spawn(binary,args,{stdio:['pipe','pipe','pipe'],env:process.env});
11
13
  const write=value=>process.stdout.write(JSON.stringify(value)+'\n');
12
- let initializeId; let reading=false; let selectedModel;
14
+ const subagents=new KimiSubagents(write);
15
+ const subagentPoll=setInterval(()=>{void subagents.poll().catch(()=>{});},250);
16
+ subagentPoll.unref();
17
+ let initializeId; let reading=false; let selectedModel; let pendingPrompt; let quotaDetail;
13
18
  async function usage() {
14
19
  if(reading) throw new Error('A usage refresh is already in progress'); reading=true;
15
20
  let web;
@@ -47,12 +52,24 @@ async function usage() {
47
52
  } finally { reading=false; if(web){web.kill('SIGTERM');setTimeout(()=>{if(web.exitCode===null)web.kill('SIGKILL');},2000).unref();} }
48
53
  }
49
54
  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{}
55
+ let message;try{message=JSON.parse(line);}catch{process.stdout.write(line+'\n');return;}
56
+ if(subagents.fromAgent(message))return;
57
+ if(pendingPrompt?.id===message.id){
58
+ const prompt=pendingPrompt; pendingPrompt=undefined;
59
+ if(message.result?.stopReason==='end_turn' && quotaDetail){
60
+ write({jsonrpc:'2.0',id:prompt.id,error:quotaRpcError(quotaDetail)}); quotaDetail=undefined; return;
61
+ }
62
+ quotaDetail=undefined;
63
+ }
64
+ 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;}
51
65
  process.stdout.write(line+'\n');
52
66
  });
67
+ createInterface({input:child.stderr}).on('line',line=>{const detail=quotaErrorDetail(line);if(detail)quotaDetail=detail;process.stderr.write(line+'\n');});
53
68
  createInterface({input:process.stdin}).on('line',line=>{
54
69
  let message;try{message=JSON.parse(line);}catch{child.stdin.write(line+'\n');return;}
70
+ subagents.fromClient(message);
55
71
  if(message.method==='initialize')initializeId=message.id;
72
+ if(message.method==='session/prompt' && message.id!==undefined){pendingPrompt={id:message.id,sessionId:message.params?.sessionId};quotaDetail=undefined;}
56
73
  if(message.method==='session/set_config_option' && message.params?.configId==='model')selectedModel=message.params.value;
57
74
  if(message.method==='session/set_model')selectedModel=message.params.modelId;
58
75
  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;}
@@ -2,34 +2,34 @@
2
2
  "id": "claude-account-limits",
3
3
  "package": "@agentclientprotocol/claude-agent-acp",
4
4
  "versions": [
5
- "0.90.0"
5
+ "0.97.0"
6
6
  ],
7
7
  "edits": [
8
8
  {
9
9
  "file": "dist/acp-agent.js",
10
- "before_sha256": "e3bbdc3399a2d702839d6e350b5b798fbe6e61d9ef916d38ccc1bc3abab15e3e",
11
- "after_sha256": "9f1ffd0c70ef4354532a4393c134046dafbfedd80f331bbc91f4219d48928ea9",
10
+ "before_sha256": "c569a914b2b9c75127193139a69bca3ca4ae6b2ebd6728de0330a16689fdc268",
11
+ "after_sha256": "7535a559b2f54913e76c9fd70799f470454a74efe4a5d92054edfe98c1242527",
12
12
  "before": "authStatus: authStatusCapability(),",
13
13
  "after": "authStatus: authStatusCapability(), \"axom/accountRateLimits\": {...axomQuota.CAPABILITY,provider:\"claude\",authBillingModes:{account:\"subscription\",api_key:\"non_subscription\",external:\"non_subscription\",gateway:\"non_subscription\",none:\"unknown\"}},"
14
14
  },
15
15
  {
16
16
  "file": "dist/acp-agent.js",
17
- "before_sha256": "9f1ffd0c70ef4354532a4393c134046dafbfedd80f331bbc91f4219d48928ea9",
18
- "after_sha256": "2a10073f12b1648a2b7d6debc092d7352ede7c2cc827c4654da25dac8cf9df18",
17
+ "before_sha256": "7535a559b2f54913e76c9fd70799f470454a74efe4a5d92054edfe98c1242527",
18
+ "after_sha256": "703b56abeeafed87febf6f3464fceb0b23730f23caf02033618a27f48f8ce301",
19
19
  "before": "case \"rate_limit_event\": {",
20
20
  "after": "case \"rate_limit_event\": {\n this.axomAccountLimits = axomQuota.claudeEvent(message.rate_limit_info, this.axomAccountLimits);\n axomQuotaNotify(this.axomAccountLimits);"
21
21
  },
22
22
  {
23
23
  "file": "dist/acp-agent.js",
24
- "before_sha256": "2a10073f12b1648a2b7d6debc092d7352ede7c2cc827c4654da25dac8cf9df18",
25
- "after_sha256": "ca788a13276c07958b8ec4eee9be5debc030534d879ce58994319adcfeb8123e",
24
+ "before_sha256": "703b56abeeafed87febf6f3464fceb0b23730f23caf02033618a27f48f8ce301",
25
+ "after_sha256": "82bcbad9894a665bcc7e31591d28abd8b6cd980a8f65ad87a131a36a1cc23462",
26
26
  "before": ".onRequest(methods.agent.session.new,",
27
27
  "after": ".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,"
28
28
  },
29
29
  {
30
30
  "file": "dist/acp-agent.js",
31
- "before_sha256": "ca788a13276c07958b8ec4eee9be5debc030534d879ce58994319adcfeb8123e",
32
- "after_sha256": "3221003e3efc74af15498c162fb0e1c62267a1de12476c85e3ad95a865fb1ff6",
31
+ "before_sha256": "82bcbad9894a665bcc7e31591d28abd8b6cd980a8f65ad87a131a36a1cc23462",
32
+ "after_sha256": "efd325571a7c7cd0e9caaa7e143ec5ba32e2eec998ebb359dc4f5627c399de7f",
33
33
  "before": "import { agent as acpAgent, CreateElicitationResponse, methods, ndJsonStream, RequestError, } from \"",
34
34
  "after": "\nconst axomQuota = (() => {\n/** Account quota normalization. No I/O: passive events must never trigger a query. */\nconst READ = '_axom/account/rateLimits/read';\nconst UPDATED = '_axom/account/rateLimits/updated';\nconst CAPABILITY = { version: 2, read: true, updated: true };\nconst percent = v => typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : null;\nconst finite = v => { if (v == null || typeof v === 'boolean') return null; const n = typeof v === 'number' ? v : Number(v); return Number.isFinite(n) ? n : null; };\nconst time = (v, milliseconds = false) => {\n if (v == null || v === 0) return null;\n const d = new Date(typeof v === 'number' ? v * (milliseconds ? 1 : 1000) : v);\n return Number.isFinite(d.getTime()) ? d.toISOString() : null;\n};\nconst status = v => ({ allowed: 'allowed', allowed_warning: 'warning', rejected: 'blocked', normal: 'allowed' }[v] ?? 'unknown');\n/** Canonical window key rides the snapshot; the UI owns the key → display-label translation.\n * Windows without a canonical key keep their vendor content untouched. */\nconst windowKey = (seconds, period) => period === 'month' ? 'monthly' : seconds === 18000 ? 'five_hour' : seconds === 604800 ? 'weekly' : null;\nconst base = (provider, mode, raw, observedAt) => ({ schemaVersion: 1, updateMode: 'snapshot', provider, billingMode: mode, availability: mode === 'subscription' ? 'available' : mode === 'unknown' ? 'unknown' : 'not_applicable', observedAt: observedAt ?? new Date().toISOString(), plan: null, limits: [], balances: [], _meta: { ['_' + provider]: structuredClone(raw) } });\nfunction codex(raw, authType = 'chatgpt', observedAt) {\n const out = base('codex', ['chatgpt', 'chatgptAuthTokens'].includes(authType) ? 'subscription' : ['apiKey','apikey','amazonBedrock'].includes(authType) ? 'non_subscription' : 'unknown', raw, observedAt);\n if (out.billingMode !== 'subscription') return out;\n const buckets = Object.entries(raw.rateLimitsByLimitId ?? {});\n if (!buckets.length && raw.rateLimits) buckets.push([raw.rateLimits.limitId ?? 'codex', raw.rateLimits]);\n for (const [id, bucket] of buckets) {\n if (!bucket) continue;\n if (bucket.planType) out.plan = { id: bucket.planType, label: bucket.planType };\n for (const name of ['primary', 'secondary']) {\n const w = bucket[name]; if (!w) continue;\n const seconds = w.windowDurationMins == null ? null : w.windowDurationMins * 60;\n out.limits.push({ id: `${id}.${name}`, label: bucket.limitName ?? id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.usedPercent), resetsAt: time(w.resetsAt), status: bucket.rateLimitReachedType ? 'blocked' : 'unknown', scope: { bucket: id } });\n }\n if (bucket.credits) out.balances.push({ id: `${id}.credits`, label: 'Credits', remaining: bucket.credits.balance ?? null, unit: 'credits', unlimited: bucket.credits.unlimited === true });\n }\n // Account IDs must not escape via provider extensions.\n delete out._meta._codex.accountId;\n return out;\n}\n/** App-server notifications describe changed buckets, not the complete account read. */\nfunction codexEvent(raw, authType, observedAt) {\n return { ...codex(raw, authType, observedAt), updateMode: 'delta' };\n}\nfunction claude(raw, observedAt) {\n const mode = raw.billing_mode ?? (raw.subscription_type || raw.rate_limits_available === true ? 'subscription' : 'unknown');\n const out = base('claude', mode, raw, observedAt);\n out.plan = raw.subscription_type ? { id: raw.subscription_type, label: raw.subscription_type } : null;\n if (mode === 'subscription' && !raw.rate_limits_available) out.availability = 'unavailable';\n const limits = raw.rate_limits ?? {};\n const rows = Array.isArray(limits.limits) ? limits.limits : [];\n if (rows.length) {\n rows.forEach((w, i) => {\n const seconds = w.group === 'session' ? 18000 : w.group === 'weekly' ? 604800 : null;\n out.limits.push({ id: `claude.${w.kind}.${i}`, label: w.scope?.model?.display_name ?? w.kind, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.percent), resetsAt: time(w.resets_at), status: status(w.severity), active: w.is_active ?? null, scope: w.scope ?? null });\n });\n } else for (const [id, seconds] of [['five_hour', 18000], ['seven_day', 604800], ['seven_day_opus',604800], ['seven_day_sonnet',604800]]) {\n const w = limits[id]; if (!w) continue;\n out.limits.push({ id: `claude.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.utilization), resetsAt: time(w.resets_at), status: 'unknown', scope: id.endsWith('opus') || id.endsWith('sonnet') ? { model: id.split('_').at(-1) } : null });\n }\n const spend=limits.spend;\n if(spend?.enabled && spend.used && spend.limit) out.limits.push({id:'claude.extra_spend',label:'Extra usage',kind:'spend',windowSeconds:null,usedPercent:percent(spend.percent),resetsAt:null,status:status(spend.severity),used:String(spend.used.amount_minor),limit:String(spend.limit.amount_minor),unit:'money',currency:spend.used.currency,exponent:spend.used.exponent});\n if(spend?.balance?.amount_minor != null) out.balances.push({id:'claude.extra_balance',label:'Extra usage balance',remaining:String(spend.balance.amount_minor),unit:'money',currency:spend.balance.currency,exponent:spend.balance.exponent});\n return out;\n}\nfunction claudeEvent(raw, previous = null, observedAt) {\n const out = previous ? structuredClone(previous) : base('claude',raw.rateLimitType?'subscription':'unknown',{},observedAt);\n out.updateMode = 'delta';\n out.balances = [];\n out.observedAt = observedAt ?? new Date().toISOString();\n // Other sessions may have refreshed the account since this process's cached read.\n // Send only facts from this event; the server owns the merged account observation.\n out.plan = null;\n out._meta = { _claude: { rateLimitEvent: raw } };\n const seconds = raw.rateLimitType === 'five_hour' ? 18000 : raw.rateLimitType?.startsWith('seven_day') ? 604800 : null;\n if (seconds !== null) {\n const id = `claude.${raw.rateLimitType}`;\n const scoped = !['five_hour','seven_day'].includes(raw.rateLimitType);\n const prior=out.limits.find(w => w.windowSeconds===seconds && JSON.stringify(w.scope??null)===JSON.stringify(scoped ? {model:raw.rateLimitType.split('_').at(-1)}:null));\n const row = { id: prior?.id ?? id, label: raw.rateLimitType, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: raw.utilization == null ? null : percent(raw.utilization * 100), resetsAt: time(raw.resetsAt), status: status(raw.status), scope: scoped ? { model: raw.rateLimitType.split('_').at(-1) } : null };\n // Event utilization is a ratio, unlike get_usage's percentage. Match the existing window without duplicating it.\n out.limits = [row];\n } else out.limits = [];\n return out;\n}\nfunction kimi(raw, observedAt) {\n const quota = raw.quota ?? raw;\n const out = base('kimi','subscription',quota,observedAt);\n for (const [id, seconds] of [['limit5h',18000],['limit7d',604800],['monthTotal',null]]) {\n const w = quota.usages?.[id]; if (!w) continue;\n out.limits.push({ id: `kimi.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, period: id === 'monthTotal' ? 'month' : null, key: windowKey(seconds, id === 'monthTotal' ? 'month' : null), usedPercent: percent(w.usedRatio * 100), resetsAt: time(w.resetAt), status: 'unknown' });\n }\n const wallet = quota.extraUsage;\n if (wallet) out.balances.push({ id:'kimi.extra',label:'Extra usage',remaining:String(wallet.balanceCents),unit:'money',currency:wallet.currency,exponent:2 });\n return out;\n}\n/** GLM quota `percentage` is the USED share (measured: 5% for 58/1000); counters win when present.\n * Counter-derived shares are rounded to 2 decimals — 100-94.2 keeps fp noise otherwise. */\nconst glmPercent = limit => {\n const used = finite(limit.currentValue), remaining = finite(limit.remaining), total = finite(limit.usage);\n const cap = remaining !== null && used !== null ? remaining + used : total;\n const round2 = v => v == null ? null : Math.round(v * 100) / 100;\n if (cap !== null && cap > 0 && remaining !== null && remaining >= 0 && remaining <= cap) return round2(100 - percent(remaining / cap * 100));\n if (cap !== null && cap > 0 && used !== null && used >= 0 && used <= cap) return round2(percent(used / cap * 100));\n return percent(limit.percentage);\n};\nfunction zcode(raw, observedAt) {\n // Raw: the GLM quota endpoint's data block — { level, limits: [{ type, number, usage,\n // currentValue, remaining, percentage, nextResetTime(epoch ms), usageDetails }] }. Which windows\n // exist is plan-dependent (measured pro plan: 5h prompts + a tool/MCP allowance; no weekly row —\n // a `number: 7` window maps when a plan returns one, and nothing is invented when it doesn't).\n const limits = Array.isArray(raw?.limits) ? raw.limits : [];\n const level = typeof raw?.level === 'string' && raw.level ? raw.level : null;\n const out = base('zcode', limits.length ? 'subscription' : 'unknown', raw, observedAt);\n out.plan = level ? { id: level, label: level } : null;\n let tools = 0;\n limits.forEach((limit, i) => {\n if (!limit || typeof limit !== 'object') return;\n const resetsAt = time(limit.nextResetTime, true);\n if (limit.type === 'TOKENS_LIMIT') {\n // GLM window convention: number 5 → the 5-hour prompt window, 7 → weekly.\n // Ids mirror the adapter's own /quota keys (token_5h / token_week).\n const seconds = limit.number === 5 ? 18000 : limit.number === 7 ? 604800 : null;\n const label = limit.number === 5 ? '5h' : limit.number === 7 ? 'Week' : `token ${limit.number ?? i}`;\n const id = limit.number === 5 ? 'zcode.token_5h' : limit.number === 7 ? 'zcode.token_week' : `zcode.token_${limit.number ?? i}`;\n out.limits.push({ id, label, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: glmPercent(limit), resetsAt, status: 'unknown' });\n } else if (limit.type === 'TIME_LIMIT' || limit.type === 'MCP_LIMIT') {\n // Built-in tool/MCP allowance: absolute counters (unit 'calls'); per-model detail stays in _meta.\n const used = finite(limit.currentValue), total = finite(limit.usage), remaining = finite(limit.remaining);\n out.limits.push({ id: tools ? `zcode.mcp.${i}` : 'zcode.mcp', label: 'MCP', kind: 'subscription', windowSeconds: null, usedPercent: glmPercent(limit), ...(used !== null && total !== null ? { used: String(used), limit: String(total), unit: 'calls' } : {}), resetsAt, status: remaining === 0 ? 'blocked' : used !== null || limit.percentage != null ? 'allowed' : 'unknown' });\n tools++;\n }\n });\n return out;\n}\nfunction commandcode(raw, observedAt) {\n const subscription = raw.subscription?.data ?? raw.subscription;\n const windows = raw.credits?.windowLimits ?? raw.windowLimits;\n const subscribed = ['active','trialing','past_due'].includes(subscription?.status) || windows?.limited === true;\n const mode = subscribed ? 'subscription' : subscription || windows ? 'non_subscription' : 'unknown';\n const out = base('commandcode',mode,raw,observedAt);\n out.plan = subscription?.planId ? { id:subscription.planId,label:subscription.planId } : null;\n for (const [id, seconds] of [['fiveHour',18000],['weekly',604800]]) {\n const w=windows?.[id]; if (!w) continue;\n out.limits.push({ id:`commandcode.${id}`,label:id,kind:'subscription',windowSeconds:seconds,key:windowKey(seconds),usedPercent:w.cap > 0 ? percent(w.used / w.cap * 100) : null,used:String(w.used),limit:String(w.cap),unit:'credits',resetsAt:time(w.resetAt,true),status:w.exceeded === true ? 'blocked' : 'allowed' });\n }\n const credits=raw.credits?.credits ?? raw.credits;\n if (subscribed && credits?.monthlyCredits != null) {\n const cap=raw.monthly?.limit;\n const known=typeof cap==='number' && Number.isFinite(cap) && cap>0;\n const used=known ? Math.max(0,cap-credits.monthlyCredits) : null;\n out.limits.push({id:'commandcode.monthly',label:'Monthly',kind:'subscription',windowSeconds:null,period:'month',key:windowKey(null,'month'),usedPercent:known?percent(used/cap*100):null,resetsAt:time(subscription?.currentPeriodEnd,true),status:'unknown',...(known?{used:String(used),limit:String(cap),unit:'credits'}:{})});\n }\n for (const [id,label] of [['monthlyCredits','Monthly credits'],['purchasedCredits','Purchased credits'],['freeCredits','Free credits']]) if (credits?.[id] != null) out.balances.push({ id:`commandcode.${id}`,label,remaining:String(credits[id]),unit:'credits' });\n return out;\n}\n\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\");\nimport { agent as acpAgent, CreateElicitationResponse, methods, ndJsonStream, RequestError, } from \""
35
35
  }
@@ -2,34 +2,34 @@
2
2
  "id": "codex-account-limits",
3
3
  "package": "@agentclientprotocol/codex-acp",
4
4
  "versions": [
5
- "2.2.2"
5
+ "2.3.0"
6
6
  ],
7
7
  "edits": [
8
8
  {
9
9
  "file": "dist/index.js",
10
- "before_sha256": "6b1bb4e7e9caaf4aeca1042ae4aec2dc2433b0b0e4b1614b38a4d41699902aaa",
11
- "after_sha256": "0181983c3a3526d7464b52b3a4ee5680968fb6ba9053878dbb41a3e23e074ff3",
12
- "before": "[AUTH_STATUS_META_KEY]: authStatusCapability()",
13
- "after": "[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\"}}"
10
+ "before_sha256": "d584cb7df74847661c3cf69d34de6ad3a24f6bc69787faeaff49a1fd9a3d8870",
11
+ "after_sha256": "ca35466484445d86e84d7b4d5ffd84789544a4f4a973d3082bbb55d6c43715a2",
12
+ "before": "[AUTH_STATUS_META_KEY]: authStatusCapability()\n }\n },\n authMethods:",
13
+ "after": "[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\"}}\n }\n },\n authMethods:"
14
14
  },
15
15
  {
16
16
  "file": "dist/index.js",
17
- "before_sha256": "0181983c3a3526d7464b52b3a4ee5680968fb6ba9053878dbb41a3e23e074ff3",
18
- "after_sha256": "27204fec9d83e4b898b8a3d9b66ecd51f1d6606f7835635012d690874b7f4d16",
17
+ "before_sha256": "ca35466484445d86e84d7b4d5ffd84789544a4f4a973d3082bbb55d6c43715a2",
18
+ "after_sha256": "9fa475ad64993e9c55a8a9443185f90774bbc5bf19e1c85d1398aadd629edd88",
19
19
  "before": "this.handleRateLimitsUpdated(notification.params);\n return null;",
20
20
  "after": "this.handleRateLimitsUpdated(notification.params);\n axomQuotaNotify(axomQuota.codexEvent(notification.params, this.sessionState.account?.type ?? \"unknown\"));\n return null;"
21
21
  },
22
22
  {
23
23
  "file": "dist/index.js",
24
- "before_sha256": "27204fec9d83e4b898b8a3d9b66ecd51f1d6606f7835635012d690874b7f4d16",
25
- "after_sha256": "baec90f7269d2f33412f9769dfcede164ad6d1e500d1898df81718bb88451268",
24
+ "before_sha256": "9fa475ad64993e9c55a8a9443185f90774bbc5bf19e1c85d1398aadd629edd88",
25
+ "after_sha256": "43175904fd64f9b56ff6d53ee423c4512e05ededa48805c942ba0f8854427ebb",
26
26
  "before": ".onRequest(\"authentication/status\", emptyExtensionParamsParser,",
27
27
  "after": ".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,"
28
28
  },
29
29
  {
30
30
  "file": "dist/index.js",
31
- "before_sha256": "baec90f7269d2f33412f9769dfcede164ad6d1e500d1898df81718bb88451268",
32
- "after_sha256": "157347843376715825331915c7edf4f7bd8d0ced92f1e54cbafea4e16d6155fc",
31
+ "before_sha256": "43175904fd64f9b56ff6d53ee423c4512e05ededa48805c942ba0f8854427ebb",
32
+ "after_sha256": "ee0276757b18277f5ee827fa08f12df52cb7266d33b5d82d90e538c47efced45",
33
33
  "before": "#!/usr/bin/env node\nimport { createRequire as __createRequire } from 'module'; const require = __cre",
34
34
  "after": "#!/usr/bin/env node\n\nconst axomQuota = (() => {\n/** Account quota normalization. No I/O: passive events must never trigger a query. */\nconst READ = '_axom/account/rateLimits/read';\nconst UPDATED = '_axom/account/rateLimits/updated';\nconst CAPABILITY = { version: 2, read: true, updated: true };\nconst percent = v => typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : null;\nconst finite = v => { if (v == null || typeof v === 'boolean') return null; const n = typeof v === 'number' ? v : Number(v); return Number.isFinite(n) ? n : null; };\nconst time = (v, milliseconds = false) => {\n if (v == null || v === 0) return null;\n const d = new Date(typeof v === 'number' ? v * (milliseconds ? 1 : 1000) : v);\n return Number.isFinite(d.getTime()) ? d.toISOString() : null;\n};\nconst status = v => ({ allowed: 'allowed', allowed_warning: 'warning', rejected: 'blocked', normal: 'allowed' }[v] ?? 'unknown');\n/** Canonical window key rides the snapshot; the UI owns the key → display-label translation.\n * Windows without a canonical key keep their vendor content untouched. */\nconst windowKey = (seconds, period) => period === 'month' ? 'monthly' : seconds === 18000 ? 'five_hour' : seconds === 604800 ? 'weekly' : null;\nconst base = (provider, mode, raw, observedAt) => ({ schemaVersion: 1, updateMode: 'snapshot', provider, billingMode: mode, availability: mode === 'subscription' ? 'available' : mode === 'unknown' ? 'unknown' : 'not_applicable', observedAt: observedAt ?? new Date().toISOString(), plan: null, limits: [], balances: [], _meta: { ['_' + provider]: structuredClone(raw) } });\nfunction codex(raw, authType = 'chatgpt', observedAt) {\n const out = base('codex', ['chatgpt', 'chatgptAuthTokens'].includes(authType) ? 'subscription' : ['apiKey','apikey','amazonBedrock'].includes(authType) ? 'non_subscription' : 'unknown', raw, observedAt);\n if (out.billingMode !== 'subscription') return out;\n const buckets = Object.entries(raw.rateLimitsByLimitId ?? {});\n if (!buckets.length && raw.rateLimits) buckets.push([raw.rateLimits.limitId ?? 'codex', raw.rateLimits]);\n for (const [id, bucket] of buckets) {\n if (!bucket) continue;\n if (bucket.planType) out.plan = { id: bucket.planType, label: bucket.planType };\n for (const name of ['primary', 'secondary']) {\n const w = bucket[name]; if (!w) continue;\n const seconds = w.windowDurationMins == null ? null : w.windowDurationMins * 60;\n out.limits.push({ id: `${id}.${name}`, label: bucket.limitName ?? id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.usedPercent), resetsAt: time(w.resetsAt), status: bucket.rateLimitReachedType ? 'blocked' : 'unknown', scope: { bucket: id } });\n }\n if (bucket.credits) out.balances.push({ id: `${id}.credits`, label: 'Credits', remaining: bucket.credits.balance ?? null, unit: 'credits', unlimited: bucket.credits.unlimited === true });\n }\n // Account IDs must not escape via provider extensions.\n delete out._meta._codex.accountId;\n return out;\n}\n/** App-server notifications describe changed buckets, not the complete account read. */\nfunction codexEvent(raw, authType, observedAt) {\n return { ...codex(raw, authType, observedAt), updateMode: 'delta' };\n}\nfunction claude(raw, observedAt) {\n const mode = raw.billing_mode ?? (raw.subscription_type || raw.rate_limits_available === true ? 'subscription' : 'unknown');\n const out = base('claude', mode, raw, observedAt);\n out.plan = raw.subscription_type ? { id: raw.subscription_type, label: raw.subscription_type } : null;\n if (mode === 'subscription' && !raw.rate_limits_available) out.availability = 'unavailable';\n const limits = raw.rate_limits ?? {};\n const rows = Array.isArray(limits.limits) ? limits.limits : [];\n if (rows.length) {\n rows.forEach((w, i) => {\n const seconds = w.group === 'session' ? 18000 : w.group === 'weekly' ? 604800 : null;\n out.limits.push({ id: `claude.${w.kind}.${i}`, label: w.scope?.model?.display_name ?? w.kind, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.percent), resetsAt: time(w.resets_at), status: status(w.severity), active: w.is_active ?? null, scope: w.scope ?? null });\n });\n } else for (const [id, seconds] of [['five_hour', 18000], ['seven_day', 604800], ['seven_day_opus',604800], ['seven_day_sonnet',604800]]) {\n const w = limits[id]; if (!w) continue;\n out.limits.push({ id: `claude.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.utilization), resetsAt: time(w.resets_at), status: 'unknown', scope: id.endsWith('opus') || id.endsWith('sonnet') ? { model: id.split('_').at(-1) } : null });\n }\n const spend=limits.spend;\n if(spend?.enabled && spend.used && spend.limit) out.limits.push({id:'claude.extra_spend',label:'Extra usage',kind:'spend',windowSeconds:null,usedPercent:percent(spend.percent),resetsAt:null,status:status(spend.severity),used:String(spend.used.amount_minor),limit:String(spend.limit.amount_minor),unit:'money',currency:spend.used.currency,exponent:spend.used.exponent});\n if(spend?.balance?.amount_minor != null) out.balances.push({id:'claude.extra_balance',label:'Extra usage balance',remaining:String(spend.balance.amount_minor),unit:'money',currency:spend.balance.currency,exponent:spend.balance.exponent});\n return out;\n}\nfunction claudeEvent(raw, previous = null, observedAt) {\n const out = previous ? structuredClone(previous) : base('claude',raw.rateLimitType?'subscription':'unknown',{},observedAt);\n out.updateMode = 'delta';\n out.balances = [];\n out.observedAt = observedAt ?? new Date().toISOString();\n // Other sessions may have refreshed the account since this process's cached read.\n // Send only facts from this event; the server owns the merged account observation.\n out.plan = null;\n out._meta = { _claude: { rateLimitEvent: raw } };\n const seconds = raw.rateLimitType === 'five_hour' ? 18000 : raw.rateLimitType?.startsWith('seven_day') ? 604800 : null;\n if (seconds !== null) {\n const id = `claude.${raw.rateLimitType}`;\n const scoped = !['five_hour','seven_day'].includes(raw.rateLimitType);\n const prior=out.limits.find(w => w.windowSeconds===seconds && JSON.stringify(w.scope??null)===JSON.stringify(scoped ? {model:raw.rateLimitType.split('_').at(-1)}:null));\n const row = { id: prior?.id ?? id, label: raw.rateLimitType, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: raw.utilization == null ? null : percent(raw.utilization * 100), resetsAt: time(raw.resetsAt), status: status(raw.status), scope: scoped ? { model: raw.rateLimitType.split('_').at(-1) } : null };\n // Event utilization is a ratio, unlike get_usage's percentage. Match the existing window without duplicating it.\n out.limits = [row];\n } else out.limits = [];\n return out;\n}\nfunction kimi(raw, observedAt) {\n const quota = raw.quota ?? raw;\n const out = base('kimi','subscription',quota,observedAt);\n for (const [id, seconds] of [['limit5h',18000],['limit7d',604800],['monthTotal',null]]) {\n const w = quota.usages?.[id]; if (!w) continue;\n out.limits.push({ id: `kimi.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, period: id === 'monthTotal' ? 'month' : null, key: windowKey(seconds, id === 'monthTotal' ? 'month' : null), usedPercent: percent(w.usedRatio * 100), resetsAt: time(w.resetAt), status: 'unknown' });\n }\n const wallet = quota.extraUsage;\n if (wallet) out.balances.push({ id:'kimi.extra',label:'Extra usage',remaining:String(wallet.balanceCents),unit:'money',currency:wallet.currency,exponent:2 });\n return out;\n}\n/** GLM quota `percentage` is the USED share (measured: 5% for 58/1000); counters win when present.\n * Counter-derived shares are rounded to 2 decimals — 100-94.2 keeps fp noise otherwise. */\nconst glmPercent = limit => {\n const used = finite(limit.currentValue), remaining = finite(limit.remaining), total = finite(limit.usage);\n const cap = remaining !== null && used !== null ? remaining + used : total;\n const round2 = v => v == null ? null : Math.round(v * 100) / 100;\n if (cap !== null && cap > 0 && remaining !== null && remaining >= 0 && remaining <= cap) return round2(100 - percent(remaining / cap * 100));\n if (cap !== null && cap > 0 && used !== null && used >= 0 && used <= cap) return round2(percent(used / cap * 100));\n return percent(limit.percentage);\n};\nfunction zcode(raw, observedAt) {\n // Raw: the GLM quota endpoint's data block — { level, limits: [{ type, number, usage,\n // currentValue, remaining, percentage, nextResetTime(epoch ms), usageDetails }] }. Which windows\n // exist is plan-dependent (measured pro plan: 5h prompts + a tool/MCP allowance; no weekly row —\n // a `number: 7` window maps when a plan returns one, and nothing is invented when it doesn't).\n const limits = Array.isArray(raw?.limits) ? raw.limits : [];\n const level = typeof raw?.level === 'string' && raw.level ? raw.level : null;\n const out = base('zcode', limits.length ? 'subscription' : 'unknown', raw, observedAt);\n out.plan = level ? { id: level, label: level } : null;\n let tools = 0;\n limits.forEach((limit, i) => {\n if (!limit || typeof limit !== 'object') return;\n const resetsAt = time(limit.nextResetTime, true);\n if (limit.type === 'TOKENS_LIMIT') {\n // GLM window convention: number 5 → the 5-hour prompt window, 7 → weekly.\n // Ids mirror the adapter's own /quota keys (token_5h / token_week).\n const seconds = limit.number === 5 ? 18000 : limit.number === 7 ? 604800 : null;\n const label = limit.number === 5 ? '5h' : limit.number === 7 ? 'Week' : `token ${limit.number ?? i}`;\n const id = limit.number === 5 ? 'zcode.token_5h' : limit.number === 7 ? 'zcode.token_week' : `zcode.token_${limit.number ?? i}`;\n out.limits.push({ id, label, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: glmPercent(limit), resetsAt, status: 'unknown' });\n } else if (limit.type === 'TIME_LIMIT' || limit.type === 'MCP_LIMIT') {\n // Built-in tool/MCP allowance: absolute counters (unit 'calls'); per-model detail stays in _meta.\n const used = finite(limit.currentValue), total = finite(limit.usage), remaining = finite(limit.remaining);\n out.limits.push({ id: tools ? `zcode.mcp.${i}` : 'zcode.mcp', label: 'MCP', kind: 'subscription', windowSeconds: null, usedPercent: glmPercent(limit), ...(used !== null && total !== null ? { used: String(used), limit: String(total), unit: 'calls' } : {}), resetsAt, status: remaining === 0 ? 'blocked' : used !== null || limit.percentage != null ? 'allowed' : 'unknown' });\n tools++;\n }\n });\n return out;\n}\nfunction commandcode(raw, observedAt) {\n const subscription = raw.subscription?.data ?? raw.subscription;\n const windows = raw.credits?.windowLimits ?? raw.windowLimits;\n const subscribed = ['active','trialing','past_due'].includes(subscription?.status) || windows?.limited === true;\n const mode = subscribed ? 'subscription' : subscription || windows ? 'non_subscription' : 'unknown';\n const out = base('commandcode',mode,raw,observedAt);\n out.plan = subscription?.planId ? { id:subscription.planId,label:subscription.planId } : null;\n for (const [id, seconds] of [['fiveHour',18000],['weekly',604800]]) {\n const w=windows?.[id]; if (!w) continue;\n out.limits.push({ id:`commandcode.${id}`,label:id,kind:'subscription',windowSeconds:seconds,key:windowKey(seconds),usedPercent:w.cap > 0 ? percent(w.used / w.cap * 100) : null,used:String(w.used),limit:String(w.cap),unit:'credits',resetsAt:time(w.resetAt,true),status:w.exceeded === true ? 'blocked' : 'allowed' });\n }\n const credits=raw.credits?.credits ?? raw.credits;\n if (subscribed && credits?.monthlyCredits != null) {\n const cap=raw.monthly?.limit;\n const known=typeof cap==='number' && Number.isFinite(cap) && cap>0;\n const used=known ? Math.max(0,cap-credits.monthlyCredits) : null;\n out.limits.push({id:'commandcode.monthly',label:'Monthly',kind:'subscription',windowSeconds:null,period:'month',key:windowKey(null,'month'),usedPercent:known?percent(used/cap*100):null,resetsAt:time(subscription?.currentPeriodEnd,true),status:'unknown',...(known?{used:String(used),limit:String(cap),unit:'credits'}:{})});\n }\n for (const [id,label] of [['monthlyCredits','Monthly credits'],['purchasedCredits','Purchased credits'],['freeCredits','Free credits']]) if (credits?.[id] != null) out.balances.push({ id:`commandcode.${id}`,label,remaining:String(credits[id]),unit:'credits' });\n return out;\n}\n\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\");\nimport { createRequire as __createRequire } from 'module'; const require = __cre"
35
35
  }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "codex-terminal-output",
3
+ "package": "@agentclientprotocol/codex-acp",
4
+ "versions": [
5
+ "2.3.0"
6
+ ],
7
+ "edits": [
8
+ {
9
+ "file": "dist/index.js",
10
+ "before_sha256": "d584cb7df74847661c3cf69d34de6ad3a24f6bc69787faeaff49a1fd9a3d8870",
11
+ "after_sha256": "504eba20f2ef3cc58bd1352c5684d65d9da8972287ec19a9726e8a923efae294",
12
+ "before": " terminalExit: { exitCode: item.exitCode }\n };\n}\nfunction singleAction(actions) {",
13
+ "after": " opaqueResult: { formatted_output: output2, exit_code: item.exitCode },\n terminalExit: { exitCode: item.exitCode }\n };\n}\nfunction singleAction(actions) {"
14
+ }
15
+ ]
16
+ }
@@ -2,27 +2,27 @@
2
2
  "id": "commandcode-account-limits",
3
3
  "package": "command-code",
4
4
  "versions": [
5
- "1.82.1"
5
+ "1.88.0"
6
6
  ],
7
7
  "edits": [
8
8
  {
9
9
  "file": "dist/cli.mjs",
10
- "before_sha256": "58e258d3e2cd46df66cb677421527b7d8b9d8ca29f6010d08a20f5e2f8bef485",
11
- "after_sha256": "7e1b01282634c865075ec5bc5758cf432b464ee85c7219713db49760ff7abc84",
12
- "before": "const m={[hn.INITIALIZE]:e=>handleInitialize(e),[hn.AUTHENTICATE]",
13
- "after": "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]"
10
+ "before_sha256": "600725f967cc98f6144066c24e850d934bd4a1f503c3bf82f2bd2584a8757dfd",
11
+ "after_sha256": "68526ea26e9f4452dd9e81eda974835beb1ca796be9c4175190288937179c342",
12
+ "before": "const m={[bn.INITIALIZE]:e=>handleInitialize(e),[bn.AUTHENTICATE]",
13
+ "after": "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;},[bn.INITIALIZE]:e=>handleInitialize(e),[bn.AUTHENTICATE]"
14
14
  },
15
15
  {
16
16
  "file": "dist/cli.mjs",
17
- "before_sha256": "7e1b01282634c865075ec5bc5758cf432b464ee85c7219713db49760ff7abc84",
18
- "after_sha256": "5a90e9497175b92d55d0c32874a4724e941a3f534e83a6f8c168a932dc2e7024",
17
+ "before_sha256": "68526ea26e9f4452dd9e81eda974835beb1ca796be9c4175190288937179c342",
18
+ "after_sha256": "fd5d7f1c4a972f951ffcac2db5ab46360be00b7b73e889154f6a34745ee65307",
19
19
  "before": "agentCapabilities:{loadSession:!0,promptCapabilities:",
20
20
  "after": "agentCapabilities:{_meta:{\"axom/accountRateLimits\":{...axomQuota.CAPABILITY,provider:\"commandcode\"}},loadSession:!0,promptCapabilities:"
21
21
  },
22
22
  {
23
23
  "file": "dist/cli.mjs",
24
- "before_sha256": "5a90e9497175b92d55d0c32874a4724e941a3f534e83a6f8c168a932dc2e7024",
25
- "after_sha256": "66b0418156ebb0edce2ca1e5803665383e6e0f7d69a873f5b37efdb2a5c88534",
24
+ "before_sha256": "fd5d7f1c4a972f951ffcac2db5ab46360be00b7b73e889154f6a34745ee65307",
25
+ "after_sha256": "e068c8efc99dfba125e0a288e4e172469e8dd97b5d8c9c9b3d5b8f5bf88e7cdc",
26
26
  "before": "#!/usr/bin/env node\nimport*as e from\"path\";import n,{join as o,dirname as r,parse as s,resolve as a,",
27
27
  "after": "#!/usr/bin/env node\n\nconst axomQuota = (() => {\n/** Account quota normalization. No I/O: passive events must never trigger a query. */\nconst READ = '_axom/account/rateLimits/read';\nconst UPDATED = '_axom/account/rateLimits/updated';\nconst CAPABILITY = { version: 2, read: true, updated: true };\nconst percent = v => typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : null;\nconst finite = v => { if (v == null || typeof v === 'boolean') return null; const n = typeof v === 'number' ? v : Number(v); return Number.isFinite(n) ? n : null; };\nconst time = (v, milliseconds = false) => {\n if (v == null || v === 0) return null;\n const d = new Date(typeof v === 'number' ? v * (milliseconds ? 1 : 1000) : v);\n return Number.isFinite(d.getTime()) ? d.toISOString() : null;\n};\nconst status = v => ({ allowed: 'allowed', allowed_warning: 'warning', rejected: 'blocked', normal: 'allowed' }[v] ?? 'unknown');\n/** Canonical window key rides the snapshot; the UI owns the key → display-label translation.\n * Windows without a canonical key keep their vendor content untouched. */\nconst windowKey = (seconds, period) => period === 'month' ? 'monthly' : seconds === 18000 ? 'five_hour' : seconds === 604800 ? 'weekly' : null;\nconst base = (provider, mode, raw, observedAt) => ({ schemaVersion: 1, updateMode: 'snapshot', provider, billingMode: mode, availability: mode === 'subscription' ? 'available' : mode === 'unknown' ? 'unknown' : 'not_applicable', observedAt: observedAt ?? new Date().toISOString(), plan: null, limits: [], balances: [], _meta: { ['_' + provider]: structuredClone(raw) } });\nfunction codex(raw, authType = 'chatgpt', observedAt) {\n const out = base('codex', ['chatgpt', 'chatgptAuthTokens'].includes(authType) ? 'subscription' : ['apiKey','apikey','amazonBedrock'].includes(authType) ? 'non_subscription' : 'unknown', raw, observedAt);\n if (out.billingMode !== 'subscription') return out;\n const buckets = Object.entries(raw.rateLimitsByLimitId ?? {});\n if (!buckets.length && raw.rateLimits) buckets.push([raw.rateLimits.limitId ?? 'codex', raw.rateLimits]);\n for (const [id, bucket] of buckets) {\n if (!bucket) continue;\n if (bucket.planType) out.plan = { id: bucket.planType, label: bucket.planType };\n for (const name of ['primary', 'secondary']) {\n const w = bucket[name]; if (!w) continue;\n const seconds = w.windowDurationMins == null ? null : w.windowDurationMins * 60;\n out.limits.push({ id: `${id}.${name}`, label: bucket.limitName ?? id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.usedPercent), resetsAt: time(w.resetsAt), status: bucket.rateLimitReachedType ? 'blocked' : 'unknown', scope: { bucket: id } });\n }\n if (bucket.credits) out.balances.push({ id: `${id}.credits`, label: 'Credits', remaining: bucket.credits.balance ?? null, unit: 'credits', unlimited: bucket.credits.unlimited === true });\n }\n // Account IDs must not escape via provider extensions.\n delete out._meta._codex.accountId;\n return out;\n}\n/** App-server notifications describe changed buckets, not the complete account read. */\nfunction codexEvent(raw, authType, observedAt) {\n return { ...codex(raw, authType, observedAt), updateMode: 'delta' };\n}\nfunction claude(raw, observedAt) {\n const mode = raw.billing_mode ?? (raw.subscription_type || raw.rate_limits_available === true ? 'subscription' : 'unknown');\n const out = base('claude', mode, raw, observedAt);\n out.plan = raw.subscription_type ? { id: raw.subscription_type, label: raw.subscription_type } : null;\n if (mode === 'subscription' && !raw.rate_limits_available) out.availability = 'unavailable';\n const limits = raw.rate_limits ?? {};\n const rows = Array.isArray(limits.limits) ? limits.limits : [];\n if (rows.length) {\n rows.forEach((w, i) => {\n const seconds = w.group === 'session' ? 18000 : w.group === 'weekly' ? 604800 : null;\n out.limits.push({ id: `claude.${w.kind}.${i}`, label: w.scope?.model?.display_name ?? w.kind, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.percent), resetsAt: time(w.resets_at), status: status(w.severity), active: w.is_active ?? null, scope: w.scope ?? null });\n });\n } else for (const [id, seconds] of [['five_hour', 18000], ['seven_day', 604800], ['seven_day_opus',604800], ['seven_day_sonnet',604800]]) {\n const w = limits[id]; if (!w) continue;\n out.limits.push({ id: `claude.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: percent(w.utilization), resetsAt: time(w.resets_at), status: 'unknown', scope: id.endsWith('opus') || id.endsWith('sonnet') ? { model: id.split('_').at(-1) } : null });\n }\n const spend=limits.spend;\n if(spend?.enabled && spend.used && spend.limit) out.limits.push({id:'claude.extra_spend',label:'Extra usage',kind:'spend',windowSeconds:null,usedPercent:percent(spend.percent),resetsAt:null,status:status(spend.severity),used:String(spend.used.amount_minor),limit:String(spend.limit.amount_minor),unit:'money',currency:spend.used.currency,exponent:spend.used.exponent});\n if(spend?.balance?.amount_minor != null) out.balances.push({id:'claude.extra_balance',label:'Extra usage balance',remaining:String(spend.balance.amount_minor),unit:'money',currency:spend.balance.currency,exponent:spend.balance.exponent});\n return out;\n}\nfunction claudeEvent(raw, previous = null, observedAt) {\n const out = previous ? structuredClone(previous) : base('claude',raw.rateLimitType?'subscription':'unknown',{},observedAt);\n out.updateMode = 'delta';\n out.balances = [];\n out.observedAt = observedAt ?? new Date().toISOString();\n // Other sessions may have refreshed the account since this process's cached read.\n // Send only facts from this event; the server owns the merged account observation.\n out.plan = null;\n out._meta = { _claude: { rateLimitEvent: raw } };\n const seconds = raw.rateLimitType === 'five_hour' ? 18000 : raw.rateLimitType?.startsWith('seven_day') ? 604800 : null;\n if (seconds !== null) {\n const id = `claude.${raw.rateLimitType}`;\n const scoped = !['five_hour','seven_day'].includes(raw.rateLimitType);\n const prior=out.limits.find(w => w.windowSeconds===seconds && JSON.stringify(w.scope??null)===JSON.stringify(scoped ? {model:raw.rateLimitType.split('_').at(-1)}:null));\n const row = { id: prior?.id ?? id, label: raw.rateLimitType, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: raw.utilization == null ? null : percent(raw.utilization * 100), resetsAt: time(raw.resetsAt), status: status(raw.status), scope: scoped ? { model: raw.rateLimitType.split('_').at(-1) } : null };\n // Event utilization is a ratio, unlike get_usage's percentage. Match the existing window without duplicating it.\n out.limits = [row];\n } else out.limits = [];\n return out;\n}\nfunction kimi(raw, observedAt) {\n const quota = raw.quota ?? raw;\n const out = base('kimi','subscription',quota,observedAt);\n for (const [id, seconds] of [['limit5h',18000],['limit7d',604800],['monthTotal',null]]) {\n const w = quota.usages?.[id]; if (!w) continue;\n out.limits.push({ id: `kimi.${id}`, label: id, kind: 'subscription', windowSeconds: seconds, period: id === 'monthTotal' ? 'month' : null, key: windowKey(seconds, id === 'monthTotal' ? 'month' : null), usedPercent: percent(w.usedRatio * 100), resetsAt: time(w.resetAt), status: 'unknown' });\n }\n const wallet = quota.extraUsage;\n if (wallet) out.balances.push({ id:'kimi.extra',label:'Extra usage',remaining:String(wallet.balanceCents),unit:'money',currency:wallet.currency,exponent:2 });\n return out;\n}\n/** GLM quota `percentage` is the USED share (measured: 5% for 58/1000); counters win when present.\n * Counter-derived shares are rounded to 2 decimals — 100-94.2 keeps fp noise otherwise. */\nconst glmPercent = limit => {\n const used = finite(limit.currentValue), remaining = finite(limit.remaining), total = finite(limit.usage);\n const cap = remaining !== null && used !== null ? remaining + used : total;\n const round2 = v => v == null ? null : Math.round(v * 100) / 100;\n if (cap !== null && cap > 0 && remaining !== null && remaining >= 0 && remaining <= cap) return round2(100 - percent(remaining / cap * 100));\n if (cap !== null && cap > 0 && used !== null && used >= 0 && used <= cap) return round2(percent(used / cap * 100));\n return percent(limit.percentage);\n};\nfunction zcode(raw, observedAt) {\n // Raw: the GLM quota endpoint's data block — { level, limits: [{ type, number, usage,\n // currentValue, remaining, percentage, nextResetTime(epoch ms), usageDetails }] }. Which windows\n // exist is plan-dependent (measured pro plan: 5h prompts + a tool/MCP allowance; no weekly row —\n // a `number: 7` window maps when a plan returns one, and nothing is invented when it doesn't).\n const limits = Array.isArray(raw?.limits) ? raw.limits : [];\n const level = typeof raw?.level === 'string' && raw.level ? raw.level : null;\n const out = base('zcode', limits.length ? 'subscription' : 'unknown', raw, observedAt);\n out.plan = level ? { id: level, label: level } : null;\n let tools = 0;\n limits.forEach((limit, i) => {\n if (!limit || typeof limit !== 'object') return;\n const resetsAt = time(limit.nextResetTime, true);\n if (limit.type === 'TOKENS_LIMIT') {\n // GLM window convention: number 5 → the 5-hour prompt window, 7 → weekly.\n // Ids mirror the adapter's own /quota keys (token_5h / token_week).\n const seconds = limit.number === 5 ? 18000 : limit.number === 7 ? 604800 : null;\n const label = limit.number === 5 ? '5h' : limit.number === 7 ? 'Week' : `token ${limit.number ?? i}`;\n const id = limit.number === 5 ? 'zcode.token_5h' : limit.number === 7 ? 'zcode.token_week' : `zcode.token_${limit.number ?? i}`;\n out.limits.push({ id, label, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: glmPercent(limit), resetsAt, status: 'unknown' });\n } else if (limit.type === 'TIME_LIMIT' || limit.type === 'MCP_LIMIT') {\n // Built-in tool/MCP allowance: absolute counters (unit 'calls'); per-model detail stays in _meta.\n const used = finite(limit.currentValue), total = finite(limit.usage), remaining = finite(limit.remaining);\n out.limits.push({ id: tools ? `zcode.mcp.${i}` : 'zcode.mcp', label: 'MCP', kind: 'subscription', windowSeconds: null, usedPercent: glmPercent(limit), ...(used !== null && total !== null ? { used: String(used), limit: String(total), unit: 'calls' } : {}), resetsAt, status: remaining === 0 ? 'blocked' : used !== null || limit.percentage != null ? 'allowed' : 'unknown' });\n tools++;\n }\n });\n return out;\n}\nfunction commandcode(raw, observedAt) {\n const subscription = raw.subscription?.data ?? raw.subscription;\n const windows = raw.credits?.windowLimits ?? raw.windowLimits;\n const subscribed = ['active','trialing','past_due'].includes(subscription?.status) || windows?.limited === true;\n const mode = subscribed ? 'subscription' : subscription || windows ? 'non_subscription' : 'unknown';\n const out = base('commandcode',mode,raw,observedAt);\n out.plan = subscription?.planId ? { id:subscription.planId,label:subscription.planId } : null;\n for (const [id, seconds] of [['fiveHour',18000],['weekly',604800]]) {\n const w=windows?.[id]; if (!w) continue;\n out.limits.push({ id:`commandcode.${id}`,label:id,kind:'subscription',windowSeconds:seconds,key:windowKey(seconds),usedPercent:w.cap > 0 ? percent(w.used / w.cap * 100) : null,used:String(w.used),limit:String(w.cap),unit:'credits',resetsAt:time(w.resetAt,true),status:w.exceeded === true ? 'blocked' : 'allowed' });\n }\n const credits=raw.credits?.credits ?? raw.credits;\n if (subscribed && credits?.monthlyCredits != null) {\n const cap=raw.monthly?.limit;\n const known=typeof cap==='number' && Number.isFinite(cap) && cap>0;\n const used=known ? Math.max(0,cap-credits.monthlyCredits) : null;\n out.limits.push({id:'commandcode.monthly',label:'Monthly',kind:'subscription',windowSeconds:null,period:'month',key:windowKey(null,'month'),usedPercent:known?percent(used/cap*100):null,resetsAt:time(subscription?.currentPeriodEnd,true),status:'unknown',...(known?{used:String(used),limit:String(cap),unit:'credits'}:{})});\n }\n for (const [id,label] of [['monthlyCredits','Monthly credits'],['purchasedCredits','Purchased credits'],['freeCredits','Free credits']]) if (credits?.[id] != null) out.balances.push({ id:`commandcode.${id}`,label,remaining:String(credits[id]),unit:'credits' });\n return out;\n}\n\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\");\nimport*as e from\"path\";import n,{join as o,dirname as r,parse as s,resolve as a,"
28
28
  }
@@ -0,0 +1,12 @@
1
+ const quotaPattern = /(?:quota|rate[\s_-]*limit|credit(?:s)?[\s_-]*(?:exhausted|depleted|insufficient)|insufficient[\s_-]*(?:quota|credit|balance)|(?:quota|credit)[\s_-]*exhausted|429)/i;
2
+
3
+ export const quotaErrorDetail = value => {
4
+ const line = typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
5
+ if (!line || !quotaPattern.test(line)) return null;
6
+ return line.length > 300 ? `${line.slice(0, 297)}...` : line;
7
+ };
8
+
9
+ export const quotaRpcError = detail => ({
10
+ code: -32000,
11
+ message: `Kimi quota is exhausted or rate limited.${detail ? ` Provider detail: ${detail}` : ''}`,
12
+ });