@platless/axom-cli 0.1.0 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/authScopes.js +19 -0
- package/dist/axom-patch/cli/README.md +132 -0
- package/dist/axom-patch/cli/account-limits/README.md +27 -0
- package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
- package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
- package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
- package/dist/axom-patch/cli/account-limits/generate.py +120 -0
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +64 -0
- package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
- package/dist/axom-patch/cli/claude-account-limits.json +37 -0
- package/dist/axom-patch/cli/codex-account-limits.json +37 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/nightly-task.md +152 -0
- package/dist/axom-patch/cli/pi-steering.json +37 -0
- package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
- package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +38 -0
- package/dist/axom-patch/cli/probes/pi-steering.mjs +80 -0
- package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +89 -0
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +61 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +82 -0
- package/dist/axom-patch/cli/qoder-foreground-output.md +42 -0
- package/dist/axom-patch/cli/qoder-steering.json +52 -0
- package/dist/axom-patch/cli/qoder-steering.md +30 -0
- package/dist/axom-patch/cli/qoder-subagent-origin.json +73 -0
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +73 -0
- package/dist/axom-patch/cli/runtime.json +63 -0
- package/dist/axom-patch/cli/steering/claude-steering-js.patch +89 -0
- package/dist/axom-patch/cli/steering/claude-steering-types.patch +28 -0
- package/dist/axom-patch/cli/steering/codex-steering.patch +57 -0
- package/dist/axom-patch/cli/steering/pi-steering.js +79 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +31 -0
- package/dist/axom-patch/cli/zcode-models.json +38 -0
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +73 -0
- package/dist/cli.js +36 -1
- package/dist/commands/auth.js +4 -13
- package/dist/commands/execute.js +4 -4
- package/dist/commands/record.js +6 -6
- package/dist/commands/struct.js +4 -4
- package/dist/commands/workstation.js +2 -2
- package/dist/credentials.js +2 -2
- package/dist/install/claude/commands.js +13 -13
- package/dist/keepers/acp/keeper.mjs +3083 -0
- package/dist/keepers/acp/manifest.json +9 -0
- package/dist/keepers/terminal/keeper.mjs +8469 -0
- package/dist/keepers/terminal/manifest.json +9 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
- package/dist/mcp.js +32 -14
- package/dist/runtime-deps/qoder-sdk/package-lock.json +1189 -0
- package/dist/runtime-deps/qoder-sdk/package.json +8 -0
- package/dist/test/keeperKit.js +67 -0
- package/dist/workstation/acp/acpAgents.js +2514 -681
- package/dist/workstation/acp/acpClient.js +95 -318
- package/dist/workstation/acp/activityBusy.js +43 -0
- package/dist/workstation/acp/activityReporting.js +54 -0
- package/dist/workstation/acp/adoption.js +28 -356
- package/dist/workstation/acp/authUrlSniffer.js +36 -0
- package/dist/workstation/acp/binaryInstall.js +100 -27
- package/dist/workstation/acp/builds/gc.js +245 -0
- package/dist/workstation/acp/builds/install/binary.js +28 -0
- package/dist/workstation/acp/builds/install/npm.js +235 -0
- package/dist/workstation/acp/builds/install/uv.js +254 -0
- package/dist/workstation/acp/builds/migrate.js +195 -0
- package/dist/workstation/acp/builds/model.js +128 -0
- package/dist/workstation/acp/builds/nodeGate.js +100 -0
- package/dist/workstation/acp/builds/store.js +352 -0
- package/dist/workstation/acp/commandTableProbe.js +68 -0
- package/dist/workstation/acp/configOptions.js +103 -1
- package/dist/workstation/acp/delegationMcp.js +24 -0
- package/dist/workstation/acp/envSecrets.js +27 -0
- package/dist/workstation/acp/initializeResponse.js +285 -0
- package/dist/workstation/acp/installErrors.js +12 -2
- package/dist/workstation/acp/installProbe.js +1 -1
- package/dist/workstation/acp/managedPatches.js +144 -0
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +80 -0
- package/dist/workstation/acp/permission.js +335 -37
- package/dist/workstation/acp/processor/checkpoint.js +87 -0
- package/dist/workstation/acp/processor/commands.js +35 -0
- package/dist/workstation/acp/processor/control.js +355 -0
- package/dist/workstation/acp/processor/emit.js +26 -0
- package/dist/workstation/acp/processor/jsonrpc.js +172 -0
- package/dist/workstation/acp/processor/memoryLog.js +49 -0
- package/dist/workstation/acp/processor/origins.js +48 -0
- package/dist/workstation/acp/processor/output.js +67 -0
- package/dist/workstation/acp/processor/projection.js +42 -0
- package/dist/workstation/acp/processor/record.js +128 -0
- package/dist/workstation/acp/processor/runtime.js +181 -0
- package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
- package/dist/workstation/acp/processor/session.js +525 -0
- package/dist/workstation/acp/processor/sideChannel.js +80 -0
- package/dist/workstation/acp/processor/state.js +74 -0
- package/dist/workstation/acp/processor/step.js +459 -0
- package/dist/workstation/acp/processor/test/fixtureLog.js +65 -0
- package/dist/workstation/acp/processor/test/harness.js +120 -0
- package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
- package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
- package/dist/workstation/acp/processor/transport/generations.js +76 -0
- package/dist/workstation/acp/processor/transport/keeper.js +130 -0
- package/dist/workstation/acp/processor/transport/local.js +269 -0
- package/dist/workstation/acp/processor/transport/types.js +78 -0
- package/dist/workstation/acp/processor/updates.js +364 -0
- package/dist/workstation/acp/processorSession.js +1512 -0
- package/dist/workstation/acp/qoderStartup.js +45 -0
- package/dist/workstation/acp/registry/catalog.js +385 -4
- package/dist/workstation/acp/registry/detect.js +55 -0
- package/dist/workstation/acp/registry/launch.js +61 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +56 -27
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +114 -0
- package/dist/workstation/acp/terminalLogin.js +171 -0
- package/dist/workstation/acp/terminalLoginSpec.js +41 -0
- package/dist/workstation/acp/test/adapterFixtures.js +256 -0
- package/dist/workstation/acp/test/daemonLife.js +106 -0
- package/dist/workstation/acp/test/fakeAgent.js +185 -9
- package/dist/workstation/acp/test/keeperWorld.js +267 -0
- package/dist/workstation/acp/test/memoryOutbox.js +55 -0
- package/dist/workstation/acp/test/raw/fixtures.js +196 -0
- package/dist/workstation/acp/test/raw/qoderCaptures.js +125 -0
- package/dist/workstation/acp/test/raw/sanitize.js +344 -0
- package/dist/workstation/acp/test/raw/sources.js +249 -0
- package/dist/workstation/acp/test/realAdapter.js +377 -0
- package/dist/workstation/acp/test/scriptAgent.js +206 -0
- package/dist/workstation/acp/test/sessionFrames.js +42 -0
- package/dist/workstation/acp/timedCommand.js +4 -2
- package/dist/workstation/acp/uvxInstall.js +72 -122
- package/dist/workstation/acp/validate.js +33 -5
- package/dist/workstation/agentFlow.js +103 -0
- package/dist/workstation/agentSessions.js +1686 -348
- package/dist/workstation/channels/agent.js +14 -6
- package/dist/workstation/channels/computer.js +203 -7
- package/dist/workstation/channels/computerExecs.js +200 -0
- package/dist/workstation/channels/management.js +86 -6
- package/dist/workstation/config.js +3 -1
- package/dist/workstation/connection/encryptedChannel.js +88 -21
- package/dist/workstation/daemon.js +309 -6
- package/dist/workstation/daemonctl.js +28 -2
- package/dist/workstation/desktop/binding.js +36 -0
- package/dist/workstation/desktop/bridge.js +212 -0
- package/dist/workstation/desktop/flow-control.js +52 -0
- package/dist/workstation/desktop/focused-text.js +130 -0
- package/dist/workstation/desktop/protocol.js +35 -0
- package/dist/workstation/exec/attach.js +126 -0
- package/dist/workstation/exec/command.js +40 -0
- package/dist/workstation/exec/durable.js +517 -0
- package/dist/workstation/exec/fallback.js +106 -0
- package/dist/workstation/exec/policy.js +837 -0
- package/dist/workstation/exec/shaping.js +211 -0
- package/dist/workstation/exec.js +3 -112
- package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
- package/dist/workstation/files/adapters/toolRequests.js +121 -0
- package/dist/workstation/files/client.js +455 -0
- package/dist/workstation/files/errors.js +28 -0
- package/dist/workstation/files/fence.js +350 -0
- package/dist/workstation/files/find/fileIndex.js +153 -0
- package/dist/workstation/files/find/fuzzy.js +155 -0
- package/dist/workstation/files/host.js +122 -0
- package/dist/workstation/files/hostProtocol.js +2 -0
- package/dist/workstation/files/media/cache.js +620 -0
- package/dist/workstation/files/media/encoding.js +308 -0
- package/dist/workstation/files/media/estimator.js +100 -0
- package/dist/workstation/files/media/media.js +325 -0
- package/dist/workstation/files/media/packedAudio.js +23 -0
- package/dist/workstation/files/media/scheduler.js +551 -0
- package/dist/workstation/files/media/types.js +5 -0
- package/dist/workstation/files/scope.js +45 -0
- package/dist/workstation/files/structured.js +284 -0
- package/dist/workstation/files/text.js +356 -0
- package/dist/workstation/files/uploads.js +351 -0
- package/dist/workstation/journal.js +65 -236
- package/dist/workstation/keepers/acp/client.js +101 -0
- package/dist/workstation/keepers/acp/envelope.js +62 -0
- package/dist/workstation/keepers/acp/host.js +419 -0
- package/dist/workstation/keepers/acp/main.js +18 -0
- package/dist/workstation/keepers/acp/memoryStream.js +121 -0
- package/dist/workstation/keepers/acp/params.js +137 -0
- package/dist/workstation/keepers/acp/protocol.js +66 -0
- package/dist/workstation/keepers/acp/recover.js +120 -0
- package/dist/workstation/keepers/acp/session.js +584 -0
- package/dist/workstation/keepers/acp/stderr.js +154 -0
- package/dist/workstation/keepers/kit/activity.js +103 -0
- package/dist/workstation/keepers/kit/bootstrap.js +164 -0
- package/dist/workstation/keepers/kit/buildVersion.js +51 -0
- package/dist/workstation/keepers/kit/bundle.js +120 -0
- package/dist/workstation/keepers/kit/client.js +432 -0
- package/dist/workstation/keepers/kit/identity.js +21 -0
- package/dist/workstation/keepers/kit/ipc.js +112 -0
- package/dist/workstation/keepers/kit/launchConfig.js +8 -0
- package/dist/workstation/keepers/kit/launcher.js +249 -0
- package/dist/workstation/keepers/kit/lock.js +123 -0
- package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
- package/dist/workstation/keepers/kit/paths.js +75 -0
- package/dist/workstation/keepers/kit/protocol.js +43 -0
- package/dist/workstation/keepers/kit/server.js +518 -0
- package/dist/workstation/keepers/kit/streamLog.js +697 -0
- package/dist/workstation/keepers/legacy.js +296 -0
- package/dist/workstation/keepers/policy.js +576 -0
- package/dist/workstation/keepers/stop.js +34 -0
- package/dist/workstation/keepers/terminal/client.js +213 -0
- package/dist/workstation/keepers/terminal/exec.js +981 -0
- package/dist/workstation/keepers/terminal/generations.js +129 -0
- package/dist/workstation/keepers/terminal/keeper.js +62 -0
- package/dist/workstation/keepers/terminal/main.js +43 -0
- package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
- package/dist/workstation/keepers/terminal/params.js +27 -0
- package/dist/workstation/keepers/terminal/protocol.js +56 -0
- package/dist/workstation/keepers/terminal/pty.js +330 -0
- package/dist/workstation/oauth/deviceAuthorization.js +21 -12
- package/dist/workstation/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +201 -8
- package/dist/workstation/protocol/common.js +36 -0
- package/dist/workstation/protocol/computer.js +107 -7
- package/dist/workstation/protocol/management.js +50 -2
- package/dist/workstation/qoderAdapter/backgroundProcess.js +118 -0
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +193 -0
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +372 -0
- package/dist/workstation/qoderAdapter/callbackContext.js +76 -0
- package/dist/workstation/qoderAdapter/completedContinuation.js +139 -0
- package/dist/workstation/qoderAdapter/contract.js +78 -0
- package/dist/workstation/qoderAdapter/foregroundOutput.js +36 -0
- package/dist/workstation/qoderAdapter/history.js +85 -0
- package/dist/workstation/qoderAdapter/identityIndex.js +191 -0
- package/dist/workstation/qoderAdapter/inputQueue.js +42 -0
- package/dist/workstation/qoderAdapter/main.js +95 -0
- package/dist/workstation/qoderAdapter/mapper.js +117 -0
- package/dist/workstation/qoderAdapter/observerPlugin.js +245 -0
- package/dist/workstation/qoderAdapter/observerReceiver.js +187 -0
- package/dist/workstation/qoderAdapter/permissions.js +61 -0
- package/dist/workstation/qoderAdapter/profile.js +93 -0
- package/dist/workstation/qoderAdapter/sdkDriver.js +48 -0
- package/dist/workstation/qoderAdapter/sdkRuntime.js +93 -0
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +145 -0
- package/dist/workstation/qoderAdapter/session.js +319 -0
- package/dist/workstation/qoderAdapter/subagentMeta.js +28 -0
- package/dist/workstation/qoderAdapter/subwork.js +85 -0
- package/dist/workstation/qoderAdapter/taskControl.js +38 -0
- package/dist/workstation/qoderAdapter/taskLedger.js +163 -0
- package/dist/workstation/qoderAdapter/types.js +1 -0
- package/dist/workstation/qoderAdapter/usage.js +37 -0
- package/dist/workstation/register.js +4 -0
- package/dist/workstation/systemMetrics.js +110 -1
- package/dist/workstation/tools/browser.js +394 -0
- package/dist/workstation/tools/client.js +277 -0
- package/dist/workstation/tools/info.js +50 -0
- package/dist/workstation/tools/keeperProcess.js +124 -0
- package/dist/workstation/tools/router.js +55 -0
- package/dist/workstation/tools/terminalRouter.js +222 -0
- package/dist/workstation/tools/validation.js +86 -0
- package/dist/workstation/update/coordinator.js +32 -0
- package/dist/workstation/update/npmInstaller.js +2 -0
- package/dist/workstation/update/ptyPreflight.js +45 -0
- package/dist/workstation/update/taskRegistry.js +141 -23
- package/dist/workstation/update/watchdog.js +66 -0
- package/package.json +20 -7
- package/skills/axom/SKILL.md +55 -17
- package/skills/axom-definition/SKILL.md +32 -32
- package/skills/axom-platform/SKILL.md +39 -39
- package/dist/workstation/acp/acpSession.js +0 -1047
- package/dist/workstation/acp/contract.js +0 -494
- package/dist/workstation/acp/draftInterceptor.js +0 -154
package/README.md
CHANGED
|
@@ -61,4 +61,4 @@ Non-secret CLI auth state is stored in `~/.axom/config.json` with mode `0600`. T
|
|
|
61
61
|
|
|
62
62
|
## Learn more
|
|
63
63
|
|
|
64
|
-
This README is a front page, not the manual. For the full command reference (arguments, exit codes, examples) and architecture notes, see `docs/modules/cli.md` and `guide/en/agent-integration/axom-cli/` in the Axomwork monorepo this package is built from.
|
|
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.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const DEFAULT_CLI_SCOPES = ['record_query', 'record_write', 'execute', 'task', 'workstation'];
|
|
2
|
+
/**
|
|
3
|
+
* Requested by `axom auth` but left unchecked on the consent page (the server preselects only
|
|
4
|
+
* the client's defaults). app_studio and setting_platform are shown locked for non-admins.
|
|
5
|
+
*/
|
|
6
|
+
export const OPTIONAL_CLI_SCOPES = ['app_studio', 'setting_user', 'setting_platform'];
|
|
7
|
+
export function cliRequestedScopes(cowork = false) {
|
|
8
|
+
return [...DEFAULT_CLI_SCOPES, ...OPTIONAL_CLI_SCOPES, ...(cowork ? ['cowork'] : [])];
|
|
9
|
+
}
|
|
10
|
+
export function grantedCliScopes(scope, requested) {
|
|
11
|
+
if (typeof scope !== 'string' || !scope.trim()) {
|
|
12
|
+
throw new Error('Server does not report granted scopes; upgrade the server before authorizing this CLI.');
|
|
13
|
+
}
|
|
14
|
+
const granted = [...new Set(scope.trim().split(/ +/))];
|
|
15
|
+
if (granted.some((value) => !requested.includes(value))) {
|
|
16
|
+
throw new Error('Server returned unsupported or unrequested scopes; upgrade the server and authorize again.');
|
|
17
|
+
}
|
|
18
|
+
return granted;
|
|
19
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# CLI / adapter 补丁说明
|
|
2
|
+
|
|
3
|
+
## 自动应用与交付
|
|
4
|
+
|
|
5
|
+
`runtime.json` 是随 CLI 发布的可信补丁策略,不从 agent 响应或网络下载补丁代码。
|
|
6
|
+
`npm run build` 编译 CLI 后自动复制本目录到 `dist/axom-patch/cli`;打包前检查内容一致。
|
|
7
|
+
Gradle 将带补丁的 npm 包、size/SHA-512 与自动分发标记封装为 Server 的 Client release。
|
|
8
|
+
Workstation Docker 同样携带这份策略;普通 CLI 安装/Server 下发更新不依赖 npm postinstall。
|
|
9
|
+
|
|
10
|
+
覆盖与发布包补丁都用同一套版本条件选择生效范围,二选一,加载时校验:
|
|
11
|
+
|
|
12
|
+
| | 覆盖(读 registry 版本) | 发布包补丁(读被改的包版本) |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| when:只在列出的版本 | `when_registry_version: ["0.86.0"]` | `versions: ["0.84.0"]`;原文件哈希不符即报错 |
|
|
15
|
+
| until:低于某版本 | `until_registry_version: "0.86.1"`,registry 读不懂时照样生效 | `until_version: "0.86.1"`;原文件哈希不符的版本按上游启动并告警 |
|
|
16
|
+
| 都不写 | 一直生效,直到删除 | 不允许 |
|
|
17
|
+
|
|
18
|
+
when 用于「这个版本有问题、已有修复版」,registry 一换版本就不再生效;until 用于「等上游发某个版本」,
|
|
19
|
+
钉住的版本不得高于界限:低于界限可用于等待正式修复版,等于界限可用于等待 registry 收录已发布版本。
|
|
20
|
+
两种都不需要为让位再发 CLI。
|
|
21
|
+
|
|
22
|
+
用户按正常界面安装/更新适配器即可:补充条目(Kimi Code、Command Code、ZCode)、分发源覆盖和启动环境变量通过 catalog 应用,
|
|
23
|
+
roster、Install、Update、启动统一读取同一来源。
|
|
24
|
+
|
|
25
|
+
**有补丁的条目必须钉版本**(2026-10-14 起的硬规则,`catalog.test.ts` 的 `patched agents stay pinned`
|
|
26
|
+
机械守卫):配方只核验过列出的版本,不钉的话 registry/npm 一发新版,Install/Update 就装上一个配方
|
|
27
|
+
不生效的包——账号配额、steering、子代理归一静默消失。钉住后 roster 上报的 `version`/`registryVersion`
|
|
28
|
+
就是生效(钉住)版本,设置页的 Update 比对(`installedVersion != registryVersion`)因此永远对着
|
|
29
|
+
catalog 的钉版本,**不对着官方 registry 比**;上游新版本要等
|
|
30
|
+
[`nightly-task.md`](nightly-task.md) 的周期任务核验移植配方后再 bump,届时
|
|
31
|
+
`reconcileCertifiedBuilds` 把已安装的工作站自动前移。
|
|
32
|
+
发布包补丁由 `managedPatches.ts` 在 Install/Update 和首次真实启动时应用。
|
|
33
|
+
历史安装也会补齐,配置显式 command 的用户自有启动程序按其配置执行。
|
|
34
|
+
|
|
35
|
+
发布包补丁步骤:
|
|
36
|
+
|
|
37
|
+
1. 按包名与精确版本选择已验证补丁;不支持的版本按上游启动,并在维护日志提示复核。
|
|
38
|
+
2. 缓存缺包时走现有 npm 安装机制;原文件身份与 SHA-256 不符合时安装/启动失败,不能声称补丁已生效。
|
|
39
|
+
3. 复制完整 adapter 及其依赖到 Axom 管理的临时目录,检查修改前/后哈希。
|
|
40
|
+
4. 写 `axom-patches.json` 收据,原子发布副本,从包声明的入口启动。
|
|
41
|
+
5. 缓存身份为「包名、版本、补丁内容哈希」;并发复用并保留各次启动参数/环境。
|
|
42
|
+
|
|
43
|
+
副本位于 Workstation config dir 的 `acp-adapters/patched-npm/`。
|
|
44
|
+
npm 原缓存保持原样;已有旧副本保留给仍运行的会话。后续 CLI 移除配方后,新启动自动改走原发布包。
|
|
45
|
+
用户无需运行 patch、修改 npm 缓存或单独配置补丁目录。
|
|
46
|
+
|
|
47
|
+
## Agent catalog(`registry/catalog.ts`)
|
|
48
|
+
|
|
49
|
+
catalog 是 Axom 对官方 ACP registry 的本地扩展层,随 CLI 发布:条目在 catalog 里 = 真机验证过
|
|
50
|
+
(roster 的 `curated`),设置页展开区只放 catalog 条目,其余官方条目折叠在 "Unverified Agents"。
|
|
51
|
+
它能做的事:静态推荐(`recommended`:claude、codex、pi)、补充/取代官方条目(`supplement` /
|
|
52
|
+
`supersedes`)、钉版本与启动环境(`distribution` / `launchEnv`,临时的放 `runtime.json`)、
|
|
53
|
+
前置依赖(`prerequisite`)、本地检测(`detect`)。
|
|
54
|
+
|
|
55
|
+
**托管与检测分工**:Axom 启动的一律是自己托管、钉版本的那份(npx 缓存、`uv tool dir`、
|
|
56
|
+
`acp-adapters/` 下的二进制),roster 的 `adapterInstalled` 只说这一份。`detect` 只看用户自己的安装
|
|
57
|
+
(PATH 命令或固定文件,不起进程):检测到就报 `detected`,设置页因"In use on this machine"把它排进推荐位,
|
|
58
|
+
但不算 Axom 已安装、也不拿来启动。前置依赖同样靠 `detect` 判断;缺了且没有 `install`
|
|
59
|
+
(ZCode)时 roster 报 `missingPrerequisite`,设置页不给 Install、只显示 catalog 的提示。
|
|
60
|
+
|
|
61
|
+
## 当前补丁
|
|
62
|
+
|
|
63
|
+
| 台账 ID | 状态/应用入口 | AI 核验与下线重点 |
|
|
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 |
|
|
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 时同步存量标题后删除配方;验收含并发、失败/取消、后台迟到、重启重放 |
|
|
71
|
+
| claude-plan-tools | active;启动任务清单工具变量 | 无变量真实 ACP 会话能发布 plan 后下线 |
|
|
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 核验后同升 |
|
|
77
|
+
| claude-async-task-preview | retired;0.86.0 钉 0.86.1-preview.2 的 override 已删除 | 修复 #1249 自 0.87.0 起入正式版;2026-10-13 无补丁原版 0.90.0 探针 6/6 通过(探针构造器参数改 `"air"`,0.90.0 将 enabled 改名为 form) |
|
|
78
|
+
| claude-late-async-terminal | retired;0.84.0/0.85.0 配方已删除 | 修复由上游 #1249 带入预览版并被采用;探针在无补丁预览版 6/6 通过 |
|
|
79
|
+
| acp-sdk-draft-variants | active;processor/updates(取代已删除的 draftInterceptor)/ draftSubagents | 发布 SDK 接收 subagent/async 变体后,验证真实通知流,再替换对应边界 |
|
|
80
|
+
| acp-sdk-foreign-session | active;processor/updates(取代已删除的 draftInterceptor) | processor 对子会话 session/update 原样直通(不经 SDK schema);正式 SDK schema 能校验子会话更新且改为先校验不丢通知后才下线;仅支持 draft 类型不足以下线 |
|
|
81
|
+
| acp-sdk-capability-fields | active;acpClient / draftSubagents | 无兼容桥 initialize 能保留 subagents,且 AIR vendor 能力协商仍有效 |
|
|
82
|
+
| pi-subagent-subwork | active;pi-acp 0.0.34 包补丁(链在 pi-steering 之后,catalog 钉 0.0.34)把 pi-subagents 派发/完成归一为 `subagent_spawned`/`subagent_state_update` | 无补丁 pi-acp 能发 T2 变体并透传 custom message 后移除;axom-cli/服务端零改动,processor 原生消费 |
|
|
83
|
+
| qoder-subagent-origin / qoder-subagent-subwork | active;原生 Qoder 1.1.71 包补丁保留 `_meta.qoder`,适配器归一到 subwork | 无补丁版本的并发过程、终态、load/resume/重连全部通过后,删除包配方与边界;core/App 不加 Qoder 分支 |
|
|
84
|
+
| acp-registry-etarget | active;版本不存在时单次回退 | 上游版本可安装时自然不触发;删除整个策略要检查 registry 契约 |
|
|
85
|
+
| claude-steering | draft;steering/ 的两份旧版 diff | 0.76/0.77 草案未经完整运行时验证,不自动应用;重新测 E1–E3 |
|
|
86
|
+
| codex-steering | draft;steering/ 的旧版 diff | 1.10/1.11 草案不适用于当前新版,不自动应用;重新测 E1–E3 |
|
|
87
|
+
|
|
88
|
+
每条完整路径、版本哈希、上游链接、删除条件与验证项见 [台账](../manifest.json)。
|
|
89
|
+
steering diff 原样来自历史 notes;维护本目录文件及台账哈希,历史归档保留实验上下文。
|
|
90
|
+
|
|
91
|
+
catalog 各 agent 的上游版本核查、补丁必要性判定与钉版本升级,按
|
|
92
|
+
[`nightly-task.md`](nightly-task.md) 的周期任务执行(AI 可直接照步骤运行)。
|
|
93
|
+
|
|
94
|
+
## 验证
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd axom-cli
|
|
98
|
+
npm run build
|
|
99
|
+
npx vitest run src/workstation/acp/patchAssets.test.ts src/workstation/acp/managedPatches.test.ts src/workstation/acp/registry/catalog.test.ts src/workstation/acp/acpAgents.test.ts
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Claude 的真实 runtime 回归(不启动 Claude 模型、不读取凭据):
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
node axom-patch/cli/probes/claude-late-async-terminal.mjs /path/to/pristine/claude-agent-acp --expect-bug
|
|
106
|
+
node axom-patch/cli/probes/claude-late-async-terminal.mjs /path/to/fixed/claude-agent-acp # 例如原版 0.86.1-preview.2
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
覆盖 completed/failed/stopped × terminalState/terminalPromise 六种晚到终态,同时要求普通 late transcript 仍被过滤。
|
|
110
|
+
下线时必须在**无补丁的新发布包**上跑第二条命令,不要用当前带补丁副本证明上游修复。
|
|
111
|
+
|
|
112
|
+
Pi 的发布包运行时探针(使用真实 adapter 类,不启动模型、不读取凭据):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
node axom-patch/cli/probes/pi-steering.mjs /path/to/pristine/pi-acp --expect-unsupported
|
|
116
|
+
node axom-patch/cli/probes/pi-steering.mjs /path/to/patched/pi-acp
|
|
117
|
+
node axom-patch/cli/probes/pi-subagent-subwork.mjs /path/to/pristine/pi-acp --expect-no-subagents
|
|
118
|
+
node axom-patch/cli/probes/pi-subagent-subwork.mjs /path/to/patched/pi-acp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
仅已核验的原生 Pi 0.87.1 声明 `supported: true, timings: ["next"]`。其他原生版本不声明能力。
|
|
122
|
+
运行中普通消息因此走既有 NEXT 自动投递;已排队消息仍可手动插话。当前工具先完成,再处理插话。
|
|
123
|
+
空闲、取消中、压缩中返回 `promptRequired`,不提交原生消息。传输异常继续使用现有平台处置。
|
|
124
|
+
队列插话、取消、出队共享独立互斥锁;成功回执后的历史物化和队列删除不可被请求取消打断。
|
|
125
|
+
|
|
126
|
+
## AI 新增/下线操作
|
|
127
|
+
|
|
128
|
+
- 新增发布包补丁:提交本目录的 JSON 配方(包名、精确版本、每个文件的原/产物哈希与唯一替换段),
|
|
129
|
+
加入 runtime.package_patches 和台账,跑原包/补丁对照、安装启动测试与 Client release 打包校验。
|
|
130
|
+
- 新增来源/环境覆盖:修改 runtime.overrides、台账和 catalog 回归,保留 registry/用户显式 env 优先级。
|
|
131
|
+
- 下线:先按统一证据门槛核验,从 runtime 移除策略或替换 SDK 边界,保留回归;台账标 retired 并记录证据。
|
|
132
|
+
- CLI 补丁变化必须 bump CLI npm version。相同版本不会触发 Server 自动升级,不能只改 Server 镜像 tag。
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Account quota extension
|
|
2
|
+
|
|
3
|
+
Axom extension, not an upstream ACP standard. ACP extension methods use `_axom/account/rateLimits/read` and `_axom/account/rateLimits/updated`. Capability: `agentCapabilities._meta["axom/accountRateLimits"] = {version: 2, read: true, updated: true}`. A read's result and an update's params have the same snapshot schema.
|
|
4
|
+
|
|
5
|
+
Only the popover footer sends a read. Initialization, authentication, reconnects, session changes, turns, opening UI and reset deadlines do not query quota. Native Codex and Claude quota events are normalized without fetching. Command, Kimi and Antigravity publish updates from explicit reads; they do not synthesize a polling loop. UI SSE streams only database state.
|
|
6
|
+
|
|
7
|
+
Initialization publishes an identity-only unknown observation of adapter support. It does not establish quota display eligibility. Reconnects replay each live session's cached observation with its original timestamp; they never fetch usage. A pre-upgrade hello roster cannot hide a stored capability observation. Version-gated installed native extensions also advertise support in the roster without spawning or querying the provider.
|
|
8
|
+
|
|
9
|
+
Snapshots contain `schemaVersion`, `provider`, `billingMode`, `availability`, `observedAt`, nullable `plan`, independent `limits`, `balances` and bounded `_meta`. Optional `accountKey` must be opaque, never a credential. Modes are `subscription`, `non_subscription`, `unknown`; missing authentication or temporarily unavailable quota is unknown, not proof of API billing. `ExternalAgentAccountDto.supportsQuotaDisplay` is the shared eligibility rule for UI and server refresh: adapter support plus confirmed subscription billing, or an independent quota checker with unknown billing. The server identifies the existing Command Code, ZCode, Kimi and Antigravity readers through `quotaCheckerAvailable`, preserving their first manual discovery action. Non-subscription accounts expose no quota entry, and rejected refreshes do not claim a cooldown or contact the adapter. Codex/Claude authentication notifications establish billing without fetching quota; their unknown accounts remain hidden. Switching authentication publishes a complete identity snapshot to clear previous windows. Their processor also refuses quota reads until subscription authentication is confirmed. Future server-configured quota checkers should use this shared capability rather than add provider-specific UI branches.
|
|
10
|
+
|
|
11
|
+
Windows use integer seconds (monthly calendar periods use `period: "month"`), percentage consumed in 0..100, ISO UTC timestamps and nullable unknown values. Absolute credits or monetary values are decimal strings; money uses currency and exponent. Scoped model quotas remain detailed rows, not duplicated general rings. When only model quotas exist, one ring represents the most used model, with an explicit popover explanation. Vendor fields remain under `_meta._codex`, `_claude`, `_kimi`, `_commandcode`, `_antigravity`. Latest snapshots persist per workstation/configured entry; delayed older updates are ignored. A server transaction admits one refresh per 60 seconds, including failures. Errors preserve the prior snapshot.
|
|
12
|
+
|
|
13
|
+
Verified managed versions: Codex ACP 2.2.2; Claude ACP 0.89.1 (SDK experimental usage control); Command Code 1.74.3; native Kimi Code 2.1.1; ZCode ACP 0.69.3. New versions do not inherit unverified patches. Regenerate the npm recipes with `generate.py --codex <pristine-package-dir> --claude <pristine-package-dir> --commandcode <pristine-package-dir> [--zcode <pristine-package-dir>]`; update inventory artifact hashes after changes. Never patch a user's global installation.
|
|
14
|
+
|
|
15
|
+
Kimi's native shim starts a temporary loopback web process only during an explicit read and calls the SDK-backed local usage route; process cleanup is bounded. Its native provider catalog identifies actual API credentials before querying managed quota. The common launcher selects this shim from `runtime.json.native_extensions`. Claude uses an existing session's query when possible, otherwise a nonpersistent SDK control session. Neither submits a model inference request for quota. Native payload availability can vary with login/provider/version; failed reads are surfaced without overwriting previous observations.
|
|
16
|
+
|
|
17
|
+
`updateMode: "snapshot"` means a complete read or observation; `"delta"` updates only its supplied windows/balances. The server merges deltas transactionally by id (or equivalent window/scope for legacy Claude ids). Null partial measurements retain known values in the same reset period, with one-second timestamp rounding tolerance; a changed period becomes unknown rather than borrowing old utilization. Complete reads remove windows that are absent. Legacy update notifications without an explicit mode are treated as deltas by the CLI. Capability v2 lets explicit reads avoid old v1 session processes. No automatic query was added.
|
|
18
|
+
|
|
19
|
+
Command's month window uses its native `getPlanInfo` allowance and remaining monthly credits; billing `currentPeriodEnd` supplies reset time. Unknown plans have unknown percentage rather than an inferred cap. Purchased/free credits stay separate balances. Claude emits only the affected event window; the weekly snapshot is first acquired through the footer's manual refresh and survives subsequent 5-hour events.
|
|
20
|
+
|
|
21
|
+
ZCode's read resolves the individual coding-plan key through the adapter's own credential chain (`codingPlanRequestAuthFor`: legacy config.json first, then the encrypted `~/.zcode/v2/credentials.json` store #320 — the adapter's own `/quota` and `account/usage_stats` still read config.json only and stay broken on fresh 3.14.x installs; fixed upstream when they adopt the store path). The GLM quota endpoint returns plan `level` plus `limits[]` rows: `TOKENS_LIMIT number=5` is the 5h prompt window (percentage-only, no counters), `number=7` a weekly window when a plan has one, `TIME_LIMIT`/`MCP_LIMIT` the built-in tool allowance with absolute call counts and per-model detail (kept in `_meta._zcode`). The endpoint offers no monthly window on any measured plan; nothing is inferred. The read throws when no entitled individual plan is servable (team keys are deliberately not served by the adapter), which surfaces as a standard refresh failure.
|
|
22
|
+
|
|
23
|
+
Antigravity's native extension is limited to official ACP **1.3.0**. Its bundled discovery document defines `ModelDetails.quotaInfo.remainingFraction` (remaining, not consumed), `resetTime`, and `UserTier.availableCredits`. The sidecar reproduces the native consumer `loadCodeAssist` endpoint selection and reads `fetchAvailableModels` only on an explicit read. It does not perform onboarding, inference, alter settings, or write refreshed OAuth credentials. It uses the same `GEMINI_HOME/antigravity-acp` settings and consumer token store (`gemini` / `antigravity-acp` Keychain on macOS, secure-file fallback; `AGY_ACP_FORCE_FILE_STORAGE` respected). Explicit successful ACP authentication overrides the file's auth type. API key, Agent Platform, enterprise OAuth and gateway are outside this consumer subscription integration.
|
|
24
|
+
|
|
25
|
+
The quota schema supplies no window duration or monthly allowance: keep `windowSeconds: null`, list each model and its reset, and keep AI credits as a balance. Do not infer shared buckets from identical percentages/reset times. Extensions allowlist quota/tier fields and exclude project, tokens and account identity. Official ACP `usage_update` is context-token usage, not an account quota notification; it is never converted into subscription usage. No spontaneous account quota notification is exposed in 1.3.0. Protocol/normalization tests pass against the bundled native schema; a live read on the development machine could not be measured because its Keychain credential read timed out. A locked/inaccessible store surfaces the standard refresh failure and preserves the last snapshot.
|
|
26
|
+
|
|
27
|
+
Antigravity quota capability v3 fixes interactive Keychain authorization: the first explicit read permits up to 120 seconds for the OS dialog, within longer quota-specific CLI/server/client deadlines. A successful credential read and refreshed access token stay only in the reader process's memory; subsequent manual reads do not re-open Keychain. Explicit authentication/logout invalidates that cache, and failed credential loads are retryable. This matters because the native ACP credential writer deletes and recreates the Keychain item, potentially invalidating previous per-item “Always Allow” grants. No ACL, partition list or native credential file is modified by Axom. Older v2 model sessions are bypassed for quota reads without interruption. When necessary, a separate control connection is retained across manual reads, closed on authentication changes and CLI shutdown; it performs no background quota queries. Tests simulate delayed authorization and repeated reads without invoking the real password dialog; live OS authorization remains a user-side verification step.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Version-gated native ACP sidecar. Never queries on initialize, auth, turns or timers. */
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { READ, UPDATED, CAPABILITY } from './normalize.mjs';
|
|
5
|
+
import { readAntigravity, clearAntigravityCredentials } from './antigravity-read.mjs';
|
|
6
|
+
const [binary, ...args] = process.argv.slice(2);
|
|
7
|
+
const child = spawn(binary, args, { stdio: ['pipe', 'pipe', 'inherit'], env: process.env });
|
|
8
|
+
const write = value => process.stdout.write(JSON.stringify(value) + '\n');
|
|
9
|
+
let initializeId, authType, reading = false;
|
|
10
|
+
const authRequests = new Map();
|
|
11
|
+
createInterface({ input: child.stdout }).on('line', line => {
|
|
12
|
+
let message; try { message = JSON.parse(line); } catch { process.stdout.write(line + '\n'); return; }
|
|
13
|
+
if (message.id === initializeId && message.result) {
|
|
14
|
+
message.result.agentCapabilities ??= {};
|
|
15
|
+
message.result.agentCapabilities._meta ??= {};
|
|
16
|
+
message.result.agentCapabilities._meta['axom/accountRateLimits'] = { ...CAPABILITY, version: 5, provider: 'antigravity' };
|
|
17
|
+
}
|
|
18
|
+
if (authRequests.has(message.id)) {
|
|
19
|
+
if (message.result && !message.error) { authType = authRequests.get(message.id); clearAntigravityCredentials(); }
|
|
20
|
+
authRequests.delete(message.id);
|
|
21
|
+
}
|
|
22
|
+
write(message);
|
|
23
|
+
});
|
|
24
|
+
createInterface({ input: process.stdin }).on('line', line => {
|
|
25
|
+
let message; try { message = JSON.parse(line); } catch { child.stdin.write(line + '\n'); return; }
|
|
26
|
+
if (message.method === 'initialize') initializeId = message.id;
|
|
27
|
+
if (message.method === 'authenticate' && message.id !== undefined) authRequests.set(message.id, message.params?.methodId);
|
|
28
|
+
if (message.method === 'logout') { authType = undefined; clearAntigravityCredentials(); }
|
|
29
|
+
if (message.method === READ && message.id !== undefined) {
|
|
30
|
+
if (reading) { write({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: 'A usage refresh is already in progress' } }); return; }
|
|
31
|
+
reading = true;
|
|
32
|
+
void readAntigravity({ authType }).then(result => {
|
|
33
|
+
write({ jsonrpc: '2.0', id: message.id, result });
|
|
34
|
+
write({ jsonrpc: '2.0', method: UPDATED, params: result });
|
|
35
|
+
}, error => {
|
|
36
|
+
if (/^Antigravity (oauth|account|quota) request failed \(HTTP \d{3}\)$/.test(error?.message ?? '')) process.stderr.write(`${error.message}\n`);
|
|
37
|
+
write({ jsonrpc: '2.0', id: message.id, error: { code: -32603, message: 'Failed to fetch usage' } });
|
|
38
|
+
})
|
|
39
|
+
.finally(() => { reading = false; });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
child.stdin.write(line + '\n');
|
|
43
|
+
});
|
|
44
|
+
process.stdin.on('end', () => child.stdin.end());
|
|
45
|
+
for (const signal of ['SIGTERM', 'SIGINT']) process.on(signal, () => child.kill(signal));
|
|
46
|
+
child.on('error', () => { process.exitCode = 1; });
|
|
47
|
+
child.on('exit', code => process.exit(code ?? 1));
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** All credential access and network I/O occur only inside an explicit quota read. */
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { homedir, type, machine } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { antigravity } from './antigravity.mjs';
|
|
8
|
+
const execute = promisify(execFile);
|
|
9
|
+
const PROD = 'https://cloudcode-pa.googleapis.com';
|
|
10
|
+
const DAILY = 'https://daily-cloudcode-pa.googleapis.com';
|
|
11
|
+
// Official ACP 1.3.0 useragent.py: CCPA uses the antigravity token to resolve
|
|
12
|
+
// client-specific entitlements. A generic fetch User-Agent resolves the wrong tier.
|
|
13
|
+
const USER_AGENT = `antigravity/acp/1.3.0 (aidev_client; os_type=${process.platform === 'win32' ? 'windows' : type().toLowerCase()}; arch=${machine().toLowerCase()}; host_path=axom/unknown; proxy_client=antigravity/sdk)`;
|
|
14
|
+
async function jsonFile(path) {
|
|
15
|
+
try { return JSON.parse(await readFile(path, 'utf8')); }
|
|
16
|
+
catch (error) { if (error.code === 'ENOENT') return null; throw new Error('Failed to read Antigravity settings'); }
|
|
17
|
+
}
|
|
18
|
+
export async function loadAntigravityCredentials(root, env, { platform = process.platform, executeKeychain = execute } = {}) {
|
|
19
|
+
if (platform === 'darwin' && !['1', 'true'].includes(env.AGY_ACP_FORCE_FILE_STORAGE)) {
|
|
20
|
+
try {
|
|
21
|
+
// This is an interactive authorization, not a three-second background probe.
|
|
22
|
+
const { stdout } = await executeKeychain('/usr/bin/security', ['find-generic-password', '-s', 'gemini', '-a', 'antigravity-acp', '-w'], { timeout: 120000, maxBuffer: 65536 });
|
|
23
|
+
return JSON.parse(stdout);
|
|
24
|
+
} catch { /* Official store's secure-file fallback; never prompt for login here. */ }
|
|
25
|
+
}
|
|
26
|
+
return jsonFile(join(root, 'acp_token.json'));
|
|
27
|
+
}
|
|
28
|
+
/** Process-local only. A native token rewrite must not make every footer refresh reauthorize. */
|
|
29
|
+
export function createCredentialCache(read) {
|
|
30
|
+
const entries = new Map();
|
|
31
|
+
return {
|
|
32
|
+
clear: () => entries.clear(),
|
|
33
|
+
async load(root, env) {
|
|
34
|
+
const key = `${root}:${env.AGY_ACP_FORCE_FILE_STORAGE ?? ''}`;
|
|
35
|
+
if (!entries.has(key)) {
|
|
36
|
+
const pending = Promise.resolve().then(() => read(root, env));
|
|
37
|
+
entries.set(key, pending);
|
|
38
|
+
void pending.then(value => { if (!value && entries.get(key) === pending) entries.delete(key); }, () => { if (entries.get(key) === pending) entries.delete(key); });
|
|
39
|
+
}
|
|
40
|
+
return entries.get(key);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const credentialCache = createCredentialCache(loadAntigravityCredentials);
|
|
45
|
+
export const clearAntigravityCredentials = () => credentialCache.clear();
|
|
46
|
+
export async function readAntigravity({ authType, env = process.env, request = fetch, loadCredentials = credentialCache.load } = {}) {
|
|
47
|
+
const root = join(env.GEMINI_HOME?.trim() || join(homedir(), '.gemini'), 'antigravity-acp');
|
|
48
|
+
authType ??= (await jsonFile(join(root, 'settings.json')))?.auth?.type;
|
|
49
|
+
if (authType !== 'oauth-personal') return antigravity({}, authType);
|
|
50
|
+
const saved = await loadCredentials(root, env);
|
|
51
|
+
if (!saved) throw new Error('Antigravity credentials unavailable');
|
|
52
|
+
const signal = AbortSignal.timeout(20000);
|
|
53
|
+
const call = async (url, body, headers = {}) => {
|
|
54
|
+
const response = await request(url, { method: 'POST', headers, body, signal });
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
if (url === 'https://oauth2.googleapis.com/token' || response.status === 401) credentialCache.clear();
|
|
57
|
+
// No response bodies, project identifiers or credentials in diagnostics.
|
|
58
|
+
const stage = url.endsWith('/token') ? 'oauth' : url.endsWith(':loadCodeAssist') ? 'account' : 'quota';
|
|
59
|
+
throw new Error(`Antigravity ${stage} request failed (HTTP ${response.status})`);
|
|
60
|
+
}
|
|
61
|
+
return response.json();
|
|
62
|
+
};
|
|
63
|
+
let token = saved.token;
|
|
64
|
+
const expiry = Date.parse(saved.expiry ?? '');
|
|
65
|
+
if (!token || !Number.isFinite(expiry) || expiry <= Date.now() + 60000) {
|
|
66
|
+
if (!saved.refresh_token || !saved.client_id || !saved.client_secret) throw new Error('Antigravity credentials expired');
|
|
67
|
+
const refreshed = await call('https://oauth2.googleapis.com/token', new URLSearchParams({
|
|
68
|
+
grant_type: 'refresh_token', refresh_token: saved.refresh_token, client_id: saved.client_id, client_secret: saved.client_secret,
|
|
69
|
+
}), { 'Content-Type': 'application/x-www-form-urlencoded' });
|
|
70
|
+
token = refreshed.access_token;
|
|
71
|
+
if (typeof token === 'string' && token) {
|
|
72
|
+
saved.token = token;
|
|
73
|
+
saved.expiry = typeof refreshed.expires_in === 'number' && Number.isFinite(refreshed.expires_in) && refreshed.expires_in > 0 && refreshed.expires_in <= 86400
|
|
74
|
+
? new Date(Date.now() + refreshed.expires_in * 1000).toISOString() : undefined;
|
|
75
|
+
}
|
|
76
|
+
// Keep refresh in memory: a quota read must not race the native adapter's credential writes.
|
|
77
|
+
}
|
|
78
|
+
if (typeof token !== 'string' || !token) throw new Error('Antigravity credentials unavailable');
|
|
79
|
+
const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': USER_AGENT };
|
|
80
|
+
const load = await call(`${PROD}/v1internal:loadCodeAssist`, JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }), headers);
|
|
81
|
+
const project = load.cloudaicompanionProject ?? saved.project_id;
|
|
82
|
+
if (typeof project !== 'string' || !project) throw new Error('Antigravity account is not onboarded');
|
|
83
|
+
// Match the official consumer ACP endpoint selection; never perform onboarding or change settings.
|
|
84
|
+
const endpoint = load.paidTier?.usesGcpTos === true ? PROD : DAILY;
|
|
85
|
+
const summary = await call(`${endpoint}/v1internal:retrieveUserQuotaSummary`, JSON.stringify({ project }), headers);
|
|
86
|
+
if (!Array.isArray(summary.groups) && !Array.isArray(summary.buckets)) throw new Error('Invalid Antigravity quota response');
|
|
87
|
+
const result = antigravity({ quotaSummary: summary, currentTier: load.currentTier, paidTier: load.paidTier }, authType);
|
|
88
|
+
if (result.availability === 'unavailable') throw new Error('Antigravity quota unavailable');
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Antigravity ACP 1.3.0: schema from its bundled Cloud Code discovery document. */
|
|
2
|
+
import { windowKey } from './normalize.mjs';
|
|
3
|
+
export function antigravity(raw = {}, authType, observedAt = new Date().toISOString()) {
|
|
4
|
+
const billingMode = authType === 'oauth-personal' ? 'subscription'
|
|
5
|
+
: ['gemini-api-key', 'vertex-ai', 'agent-platform', 'oauth-business', 'gateway'].includes(authType) ? 'non_subscription' : 'unknown';
|
|
6
|
+
const tier = raw.paidTier ?? raw.currentTier;
|
|
7
|
+
// Deliberate allowlist: never persist tokens, account email, project or routing metadata.
|
|
8
|
+
const models = Object.fromEntries(Object.entries(raw.models ?? {}).map(([id, m]) => [id, {
|
|
9
|
+
displayName: m?.displayName, quotaInfo: m?.quotaInfo ? { remainingFraction: m.quotaInfo.remainingFraction, resetTime: m.quotaInfo.resetTime } : undefined,
|
|
10
|
+
}]));
|
|
11
|
+
const out = { schemaVersion: 1, updateMode: 'snapshot', provider: 'antigravity', billingMode,
|
|
12
|
+
availability: billingMode === 'subscription' ? 'available' : billingMode === 'unknown' ? 'unknown' : 'not_applicable',
|
|
13
|
+
observedAt, plan: tier?.id ? { id: tier.id, label: tier.name ?? tier.id } : null,
|
|
14
|
+
limits: [], balances: [], _meta: { _antigravity: { authType, models } } };
|
|
15
|
+
if (billingMode !== 'subscription') return out;
|
|
16
|
+
// Use Google's summary buckets, never infer shared pools or periods from model names.
|
|
17
|
+
const summary = raw.quotaSummary ?? {};
|
|
18
|
+
const groups = Array.isArray(summary.groups) ? summary.groups : [];
|
|
19
|
+
const rows = groups.length ? groups.flatMap(g => (g.buckets ?? []).map(bucket => ({ bucket, group: g.displayName })))
|
|
20
|
+
: (summary.buckets ?? []).map(bucket => ({ bucket, group: bucket.bucketId?.startsWith('gemini-') ? 'Gemini Models'
|
|
21
|
+
: bucket.bucketId?.startsWith('3p-') ? 'Claude and GPT models' : bucket.displayName }));
|
|
22
|
+
out._meta._antigravity.quotaSummary = { groups: [] };
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
for (const { bucket: q, group } of rows) {
|
|
25
|
+
if (typeof q.bucketId !== 'string' || !q.bucketId || seen.has(q.bucketId)) continue;
|
|
26
|
+
seen.add(q.bucketId);
|
|
27
|
+
const fraction = q.remainingFraction;
|
|
28
|
+
const valid = typeof fraction === 'number' && Number.isFinite(fraction) && fraction >= 0 && fraction <= 1;
|
|
29
|
+
const usedPercent = valid ? Math.round((1 - fraction) * 10000) / 100 : null;
|
|
30
|
+
const date = typeof q.resetTime === 'string' ? new Date(q.resetTime) : null;
|
|
31
|
+
const windowSeconds = q.window === 'weekly' ? 604800 : q.window === '5h' ? 18000 : null;
|
|
32
|
+
out.limits.push({ id: `antigravity.${q.bucketId}`, label: q.displayName ?? q.bucketId, kind: 'subscription',
|
|
33
|
+
windowSeconds, key: windowKey(windowSeconds), usedPercent, resetsAt: date && Number.isFinite(date.getTime()) ? date.toISOString() : null,
|
|
34
|
+
active: q.disabled !== true,
|
|
35
|
+
status: !valid || q.disabled === true ? 'unknown' : fraction === 0 ? 'blocked' : usedPercent >= 80 ? 'warning' : 'allowed',
|
|
36
|
+
scope: { group: { id: q.bucketId.replace(/-(weekly|5h)$/, ''), display_name: group ?? 'Model quota' } } });
|
|
37
|
+
out._meta._antigravity.quotaSummary.groups.push({ displayName: group, bucketId: q.bucketId,
|
|
38
|
+
window: q.window, remainingFraction: valid ? fraction : null, resetTime: out.limits.at(-1).resetsAt, disabled: q.disabled === true });
|
|
39
|
+
}
|
|
40
|
+
for (const credit of tier?.availableCredits ?? []) {
|
|
41
|
+
const remaining = typeof credit.creditAmount === 'string' && /^\d+$/.test(credit.creditAmount) ? credit.creditAmount : null;
|
|
42
|
+
if (credit.creditType === 'GOOGLE_ONE_AI') out.balances.push({ id: 'antigravity.google-one-ai', label: 'AI credits', remaining, unit: 'credits', unlimited: false });
|
|
43
|
+
}
|
|
44
|
+
if (!out.limits.length && !out.balances.length) out.availability = 'unavailable';
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import json,hashlib
|
|
3
|
+
import argparse
|
|
4
|
+
parser=argparse.ArgumentParser(description='Regenerate exact-version quota recipes from pristine adapter package directories')
|
|
5
|
+
for name in ('codex','claude','commandcode'): parser.add_argument('--'+name,required=True,type=Path)
|
|
6
|
+
parser.add_argument('--zcode',required=False,type=Path,help='Also regenerate the zcode recipe (zcode-acp-server)')
|
|
7
|
+
options=parser.parse_args()
|
|
8
|
+
root=Path(__file__).resolve().parents[3]
|
|
9
|
+
module=(root/'axom-patch/cli/account-limits/normalize.mjs').read_text().replace('export ','')
|
|
10
|
+
helper='\nconst axomQuota = (() => {\n'+module+'\nreturn {READ,UPDATED,CAPABILITY,codex,codexEvent,claude,claudeEvent,kimi,commandcode,zcode};\n})();\nconst axomQuotaNotify = snapshot => process.stdout.write(JSON.stringify({jsonrpc:"2.0",method:axomQuota.UPDATED,params:snapshot})+"\\n");\n'
|
|
11
|
+
roots={name:getattr(options,name) for name in ('codex','claude','commandcode')}
|
|
12
|
+
if options.zcode: roots['zcode']=options.zcode
|
|
13
|
+
def patch(kind,file,version,pkg,changes,suffix='-account-limits',helper_text=helper,root_dir=None):
|
|
14
|
+
s=((root_dir or roots[kind])/file).read_text(); edits=[]
|
|
15
|
+
# The helper follows imports/shebang safely. Appending declarations executes before asynchronous requests.
|
|
16
|
+
if helper_text is not None:
|
|
17
|
+
changes.append((s[:100], (s.split('\n',1)[0]+'\n'+helper_text+s.split('\n',1)[1][:100-len(s.split('\n',1)[0])-1]) if s.startswith('#!') else helper_text+s[:100]))
|
|
18
|
+
for before,after in changes:
|
|
19
|
+
assert s.count(before)==1,(kind,before[:60],s.count(before))
|
|
20
|
+
n=s.replace(before,after)
|
|
21
|
+
edits.append(dict(file=file,before_sha256=hashlib.sha256(s.encode()).hexdigest(),after_sha256=hashlib.sha256(n.encode()).hexdigest(),before=before,after=after))
|
|
22
|
+
s=n
|
|
23
|
+
recipe=dict(id=kind+suffix,package=pkg,versions=[version],edits=edits)
|
|
24
|
+
(root/f'axom-patch/cli/{kind}{suffix}.json').write_text(json.dumps(recipe,ensure_ascii=False,indent=2)+'\n')
|
|
25
|
+
# No initialization queries: only declare the capability, and forward native events.
|
|
26
|
+
patch('codex','dist/index.js','2.2.2','@agentclientprotocol/codex-acp',[
|
|
27
|
+
('[AUTH_STATUS_META_KEY]: authStatusCapability()','[AUTH_STATUS_META_KEY]: authStatusCapability(), "axom/accountRateLimits": {...axomQuota.CAPABILITY,provider:"codex",authBillingModes:{account:"subscription",api_key:"non_subscription",external:"non_subscription",gateway:"non_subscription",none:"unknown"}}'),
|
|
28
|
+
('this.handleRateLimitsUpdated(notification.params);\n return null;', 'this.handleRateLimitsUpdated(notification.params);\n axomQuotaNotify(axomQuota.codexEvent(notification.params, this.sessionState.account?.type ?? "unknown"));\n return null;'),
|
|
29
|
+
('.onRequest("authentication/status", emptyExtensionParamsParser,', '.onRequest(axomQuota.READ, emptyExtensionParamsParser, async () => { const agent=getAgent(); const account=(await agent.runWithProcessCheck(()=>agent.codexAcpClient.getAccount())).account; if (!account) throw new Error("Not authenticated"); if (account.type !== "chatgpt") return axomQuota.codex({},account.type); const raw=await agent.runWithProcessCheck(()=>agent.codexAcpClient.getRateLimits()); const snapshot=axomQuota.codex(raw,account.type); axomQuotaNotify(snapshot); return snapshot; }).onRequest("authentication/status", emptyExtensionParamsParser,')])
|
|
30
|
+
patch('claude','dist/acp-agent.js','0.90.0','@agentclientprotocol/claude-agent-acp',[
|
|
31
|
+
('authStatus: authStatusCapability(),','authStatus: authStatusCapability(), "axom/accountRateLimits": {...axomQuota.CAPABILITY,provider:"claude",authBillingModes:{account:"subscription",api_key:"non_subscription",external:"non_subscription",gateway:"non_subscription",none:"unknown"}},'),
|
|
32
|
+
('case "rate_limit_event": {','case "rate_limit_event": {\n this.axomAccountLimits = axomQuota.claudeEvent(message.rate_limit_info, this.axomAccountLimits);\n axomQuotaNotify(this.axomAccountLimits);'),
|
|
33
|
+
('.onRequest(methods.agent.session.new,', '.onRequest(axomQuota.READ, {parse: value => value ?? {}}, async ctx => { const session=agent.sessions[ctx.params.sessionId] ?? Object.values(agent.sessions)[0]; const owned=!session?.query; const q=session?.query ?? query({prompt:"/usage",options:{cwd:ctx.params.cwd??process.cwd(),persistSession:false}}); let raw;try{const auth=fromAccountInfo(await q.accountInfo()); if(["api_key","external","gateway"].includes(auth?.kind)){raw={billing_mode:"non_subscription",rate_limits_available:false};}else{raw=await q.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET({skipBehaviors:true});raw.subscription_type??=auth?.account?.plan;raw.billing_mode=auth?.kind==="account"?"subscription":"unknown";}}finally{if(owned)q.close();} agent.axomAccountLimits=axomQuota.claude({subscription_type:raw.subscription_type,rate_limits_available:raw.rate_limits_available,rate_limits:raw.rate_limits,billing_mode:raw.billing_mode}); if(agent.axomAccountLimits.billingMode==="unknown" || agent.axomAccountLimits.availability==="unavailable") throw new Error("Failed to fetch usage"); axomQuotaNotify(agent.axomAccountLimits); return agent.axomAccountLimits; })\n .onRequest(methods.agent.session.new,')])
|
|
34
|
+
patch('commandcode','dist/cli.mjs','1.82.1','command-code',[
|
|
35
|
+
# 1.82.1 renamed the handler map (u/dn -> m/hn) and gave INITIALIZE an explicit arrow body; the
|
|
36
|
+
# read method still goes first in the same map.
|
|
37
|
+
('const m={[hn.INITIALIZE]:e=>handleInitialize(e),[hn.AUTHENTICATE]','const m={["_axom/account/rateLimits/read"]:async()=>{const raw=await fetchUsageData2(); if(raw.errors.length) throw new Error("Failed to fetch usage"); const snapshot=axomQuota.commandcode({credits:raw.credits,subscription:raw.subscription,orgLimits:raw.whoami?.orgLimits??[],monthly:{limit:raw.subscription?.data?.status==="active"?(getPlanInfo({planId:raw.subscription?.data?.planId??""})?.monthlyCredits??null):null}}); axomQuotaNotify(snapshot); return snapshot;},[hn.INITIALIZE]:e=>handleInitialize(e),[hn.AUTHENTICATE]'),
|
|
38
|
+
('agentCapabilities:{loadSession:!0,promptCapabilities:', 'agentCapabilities:{_meta:{"axom/accountRateLimits":{...axomQuota.CAPABILITY,provider:"commandcode"}},loadSession:!0,promptCapabilities:')])
|
|
39
|
+
if options.zcode: patch('zcode','dist/index.js','0.69.4','zcode-acp-server',[
|
|
40
|
+
(' .onRequest("account/usage_stats", z.object({}).passthrough(), () => accountUsageStats())',
|
|
41
|
+
' .onRequest(axomQuota.READ, z.object({}).passthrough(), async () => {\n'
|
|
42
|
+
' const { buildAccountProviderConfig, codingPlanRequestAuthFor } = await import("./config/account-provider.js");\n'
|
|
43
|
+
' const { resolveQuotaHost } = await import("./quota/client.js");\n'
|
|
44
|
+
' let apiKey = null, family = "";\n'
|
|
45
|
+
' for (const [providerId, provider] of Object.entries(buildAccountProviderConfig()?.providers ?? {})) {\n'
|
|
46
|
+
' if (!provider?.access?.entitled) continue;\n'
|
|
47
|
+
' const auth = codingPlanRequestAuthFor(providerId);\n'
|
|
48
|
+
' if (auth?.apiKey) { apiKey = auth.apiKey; family = providerId; break; }\n'
|
|
49
|
+
' }\n'
|
|
50
|
+
' if (!apiKey) throw new Error("No entitled GLM coding plan: sign in once in the ZCode desktop app");\n'
|
|
51
|
+
' const resp = await fetch(resolveQuotaHost(family.startsWith("account:zai") ? "https://api.z.ai" : "") + "/api/monitor/usage/quota/limit", { headers: { Accept: "application/json, text/plain, */*", Authorization: "Bearer " + apiKey }, signal: AbortSignal.timeout(8000) });\n'
|
|
52
|
+
' const text = await resp.text();\n'
|
|
53
|
+
' let json = null; try { json = text ? JSON.parse(text) : null; } catch {}\n'
|
|
54
|
+
' if (resp.status === 429 || !json || json.success !== true) throw new Error("GLM quota read failed" + (json?.msg ? ": " + json.msg : " (HTTP " + resp.status + ")"));\n'
|
|
55
|
+
' const snapshot = axomQuota.zcode({ provider: family, level: json.data?.level, limits: json.data?.limits });\n'
|
|
56
|
+
' axomQuotaNotify(snapshot);\n'
|
|
57
|
+
' return snapshot;\n'
|
|
58
|
+
' })\n'
|
|
59
|
+
' .onRequest("account/usage_stats", z.object({}).passthrough(), () => accountUsageStats())'),
|
|
60
|
+
(' .onRequest("initialize", (ctx) => server.initialize(ctx.params, ctx.client))',
|
|
61
|
+
' .onRequest("initialize", async (ctx) => { const result = await server.initialize(ctx.params, ctx.client); const caps = result.agentCapabilities ?? {}; result.agentCapabilities = { ...caps, _meta: { ...caps._meta, "axom/accountRateLimits": { ...axomQuota.CAPABILITY, provider: "zcode" } } }; return result; })')])
|
|
62
|
+
models_fn='''function axomFreshInstallModels() {
|
|
63
|
+
const fallback = { providerId: DEFAULT_PROVIDER_ID, providerName: DEFAULT_PROVIDER_NAME, modelId: DEFAULT_MODEL_ID };
|
|
64
|
+
try {
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const [pid, p] of Object.entries(axomAccountProviders()?.providers ?? {})) {
|
|
67
|
+
if (!p?.access?.entitled || !axomPlanAuth(pid)?.apiKey) continue;
|
|
68
|
+
const providerId = configProviderIdFor(pid);
|
|
69
|
+
for (const modelId of p.builtinModelIds ?? []) {
|
|
70
|
+
if (out.some((m) => m.providerId === providerId && m.modelId === modelId)) continue;
|
|
71
|
+
out.push({ providerId, providerName: providerId, modelId });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (out.length === 0) return [fallback];
|
|
75
|
+
return out.some((m) => m.providerId === fallback.providerId && m.modelId === fallback.modelId)
|
|
76
|
+
? [fallback, ...out.filter((m) => !(m.providerId === fallback.providerId && m.modelId === fallback.modelId))]
|
|
77
|
+
: [fallback, ...out];
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return [fallback];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
'''
|
|
84
|
+
if options.zcode: patch('zcode-models','dist/config/options.js','0.69.3','zcode-acp-server',[
|
|
85
|
+
('import { configProviderIdFor } from "./account-provider.js";',
|
|
86
|
+
'import { buildAccountProviderConfig as axomAccountProviders, codingPlanRequestAuthFor as axomPlanAuth, configProviderIdFor } from "./account-provider.js";'),
|
|
87
|
+
('/** Read the config.json contents (UTF-8). Throws on read/parse failure. */',
|
|
88
|
+
models_fn+'\n/** Read the config.json contents (UTF-8). Throws on read/parse failure. */'),
|
|
89
|
+
(''' if (out.length === 0 && Object.keys(cfg.provider ?? {}).length === 0) {
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
providerId: DEFAULT_PROVIDER_ID,
|
|
93
|
+
providerName: DEFAULT_PROVIDER_NAME,
|
|
94
|
+
modelId: DEFAULT_MODEL_ID,
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
}''',
|
|
98
|
+
' if (out.length === 0 && Object.keys(cfg.provider ?? {}).length === 0) {\n return axomFreshInstallModels();\n }'),
|
|
99
|
+
(''' if (out.length > 0)
|
|
100
|
+
return out;
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
providerId: DEFAULT_PROVIDER_ID,
|
|
104
|
+
providerName: DEFAULT_PROVIDER_NAME,
|
|
105
|
+
modelId: DEFAULT_MODEL_ID,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
}''',
|
|
110
|
+
' if (out.length > 0)\n return out;\n return axomFreshInstallModels();\n }\n}'),
|
|
111
|
+
],suffix='',helper_text=None,root_dir=options.zcode)
|
|
112
|
+
runtime=root/'axom-patch/cli/runtime.json'; data=json.loads(runtime.read_text())
|
|
113
|
+
for kind in roots:
|
|
114
|
+
name=kind+'-account-limits.json'
|
|
115
|
+
if name not in data['package_patches']:data['package_patches'].append(name)
|
|
116
|
+
if options.zcode:
|
|
117
|
+
name='zcode-models.json'
|
|
118
|
+
if name not in data['package_patches']:data['package_patches'].append(name)
|
|
119
|
+
runtime.write_text(json.dumps(data,indent=2)+'\n')
|
|
120
|
+
print('Generated exact-version recipes'+(' including zcode' if options.zcode else ''))
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Verified Kimi 2.1.1 ACP extension shim. The web usage route is invoked ONLY for an explicit read. */
|
|
2
|
+
import {spawn} from 'node:child_process';
|
|
3
|
+
import {createInterface} from 'node:readline';
|
|
4
|
+
import {readFile} from 'node:fs/promises';
|
|
5
|
+
import {createServer} from 'node:net';
|
|
6
|
+
import {homedir} from 'node:os';
|
|
7
|
+
import {join} from 'node:path';
|
|
8
|
+
import {READ, UPDATED, CAPABILITY, kimi} from './normalize.mjs';
|
|
9
|
+
const [binary,...args]=process.argv.slice(2);
|
|
10
|
+
const child=spawn(binary,args,{stdio:['pipe','pipe','inherit'],env:process.env});
|
|
11
|
+
const write=value=>process.stdout.write(JSON.stringify(value)+'\n');
|
|
12
|
+
let initializeId; let reading=false; let selectedModel;
|
|
13
|
+
async function usage() {
|
|
14
|
+
if(reading) throw new Error('A usage refresh is already in progress'); reading=true;
|
|
15
|
+
let web;
|
|
16
|
+
try {
|
|
17
|
+
const port=await new Promise((resolve,reject)=>{const server=createServer();server.on('error',reject);server.listen(0,'127.0.0.1',()=>{const port=server.address().port;server.close(()=>resolve(port));});});
|
|
18
|
+
web=spawn(binary,['web','--port',String(port),'--no-open'],{stdio:'ignore',env:process.env});
|
|
19
|
+
let spawnError;web.on('error',error=>{spawnError=error;});
|
|
20
|
+
const controller=new AbortController();const timeout=setTimeout(()=>controller.abort(),20000);
|
|
21
|
+
try {
|
|
22
|
+
for(let attempt=0;attempt<60;attempt++) {
|
|
23
|
+
if(spawnError)throw spawnError;
|
|
24
|
+
if(web.exitCode!==null) throw new Error('The Kimi usage service exited');
|
|
25
|
+
try {
|
|
26
|
+
const token=(await readFile(join(process.env.KIMI_CODE_HOME??join(homedir(),'.kimi-code'),'server.token'),'utf8')).trim();
|
|
27
|
+
const request=async path=>{const response=await fetch(`http://127.0.0.1:${port}/api/v1/${path}`,{headers:{Authorization:`Bearer ${token}`},signal:controller.signal});if(!response.ok)throw new Error('Failed to fetch usage');return response.json();};
|
|
28
|
+
// Native catalog resolves credentials, including environment overrides, without exposing keys.
|
|
29
|
+
const config=(await request('config')).data;
|
|
30
|
+
const providers=(await request('providers')).data?.items??[];
|
|
31
|
+
const model=config?.models?.[selectedModel??config?.default_model];
|
|
32
|
+
const provider=providers.find(p=>p.id===(model?.providerId??model?.provider_id??model?.provider??config?.default_provider));
|
|
33
|
+
if(provider?.has_api_key===true || (provider && provider.id!=='managed:kimi-code'))return {schemaVersion:1,provider:'kimi',billingMode:'non_subscription',availability:'not_applicable',observedAt:new Date().toISOString(),plan:null,limits:[],balances:[],_meta:{_kimi:{credential:'api'}}};
|
|
34
|
+
if(!provider || provider.status!=='connected')throw new Error('Failed to fetch usage');
|
|
35
|
+
const response=await fetch(`http://127.0.0.1:${port}/api/v1/oauth/usage`,{headers:{Authorization:`Bearer ${token}`},signal:controller.signal});
|
|
36
|
+
if(!response.ok) throw new Error('Failed to fetch usage');
|
|
37
|
+
const body=await response.json();
|
|
38
|
+
if(body.data?.kind!=='ok') throw new Error(body.data?.message??'Failed to fetch usage');
|
|
39
|
+
return kimi(body.data.quota);
|
|
40
|
+
} catch(error) {
|
|
41
|
+
if(controller.signal.aborted || (error.message && !['fetch failed'].includes(error.message) && error.code!=='ENOENT')) throw error;
|
|
42
|
+
}
|
|
43
|
+
await new Promise(resolve=>setTimeout(resolve,150));
|
|
44
|
+
}
|
|
45
|
+
throw new Error('The Kimi usage service did not start');
|
|
46
|
+
} finally {clearTimeout(timeout);}
|
|
47
|
+
} finally { reading=false; if(web){web.kill('SIGTERM');setTimeout(()=>{if(web.exitCode===null)web.kill('SIGKILL');},2000).unref();} }
|
|
48
|
+
}
|
|
49
|
+
createInterface({input:child.stdout}).on('line',line=>{
|
|
50
|
+
try {const message=JSON.parse(line);if(message.id===initializeId&&message.result){message.result.agentCapabilities??={};message.result.agentCapabilities._meta??={};message.result.agentCapabilities._meta['axom/accountRateLimits']={...CAPABILITY,provider:'kimi'};write(message);return;}}catch{}
|
|
51
|
+
process.stdout.write(line+'\n');
|
|
52
|
+
});
|
|
53
|
+
createInterface({input:process.stdin}).on('line',line=>{
|
|
54
|
+
let message;try{message=JSON.parse(line);}catch{child.stdin.write(line+'\n');return;}
|
|
55
|
+
if(message.method==='initialize')initializeId=message.id;
|
|
56
|
+
if(message.method==='session/set_config_option' && message.params?.configId==='model')selectedModel=message.params.value;
|
|
57
|
+
if(message.method==='session/set_model')selectedModel=message.params.modelId;
|
|
58
|
+
if(message.method===READ&&message.id!==undefined){void usage().then(result=>{write({jsonrpc:'2.0',id:message.id,result});write({jsonrpc:'2.0',method:UPDATED,params:result});},()=>write({jsonrpc:'2.0',id:message.id,error:{code:-32603,message:'Failed to fetch usage'}}));return;}
|
|
59
|
+
child.stdin.write(line+'\n');
|
|
60
|
+
});
|
|
61
|
+
process.stdin.on('end',()=>child.stdin.end());
|
|
62
|
+
for(const signal of ['SIGTERM','SIGINT'])process.on(signal,()=>child.kill(signal));
|
|
63
|
+
child.on('error',()=>process.exitCode=1);
|
|
64
|
+
child.on('exit',code=>process.exit(code??1));
|