@platless/axom-cli 0.3.13 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/axom-patch/cli/README.md +30 -8
- 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-subagent-subwork.json +23 -0
- package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +5 -1
- package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +1 -1
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +20 -8
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +2 -1
- package/dist/axom-patch/cli/qoder-foreground-output.md +4 -3
- package/dist/axom-patch/cli/qoder-steering.json +15 -15
- package/dist/axom-patch/cli/qoder-subagent-origin.json +21 -21
- package/dist/axom-patch/cli/runtime.json +34 -8
- 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 +33 -6
- 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/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 +2163 -611
- package/dist/workstation/acp/acpClient.js +60 -497
- package/dist/workstation/acp/adoption.js +10 -396
- 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/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 +76 -160
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +20 -3
- package/dist/workstation/acp/permission.js +42 -10
- 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 +1 -1
- package/dist/workstation/acp/registry/catalog.js +154 -32
- package/dist/workstation/acp/registry/launch.js +61 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +114 -0
- package/dist/workstation/acp/terminalLogin.js +16 -42
- 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/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/agentFlow.js +103 -0
- package/dist/workstation/agentSessions.js +1324 -474
- package/dist/workstation/channels/agent.js +7 -5
- package/dist/workstation/channels/computer.js +69 -21
- package/dist/workstation/channels/computerExecs.js +200 -0
- package/dist/workstation/channels/management.js +84 -4
- package/dist/workstation/daemon.js +288 -7
- package/dist/workstation/daemonctl.js +28 -2
- 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/{fileOps.js → files/text.js} +117 -38
- 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/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +48 -4
- package/dist/workstation/protocol/common.js +30 -0
- package/dist/workstation/protocol/computer.js +15 -4
- package/dist/workstation/protocol/management.js +50 -2
- package/dist/workstation/qoderAdapter/backgroundProcess.js +18 -6
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +78 -147
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +372 -0
- package/dist/workstation/qoderAdapter/callbackContext.js +76 -0
- package/dist/workstation/qoderAdapter/foregroundOutput.js +15 -9
- package/dist/workstation/qoderAdapter/history.js +1 -3
- package/dist/workstation/qoderAdapter/main.js +4 -2
- package/dist/workstation/qoderAdapter/sdkDriver.js +1 -2
- package/dist/workstation/qoderAdapter/sdkRuntime.js +93 -0
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +145 -0
- package/dist/workstation/qoderAdapter/session.js +1 -1
- package/dist/workstation/qoderAdapter/subwork.js +15 -7
- package/dist/workstation/systemMetrics.js +110 -1
- package/dist/workstation/tools/client.js +9 -1
- package/dist/workstation/tools/info.js +50 -0
- package/dist/workstation/tools/keeperProcess.js +15 -5
- package/dist/workstation/tools/router.js +55 -0
- package/dist/workstation/tools/terminalRouter.js +222 -0
- package/dist/workstation/tools/validation.js +37 -1
- package/dist/workstation/update/coordinator.js +3 -0
- package/dist/workstation/update/ptyPreflight.js +21 -0
- package/dist/workstation/update/taskRegistry.js +105 -11
- package/package.json +10 -7
- package/dist/workstation/acp/acpSession.js +0 -1475
- package/dist/workstation/acp/contract.js +0 -499
- package/dist/workstation/acp/draftInterceptor.js +0 -163
- package/dist/workstation/acp/sessionScope.js +0 -19
- package/dist/workstation/filePaths.js +0 -174
- package/dist/workstation/fileWorker.js +0 -81
- package/dist/workstation/fileWorkerClient.js +0 -121
- package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
- package/dist/workstation/tools/core.js +0 -50
- package/dist/workstation/tools/files.js +0 -525
- package/dist/workstation/tools/terminal.js +0 -214
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/** Account quota normalization. No I/O: passive events must never trigger a query. */
|
|
2
|
+
export const READ = '_axom/account/rateLimits/read';
|
|
3
|
+
export const UPDATED = '_axom/account/rateLimits/updated';
|
|
4
|
+
export const CAPABILITY = { version: 2, read: true, updated: true };
|
|
5
|
+
const percent = v => typeof v === 'number' && Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : null;
|
|
6
|
+
const finite = v => { if (v == null || typeof v === 'boolean') return null; const n = typeof v === 'number' ? v : Number(v); return Number.isFinite(n) ? n : null; };
|
|
7
|
+
const time = (v, milliseconds = false) => {
|
|
8
|
+
if (v == null || v === 0) return null;
|
|
9
|
+
const d = new Date(typeof v === 'number' ? v * (milliseconds ? 1 : 1000) : v);
|
|
10
|
+
return Number.isFinite(d.getTime()) ? d.toISOString() : null;
|
|
11
|
+
};
|
|
12
|
+
const status = v => ({ allowed: 'allowed', allowed_warning: 'warning', rejected: 'blocked', normal: 'allowed' }[v] ?? 'unknown');
|
|
13
|
+
/** Canonical window key rides the snapshot; the UI owns the key → display-label translation.
|
|
14
|
+
* Windows without a canonical key keep their vendor content untouched. */
|
|
15
|
+
export const windowKey = (seconds, period) => period === 'month' ? 'monthly' : seconds === 18000 ? 'five_hour' : seconds === 604800 ? 'weekly' : null;
|
|
16
|
+
const 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) } });
|
|
17
|
+
export function codex(raw, authType = 'chatgpt', observedAt) {
|
|
18
|
+
const out = base('codex', ['chatgpt', 'chatgptAuthTokens'].includes(authType) ? 'subscription' : ['apiKey','apikey','amazonBedrock'].includes(authType) ? 'non_subscription' : 'unknown', raw, observedAt);
|
|
19
|
+
if (out.billingMode !== 'subscription') return out;
|
|
20
|
+
const buckets = Object.entries(raw.rateLimitsByLimitId ?? {});
|
|
21
|
+
if (!buckets.length && raw.rateLimits) buckets.push([raw.rateLimits.limitId ?? 'codex', raw.rateLimits]);
|
|
22
|
+
for (const [id, bucket] of buckets) {
|
|
23
|
+
if (!bucket) continue;
|
|
24
|
+
if (bucket.planType) out.plan = { id: bucket.planType, label: bucket.planType };
|
|
25
|
+
for (const name of ['primary', 'secondary']) {
|
|
26
|
+
const w = bucket[name]; if (!w) continue;
|
|
27
|
+
const seconds = w.windowDurationMins == null ? null : w.windowDurationMins * 60;
|
|
28
|
+
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 } });
|
|
29
|
+
}
|
|
30
|
+
if (bucket.credits) out.balances.push({ id: `${id}.credits`, label: 'Credits', remaining: bucket.credits.balance ?? null, unit: 'credits', unlimited: bucket.credits.unlimited === true });
|
|
31
|
+
}
|
|
32
|
+
// Account IDs must not escape via provider extensions.
|
|
33
|
+
delete out._meta._codex.accountId;
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/** App-server notifications describe changed buckets, not the complete account read. */
|
|
37
|
+
export function codexEvent(raw, authType, observedAt) {
|
|
38
|
+
return { ...codex(raw, authType, observedAt), updateMode: 'delta' };
|
|
39
|
+
}
|
|
40
|
+
export function claude(raw, observedAt) {
|
|
41
|
+
const mode = raw.billing_mode ?? (raw.subscription_type || raw.rate_limits_available === true ? 'subscription' : 'unknown');
|
|
42
|
+
const out = base('claude', mode, raw, observedAt);
|
|
43
|
+
out.plan = raw.subscription_type ? { id: raw.subscription_type, label: raw.subscription_type } : null;
|
|
44
|
+
if (mode === 'subscription' && !raw.rate_limits_available) out.availability = 'unavailable';
|
|
45
|
+
const limits = raw.rate_limits ?? {};
|
|
46
|
+
const rows = Array.isArray(limits.limits) ? limits.limits : [];
|
|
47
|
+
if (rows.length) {
|
|
48
|
+
rows.forEach((w, i) => {
|
|
49
|
+
const seconds = w.group === 'session' ? 18000 : w.group === 'weekly' ? 604800 : null;
|
|
50
|
+
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 });
|
|
51
|
+
});
|
|
52
|
+
} else for (const [id, seconds] of [['five_hour', 18000], ['seven_day', 604800], ['seven_day_opus',604800], ['seven_day_sonnet',604800]]) {
|
|
53
|
+
const w = limits[id]; if (!w) continue;
|
|
54
|
+
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 });
|
|
55
|
+
}
|
|
56
|
+
const spend=limits.spend;
|
|
57
|
+
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});
|
|
58
|
+
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});
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
export function claudeEvent(raw, previous = null, observedAt) {
|
|
62
|
+
const out = previous ? structuredClone(previous) : base('claude',raw.rateLimitType?'subscription':'unknown',{},observedAt);
|
|
63
|
+
out.updateMode = 'delta';
|
|
64
|
+
out.balances = [];
|
|
65
|
+
out.observedAt = observedAt ?? new Date().toISOString();
|
|
66
|
+
// Other sessions may have refreshed the account since this process's cached read.
|
|
67
|
+
// Send only facts from this event; the server owns the merged account observation.
|
|
68
|
+
out.plan = null;
|
|
69
|
+
out._meta = { _claude: { rateLimitEvent: raw } };
|
|
70
|
+
const seconds = raw.rateLimitType === 'five_hour' ? 18000 : raw.rateLimitType?.startsWith('seven_day') ? 604800 : null;
|
|
71
|
+
if (seconds !== null) {
|
|
72
|
+
const id = `claude.${raw.rateLimitType}`;
|
|
73
|
+
const scoped = !['five_hour','seven_day'].includes(raw.rateLimitType);
|
|
74
|
+
const prior=out.limits.find(w => w.windowSeconds===seconds && JSON.stringify(w.scope??null)===JSON.stringify(scoped ? {model:raw.rateLimitType.split('_').at(-1)}:null));
|
|
75
|
+
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 };
|
|
76
|
+
// Event utilization is a ratio, unlike get_usage's percentage. Match the existing window without duplicating it.
|
|
77
|
+
out.limits = [row];
|
|
78
|
+
} else out.limits = [];
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
export function kimi(raw, observedAt) {
|
|
82
|
+
const quota = raw.quota ?? raw;
|
|
83
|
+
const out = base('kimi','subscription',quota,observedAt);
|
|
84
|
+
for (const [id, seconds] of [['limit5h',18000],['limit7d',604800],['monthTotal',null]]) {
|
|
85
|
+
const w = quota.usages?.[id]; if (!w) continue;
|
|
86
|
+
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' });
|
|
87
|
+
}
|
|
88
|
+
const wallet = quota.extraUsage;
|
|
89
|
+
if (wallet) out.balances.push({ id:'kimi.extra',label:'Extra usage',remaining:String(wallet.balanceCents),unit:'money',currency:wallet.currency,exponent:2 });
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/** GLM quota `percentage` is the USED share (measured: 5% for 58/1000); counters win when present.
|
|
93
|
+
* Counter-derived shares are rounded to 2 decimals — 100-94.2 keeps fp noise otherwise. */
|
|
94
|
+
const glmPercent = limit => {
|
|
95
|
+
const used = finite(limit.currentValue), remaining = finite(limit.remaining), total = finite(limit.usage);
|
|
96
|
+
const cap = remaining !== null && used !== null ? remaining + used : total;
|
|
97
|
+
const round2 = v => v == null ? null : Math.round(v * 100) / 100;
|
|
98
|
+
if (cap !== null && cap > 0 && remaining !== null && remaining >= 0 && remaining <= cap) return round2(100 - percent(remaining / cap * 100));
|
|
99
|
+
if (cap !== null && cap > 0 && used !== null && used >= 0 && used <= cap) return round2(percent(used / cap * 100));
|
|
100
|
+
return percent(limit.percentage);
|
|
101
|
+
};
|
|
102
|
+
export function zcode(raw, observedAt) {
|
|
103
|
+
// Raw: the GLM quota endpoint's data block — { level, limits: [{ type, number, usage,
|
|
104
|
+
// currentValue, remaining, percentage, nextResetTime(epoch ms), usageDetails }] }. Which windows
|
|
105
|
+
// exist is plan-dependent (measured pro plan: 5h prompts + a tool/MCP allowance; no weekly row —
|
|
106
|
+
// a `number: 7` window maps when a plan returns one, and nothing is invented when it doesn't).
|
|
107
|
+
const limits = Array.isArray(raw?.limits) ? raw.limits : [];
|
|
108
|
+
const level = typeof raw?.level === 'string' && raw.level ? raw.level : null;
|
|
109
|
+
const out = base('zcode', limits.length ? 'subscription' : 'unknown', raw, observedAt);
|
|
110
|
+
out.plan = level ? { id: level, label: level } : null;
|
|
111
|
+
let tools = 0;
|
|
112
|
+
limits.forEach((limit, i) => {
|
|
113
|
+
if (!limit || typeof limit !== 'object') return;
|
|
114
|
+
const resetsAt = time(limit.nextResetTime, true);
|
|
115
|
+
if (limit.type === 'TOKENS_LIMIT') {
|
|
116
|
+
// GLM window convention: number 5 → the 5-hour prompt window, 7 → weekly.
|
|
117
|
+
// Ids mirror the adapter's own /quota keys (token_5h / token_week).
|
|
118
|
+
const seconds = limit.number === 5 ? 18000 : limit.number === 7 ? 604800 : null;
|
|
119
|
+
const label = limit.number === 5 ? '5h' : limit.number === 7 ? 'Week' : `token ${limit.number ?? i}`;
|
|
120
|
+
const id = limit.number === 5 ? 'zcode.token_5h' : limit.number === 7 ? 'zcode.token_week' : `zcode.token_${limit.number ?? i}`;
|
|
121
|
+
out.limits.push({ id, label, kind: 'subscription', windowSeconds: seconds, key: windowKey(seconds), usedPercent: glmPercent(limit), resetsAt, status: 'unknown' });
|
|
122
|
+
} else if (limit.type === 'TIME_LIMIT' || limit.type === 'MCP_LIMIT') {
|
|
123
|
+
// Built-in tool/MCP allowance: absolute counters (unit 'calls'); per-model detail stays in _meta.
|
|
124
|
+
const used = finite(limit.currentValue), total = finite(limit.usage), remaining = finite(limit.remaining);
|
|
125
|
+
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' });
|
|
126
|
+
tools++;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
export function commandcode(raw, observedAt) {
|
|
132
|
+
const subscription = raw.subscription?.data ?? raw.subscription;
|
|
133
|
+
const windows = raw.credits?.windowLimits ?? raw.windowLimits;
|
|
134
|
+
const subscribed = ['active','trialing','past_due'].includes(subscription?.status) || windows?.limited === true;
|
|
135
|
+
const mode = subscribed ? 'subscription' : subscription || windows ? 'non_subscription' : 'unknown';
|
|
136
|
+
const out = base('commandcode',mode,raw,observedAt);
|
|
137
|
+
out.plan = subscription?.planId ? { id:subscription.planId,label:subscription.planId } : null;
|
|
138
|
+
for (const [id, seconds] of [['fiveHour',18000],['weekly',604800]]) {
|
|
139
|
+
const w=windows?.[id]; if (!w) continue;
|
|
140
|
+
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' });
|
|
141
|
+
}
|
|
142
|
+
const credits=raw.credits?.credits ?? raw.credits;
|
|
143
|
+
if (subscribed && credits?.monthlyCredits != null) {
|
|
144
|
+
const cap=raw.monthly?.limit;
|
|
145
|
+
const known=typeof cap==='number' && Number.isFinite(cap) && cap>0;
|
|
146
|
+
const used=known ? Math.max(0,cap-credits.monthlyCredits) : null;
|
|
147
|
+
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'}:{})});
|
|
148
|
+
}
|
|
149
|
+
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' });
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "claude-account-limits",
|
|
3
|
+
"package": "@agentclientprotocol/claude-agent-acp",
|
|
4
|
+
"versions": [
|
|
5
|
+
"0.90.0"
|
|
6
|
+
],
|
|
7
|
+
"edits": [
|
|
8
|
+
{
|
|
9
|
+
"file": "dist/acp-agent.js",
|
|
10
|
+
"before_sha256": "e3bbdc3399a2d702839d6e350b5b798fbe6e61d9ef916d38ccc1bc3abab15e3e",
|
|
11
|
+
"after_sha256": "9f1ffd0c70ef4354532a4393c134046dafbfedd80f331bbc91f4219d48928ea9",
|
|
12
|
+
"before": "authStatus: authStatusCapability(),",
|
|
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
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"file": "dist/acp-agent.js",
|
|
17
|
+
"before_sha256": "9f1ffd0c70ef4354532a4393c134046dafbfedd80f331bbc91f4219d48928ea9",
|
|
18
|
+
"after_sha256": "2a10073f12b1648a2b7d6debc092d7352ede7c2cc827c4654da25dac8cf9df18",
|
|
19
|
+
"before": "case \"rate_limit_event\": {",
|
|
20
|
+
"after": "case \"rate_limit_event\": {\n this.axomAccountLimits = axomQuota.claudeEvent(message.rate_limit_info, this.axomAccountLimits);\n axomQuotaNotify(this.axomAccountLimits);"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"file": "dist/acp-agent.js",
|
|
24
|
+
"before_sha256": "2a10073f12b1648a2b7d6debc092d7352ede7c2cc827c4654da25dac8cf9df18",
|
|
25
|
+
"after_sha256": "ca788a13276c07958b8ec4eee9be5debc030534d879ce58994319adcfeb8123e",
|
|
26
|
+
"before": ".onRequest(methods.agent.session.new,",
|
|
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
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"file": "dist/acp-agent.js",
|
|
31
|
+
"before_sha256": "ca788a13276c07958b8ec4eee9be5debc030534d879ce58994319adcfeb8123e",
|
|
32
|
+
"after_sha256": "3221003e3efc74af15498c162fb0e1c62267a1de12476c85e3ad95a865fb1ff6",
|
|
33
|
+
"before": "import { agent as acpAgent, CreateElicitationResponse, methods, ndJsonStream, RequestError, } from \"",
|
|
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
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "codex-account-limits",
|
|
3
|
+
"package": "@agentclientprotocol/codex-acp",
|
|
4
|
+
"versions": [
|
|
5
|
+
"2.2.2"
|
|
6
|
+
],
|
|
7
|
+
"edits": [
|
|
8
|
+
{
|
|
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\"}}"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"file": "dist/index.js",
|
|
17
|
+
"before_sha256": "0181983c3a3526d7464b52b3a4ee5680968fb6ba9053878dbb41a3e23e074ff3",
|
|
18
|
+
"after_sha256": "27204fec9d83e4b898b8a3d9b66ecd51f1d6606f7835635012d690874b7f4d16",
|
|
19
|
+
"before": "this.handleRateLimitsUpdated(notification.params);\n return null;",
|
|
20
|
+
"after": "this.handleRateLimitsUpdated(notification.params);\n axomQuotaNotify(axomQuota.codexEvent(notification.params, this.sessionState.account?.type ?? \"unknown\"));\n return null;"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"file": "dist/index.js",
|
|
24
|
+
"before_sha256": "27204fec9d83e4b898b8a3d9b66ecd51f1d6606f7835635012d690874b7f4d16",
|
|
25
|
+
"after_sha256": "baec90f7269d2f33412f9769dfcede164ad6d1e500d1898df81718bb88451268",
|
|
26
|
+
"before": ".onRequest(\"authentication/status\", emptyExtensionParamsParser,",
|
|
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
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"file": "dist/index.js",
|
|
31
|
+
"before_sha256": "baec90f7269d2f33412f9769dfcede164ad6d1e500d1898df81718bb88451268",
|
|
32
|
+
"after_sha256": "157347843376715825331915c7edf4f7bd8d0ced92f1e54cbafea4e16d6155fc",
|
|
33
|
+
"before": "#!/usr/bin/env node\nimport { createRequire as __createRequire } from 'module'; const require = __cre",
|
|
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
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "commandcode-account-limits",
|
|
3
|
+
"package": "command-code",
|
|
4
|
+
"versions": [
|
|
5
|
+
"1.82.1"
|
|
6
|
+
],
|
|
7
|
+
"edits": [
|
|
8
|
+
{
|
|
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]"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"file": "dist/cli.mjs",
|
|
17
|
+
"before_sha256": "7e1b01282634c865075ec5bc5758cf432b464ee85c7219713db49760ff7abc84",
|
|
18
|
+
"after_sha256": "5a90e9497175b92d55d0c32874a4724e941a3f534e83a6f8c168a932dc2e7024",
|
|
19
|
+
"before": "agentCapabilities:{loadSession:!0,promptCapabilities:",
|
|
20
|
+
"after": "agentCapabilities:{_meta:{\"axom/accountRateLimits\":{...axomQuota.CAPABILITY,provider:\"commandcode\"}},loadSession:!0,promptCapabilities:"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"file": "dist/cli.mjs",
|
|
24
|
+
"before_sha256": "5a90e9497175b92d55d0c32874a4724e941a3f534e83a6f8c168a932dc2e7024",
|
|
25
|
+
"after_sha256": "66b0418156ebb0edce2ca1e5803665383e6e0f7d69a873f5b37efdb2a5c88534",
|
|
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
|
+
"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
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Nightly 任务:catalog agent 版本升级
|
|
2
|
+
|
|
3
|
+
供 AI 周期执行的任务说明。**默认产出是把 catalog / runtime.json 的钉版本升到上游最新**:
|
|
4
|
+
对每个有新版本的 agent,判定新版本是否已解决补丁问题(或官方提供了可替代方案),已解决走下线,
|
|
5
|
+
没解决就把补丁移植到新版本并把钉版本升上去。「只分析不升级」是例外,只允许用在判定不了、
|
|
6
|
+
且报告里写明了缺什么证据/设备的情况。
|
|
7
|
+
|
|
8
|
+
与每周一 09:00 的 [`scripts/check-upstream-patches.py`](../../scripts/check-upstream-patches.py)
|
|
9
|
+
(launchd,覆盖全部组件、只产报告)互补:本任务只管 **CLI catalog / adapter 补丁层**,但执行升级。
|
|
10
|
+
|
|
11
|
+
## 钉版本规则(本任务的前提)
|
|
12
|
+
|
|
13
|
+
- **Axom 给适配器打了补丁(package_patches 配方 / native_extensions 包装)⇒ 必须钉版本**。
|
|
14
|
+
不钉的话 registry/npm 一发新版,Install/Update 就装上一个补丁不生效的包。
|
|
15
|
+
- **没打补丁 ⇒ 不钉**,版本跟 registry 走(用户自己的 Update 决定)。
|
|
16
|
+
- **例外:registry 没有这个 agent**(supplement:kimi-code、command-code、zcode-acp、hermes)⇒
|
|
17
|
+
版本天然由我们的 catalog 决定,永远钉着。
|
|
18
|
+
- scan/Update 的比对语义:roster 上报的 `version`/`registryVersion` 对钉住条目就是**生效(钉住)
|
|
19
|
+
版本**,设置页 `updatable` 因此永远对着 catalog 钉版本比,不对着官方 registry 比——这是特性,
|
|
20
|
+
不要"修"。
|
|
21
|
+
- `catalog.test.ts > patched agents stay pinned` 是机械守卫:新增补丁不补钉会红;本任务改版本后
|
|
22
|
+
它也负责防止「钉了一个配方不覆盖的版本」。
|
|
23
|
+
|
|
24
|
+
## 产物
|
|
25
|
+
|
|
26
|
+
每次运行(无论动作多少)写报告到 `docs/superpowers/nightly-reports/YYYY-MM-DD-cli-catalog.md`:
|
|
27
|
+
每个 agent 的结论(已解决/仍需要/无法判定)、证据、动作、遗留。报告不写本目录——整个
|
|
28
|
+
`axom-patch/cli/` 随 CLI 打包进 dist。
|
|
29
|
+
|
|
30
|
+
## 第 0 步:边界(每次都要读)
|
|
31
|
+
|
|
32
|
+
- 永不自动 git push、发版、发 issue/PR;改动仅限第 4 步编辑集。
|
|
33
|
+
- 补丁**下线**必须满足 [`docs/handbook/upstream-patches.md`](../../docs/handbook/upstream-patches.md)
|
|
34
|
+
的五条证据(修复链接、`fixed_release`、`adopted_version`、无补丁回归、`removal_commit`)。
|
|
35
|
+
「上游发了新版本」不是「已修复」;探针通过才是。
|
|
36
|
+
- 钉版本只升不降;配方与钉版本一起动,不允许钉着一个配方不生效的版本。
|
|
37
|
+
- 改了任何随 CLI 发布的内容**必须 bump `axom-cli/package.json` npm version**——相同版本不触发
|
|
38
|
+
Server 自动升级。
|
|
39
|
+
|
|
40
|
+
## 第 1 步:数据收集
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd <repo>
|
|
44
|
+
curl -s --max-time 30 https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json -o /tmp/acp-registry.json
|
|
45
|
+
for p in pi-acp @agentclientprotocol/claude-agent-acp @agentclientprotocol/codex-acp \
|
|
46
|
+
command-code zcode-acp-server @qoder-ai/qodercli; do npm view "$p" version; done
|
|
47
|
+
gh pr view 458 -R agentclientprotocol/registry --json state,title # kimi-code 收录
|
|
48
|
+
gh pr view 566 -R agentclientprotocol/registry --json state,title # zcode-acp 收录
|
|
49
|
+
gh pr view 650 -R agentclientprotocol/registry --json state,title
|
|
50
|
+
python3 -c "import json;d=json.load(open('$HOME/.axom/workstation/acp-adapters/builds.json'));print({k:v['current'] for k,v in d['agents'].items()})"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
对照表(钉版本与配方以文件为准,本表是索引;升级后同步更新本表):
|
|
54
|
+
|
|
55
|
+
| agent id | 来源 | 钉在哪 | 配方 |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| claude-acp | npm @agentclientprotocol/claude-agent-acp | catalog.ts `distribution` | claude-account-limits + launchEnv |
|
|
58
|
+
| codex-acp | npm @agentclientprotocol/codex-acp | catalog.ts `distribution` | codex-account-limits + launchEnv |
|
|
59
|
+
| pi-acp | npm pi-acp | catalog.ts `distribution` | pi-steering、pi-subagent-subwork |
|
|
60
|
+
| qoder | npm @qoder-ai/qodercli | runtime.json override(追赶钉,until registry 同版) | qoder-steering、qoder-subagent-origin |
|
|
61
|
+
| kimi-code | GitHub release 二进制 | catalog.ts `supplement` | native extension kimi-proxy |
|
|
62
|
+
| command-code | npm command-code | catalog.ts `supplement` | commandcode-account-limits |
|
|
63
|
+
| zcode-acp | npm zcode-acp-server | catalog.ts `supplement` | zcode-account-limits、zcode-models、zcode-subagent-subwork |
|
|
64
|
+
| antigravity-acp | registry binary .par(dl.google.com) | catalog.ts `distribution` | native extension antigravity-proxy |
|
|
65
|
+
|
|
66
|
+
## 第 2 步:逐 agent 判定(三选一,必须给结论)
|
|
67
|
+
|
|
68
|
+
对每个「npm latest / registry 版本 > 当前钉版本」的 agent:
|
|
69
|
+
|
|
70
|
+
1. **上游已解决,或官方有替代方案** → 走下线证据门槛(第 0 步),按台账 remove_when 退休补丁,
|
|
71
|
+
删钉版本,版本交还 registry。证据:探针在无补丁新版本上通过,或上游实现了等价能力
|
|
72
|
+
(如 adapter 原生暴露配额、原生发出 subagent 变体)。
|
|
73
|
+
2. **仍需要补丁**(默认预期)→ 第 3 步移植配方,第 4 步升钉版本。**当天完成**。
|
|
74
|
+
3. **判定不了** → 保持钉住,报告写明缺什么(哪个探针不存在、哪步需要真机/凭据)。这是唯一
|
|
75
|
+
允许不升级的出口。
|
|
76
|
+
|
|
77
|
+
另查两件让位事件:
|
|
78
|
+
|
|
79
|
+
- registry 收录了 supplement 的 id → supplement 让位前确认让位目标版本有配方覆盖;没有就先把
|
|
80
|
+
supplement 换成显式 `distribution` 钉 + 移植配方,再让位。
|
|
81
|
+
- 条件 override 到界(qoder until_registry_version)→ 让位目标高于配方版本时同样先移植再放行。
|
|
82
|
+
|
|
83
|
+
## 第 3 步:移植补丁到新版本
|
|
84
|
+
|
|
85
|
+
1. **取 pristine**:`mkdir /tmp/axom-nightly && cd /tmp/axom-nightly && npm pack <pkg>@<ver>` 解包
|
|
86
|
+
(antigravity 用 curl 拉 darwin-arm64 zip)。
|
|
87
|
+
2. **先问「还需要吗」**:
|
|
88
|
+
- 配额类:grep 新版本有无原生 `accountRateLimits` / 等价能力;没有 → 仍需要。
|
|
89
|
+
- 行为类(steering / subagent):查 initialize 有无 `_meta.steering`、有无 `subagent_spawned`
|
|
90
|
+
等原生实现;没有 → 仍需要。
|
|
91
|
+
3. **account-limits 类配方**(claude/codex/commandcode/zcode):改 `account-limits/generate.py`
|
|
92
|
+
里的版本号与锚点串(`count==1` 断言失败 = 上游改了形状,对照新 pristine 修锚点),指向新
|
|
93
|
+
pristine 目录重跑,生成新配方 JSON。
|
|
94
|
+
4. **手工配方**(zcode-models、zcode-subagent-subwork、qoder-*):对每个 edit 检查 `before` 在新
|
|
95
|
+
pristine 里是否仍唯一存在;压缩标识符改名(如 `gnd`→`Ocd`、`as`→`cs`)就同步改锚点;然后按
|
|
96
|
+
**配方在 runtime.json `package_patches` 里的顺序链式重算哈希**——第二个配方的 before 哈希
|
|
97
|
+
基准是第一个配方打完之后的文件(qoder-subagent-origin 在 qoder-steering 之前)。
|
|
98
|
+
5. **验证**(缺一不可):
|
|
99
|
+
- 顺序应用配方到 pristine 副本,逐 edit 核对 before/after sha256;
|
|
100
|
+
- 有探针的包跑探针:补丁副本必须通过,pristine 必须**复现缺失**(证明补丁仍需要)——
|
|
101
|
+
`node axom-patch/cli/probes/<pkg-*>.mjs <patched-dir>`(探针内钉了版本断言,随移植更新);
|
|
102
|
+
- 探针桩跟不上上游新代码路径(新解引用/新自由变量)时,更新探针桩而不是删断言;断言意图
|
|
103
|
+
不变,措辞跟上新行为(参考 2026-10-14 qoder-subagent-origin 的 toolCallPublisher 桩)。
|
|
104
|
+
|
|
105
|
+
## 第 4 步:升钉版本(编辑集,同一提交改完)
|
|
106
|
+
|
|
107
|
+
1. `axom-cli/src/workstation/acp/registry/catalog.ts`:bump `distribution.version` /
|
|
108
|
+
`supplement.version` 与来源(npx spec / 六平台归档 URL);binary 条目换 URL 时逐平台 HEAD
|
|
109
|
+
确认,能拿 sha256 就补。**diff registry 现条目的 args/env**——钉住来源整个替换 registry 的
|
|
110
|
+
distribution,registry 新加的要并进来。
|
|
111
|
+
2. `axom-patch/cli/runtime.json`:override 钉版本与 `until_registry_version` 同步;配方文件的
|
|
112
|
+
`versions`(字节一致可双版本并存,如 zcode 0.69.3+0.69.4;锚点变了就只留新版本);native
|
|
113
|
+
extension `versions` 加新版本(旧的保留,过渡期安装仍被包住)。
|
|
114
|
+
3. `axom-patch/manifest.json`:`dependencies.<pkg>.reviewed_version` = 本次核验的版本;
|
|
115
|
+
`patches[].artifacts` 的 sha256 全部重算(copy-patches.mjs 会校验,漏了 build 直接红)。
|
|
116
|
+
4. `axom-cli/package.json` bump npm version(相对上一个已发布版本)。
|
|
117
|
+
5. 文档:`axom-patch/cli/README.md` 当前补丁表、本文件对照表。
|
|
118
|
+
|
|
119
|
+
## 第 5 步:验证
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cd axom-cli
|
|
123
|
+
npx vitest run src/workstation/acp/patchAssets.test.ts src/workstation/acp/managedPatches.test.ts \
|
|
124
|
+
src/workstation/acp/registry/catalog.test.ts src/workstation/acp/acpAgents.test.ts
|
|
125
|
+
npm run build # 含 copy-patches 的台账工件校验
|
|
126
|
+
cd ..
|
|
127
|
+
python3 scripts/check-upstream-patches.py --offline
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
main 上有一批与本任务无关的既有失败(daemon 接线、浏览器 keeper、journal 时序、migrate 的
|
|
131
|
+
claude 旧 override 用例);与 `git stash` 前后对照失败集一致即可,不要顺手修。
|
|
132
|
+
|
|
133
|
+
真机冒烟(可选;涉及启动行为变化时做):dev 实例 Install/Update 一次,scan 确认版本行收敛到
|
|
134
|
+
新钉版本;`reconcileCertifiedBuilds` 会在 CLI 发出后自动把已装工作站前移。
|
|
135
|
+
|
|
136
|
+
## 第 6 步:报告
|
|
137
|
+
|
|
138
|
+
写入 `docs/superpowers/nightly-reports/`:对照表快照、每个 agent 的三选一结论与证据、动作清单、
|
|
139
|
+
遗留(只属于出口 3)。发版走既有 Client release 流程,本任务不触碰。
|
|
140
|
+
|
|
141
|
+
## 快照(2026-10-14 首跑,全部完成升级)
|
|
142
|
+
|
|
143
|
+
| agent | 旧钉 | 新钉 | registry/npm 当时 | 结论 |
|
|
144
|
+
|---|---|---|---|---|
|
|
145
|
+
| claude-acp | —(补钉) | 0.90.0 | 0.90.0 | 上游无原生配额 → 补钉;配方经 generate.py 重生成(normalize.mjs 对齐) |
|
|
146
|
+
| codex-acp | —(补钉) | 2.2.2 | 2.2.2 | 同上 |
|
|
147
|
+
| pi-acp | —(补钉) | 0.0.34 | 0.0.34 | 上游无 steering/subagent 变体 → 补钉 |
|
|
148
|
+
| antigravity-acp | —(补钉 1.3.0 后升) | **1.4.0** | registry 1.4.0 | 真实 1.4.0 .par 经 proxy 实测:透传无损、能力注入成功;配额 READ 留真机 |
|
|
149
|
+
| command-code | 1.74.3 | **1.82.1** | npm 1.82.1 | 上游仍无配额能力;handler map 改名(u/dn→m/hn)锚点已跟,generate.py 移植 |
|
|
150
|
+
| zcode-acp | 0.69.3 | **0.69.4** | npm 0.69.4 | 三份配方目标文件字节一致(versions 双版本);subagent 探针补丁副本过、pristine 复现缺失 |
|
|
151
|
+
| qoder | 1.1.61 | **1.1.71** | npm 1.1.71 | 仍无 ACP steering;三处锚点改名已移植,哈希链按 origin→steering 重算,双探针通过、pristine 复现缺失 |
|
|
152
|
+
| kimi-code | 2.1.1 | 2.1.1 | registry 未收录 | 无新版(GitHub release 同版) |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "pi-subagent-subwork",
|
|
3
|
+
"package": "pi-acp",
|
|
4
|
+
"versions": [
|
|
5
|
+
"0.0.34"
|
|
6
|
+
],
|
|
7
|
+
"edits": [
|
|
8
|
+
{
|
|
9
|
+
"file": "dist/index.js",
|
|
10
|
+
"before_sha256": "1ee621825b7e02d74312336fc0f820046bd41fe26455143f6f6e5078a727fa79",
|
|
11
|
+
"after_sha256": "228205f547edccd553d873a5f5c5dea6795ab6d33f989f14067e239153bbb2ef",
|
|
12
|
+
"before": "var PiAcpSession = class {\n axomSteering = new AxomPiSteering(this);",
|
|
13
|
+
"after": "// Injected into pi-acp 0.0.34 after pi-steering. Normalizes pi-subagents children into\n// draft ACP subagent variants (RFD #1992), the same contract claude-agent-acp ships\n// natively. All ids are derived from the root session id plus toolCallId/runId, never\n// random, so a keeper raw-log replay re-derives identical frames. Frames ride the\n// session's existing lastEmit chain so they keep one total order with every other frame.\nconst VOLATILE_PROGRESS_KEYS = /* @__PURE__ */ new Set([\n \"durationMs\",\n \"lastActivityAt\",\n \"recentOutput\",\n \"inputTokens\",\n \"outputTokens\",\n \"cacheRead\",\n \"cacheWrite\",\n \"tokens\",\n \"cost\",\n \"window\",\n \"windowPeak\",\n \"usage\"\n]);\nclass AxomPiSubagents {\n constructor(session) {\n this.session = session;\n this.children = /* @__PURE__ */ new Map();\n this.runs = /* @__PURE__ */ new Map();\n }\n emitTo(sessionId, update) {\n const session = this.session;\n session.lastEmit = session.lastEmit.then(\n () => session.conn.sessionUpdate({ sessionId, update })\n ).catch(() => {\n });\n }\n isLaunch(args) {\n return args && typeof args === \"object\" && typeof args.agent === \"string\" && args.agent && !args.action && (typeof args.task === \"string\" || args.workflow !== undefined);\n }\n textOf(content, cap) {\n if (!Array.isArray(content)) return \"\";\n let text = \"\";\n for (const block of content) {\n if (block && typeof block === \"object\" && block.type === \"text\" && typeof block.text === \"string\") text += (text ? \"\\n\" : \"\") + block.text;\n if (text.length >= cap) break;\n }\n return text.slice(0, cap);\n }\n spawn(childId, name, task, meta) {\n this.emitTo(this.session.sessionId, {\n sessionUpdate: \"subagent_spawned\",\n subagentSessionId: childId,\n name,\n task,\n capabilities: {},\n _meta: { pi: meta }\n });\n }\n onToolStart(ev) {\n if (ev.toolName !== \"subagent\" || !this.isLaunch(ev.args)) return;\n const toolCallId = String(ev.toolCallId ?? \"\");\n if (!toolCallId || this.children.has(toolCallId)) return;\n const args = ev.args;\n const childId = `pi-agent:${this.session.sessionId}:${toolCallId}`;\n const task = typeof args.task === \"string\" && args.task ? args.task : `workflow: ${String(args.workflow)}`;\n const child = { childId, terminal: false, lastUpdate: undefined };\n this.children.set(toolCallId, child);\n this.spawn(childId, args.agent, task, { toolCallId });\n }\n // pi-subagents resends the full foreground progress snapshot on a timer; measured on real\n // genius runs, frame-to-frame deltas are timers (durationMs/lastActivityAt), a rolling output\n // tail (recentOutput[] — the child's content, which must not stream into the parent session\n // anyway) and token/cost/window telemetry that ticks on every model step. Strip all of those\n // recursively and drop an update whose fingerprint did not change. Discrete signals — status\n // text, turnCount, recentTools[] and toolCalls[] growth — still pass, and the terminal frames\n // (with the final usage) arrive on tool_execution_end, which is never deduplicated.\n stripVolatile(value) {\n if (Array.isArray(value)) return value.map((item) => this.stripVolatile(item));\n if (value && typeof value === \"object\") {\n const out = {};\n for (const key of Object.keys(value).sort()) {\n if (VOLATILE_PROGRESS_KEYS.has(key)) continue;\n out[key] = this.stripVolatile(value[key]);\n }\n return out;\n }\n return value;\n }\n pruneEmpty(value) {\n if (Array.isArray(value)) {\n const kept = value.map((item) => this.pruneEmpty(item)).filter((item) => item !== void 0);\n return kept.length ? kept : void 0;\n }\n if (value && typeof value === \"object\") {\n const out = {};\n for (const key of Object.keys(value)) {\n const pruned = this.pruneEmpty(value[key]);\n if (pruned !== void 0) out[key] = pruned;\n }\n return Object.keys(out).length ? out : void 0;\n }\n return value;\n }\n onToolUpdate(ev) {\n const child = this.children.get(String(ev.toolCallId ?? \"\"));\n if (!child) return false;\n const fingerprint = JSON.stringify(this.pruneEmpty(this.stripVolatile(ev.partialResult)));\n if (fingerprint === child.lastUpdate) return true;\n child.lastUpdate = fingerprint;\n return false;\n }\n onToolEnd(ev) {\n const toolCallId = String(ev.toolCallId ?? \"\");\n const child = this.children.get(toolCallId);\n if (!child || child.terminal) return;\n const details = ev.result && typeof ev.result === \"object\" && ev.result.details && typeof ev.result.details === \"object\" ? ev.result.details : {};\n const runId = typeof details.runId === \"string\" ? details.runId : typeof details.asyncId === \"string\" ? details.asyncId : undefined;\n if (runId && !this.runs.has(runId)) this.runs.set(runId, child);\n if (details.asyncId !== undefined || details.asyncDir !== undefined) return;\n child.terminal = true;\n const preview = this.textOf(ev.result && ev.result.content, 2000);\n if (preview) this.emitTo(child.childId, { sessionUpdate: \"agent_message_chunk\", content: { type: \"text\", text: preview } });\n this.emitTo(this.session.sessionId, {\n sessionUpdate: \"subagent_state_update\",\n subagentSessionId: child.childId,\n state: ev.isError === true ? \"failed\" : \"completed\",\n _meta: { pi: { toolCallId } }\n });\n }\n onNotify(entry) {\n const details = entry.details && typeof entry.details === \"object\" ? entry.details : {};\n const runs = Array.isArray(details.runs) ? details.runs : [];\n for (const run of runs) {\n if (!run || typeof run !== \"object\") continue;\n const runId = typeof run.runId === \"string\" ? run.runId : typeof run.id === \"string\" ? run.id : undefined;\n if (!runId) continue;\n let child = this.runs.get(runId);\n if (!child) {\n child = { childId: `pi-agent:${this.session.sessionId}:run:${runId}`, terminal: false };\n this.runs.set(runId, child);\n this.spawn(child.childId, typeof run.agent === \"string\" && run.agent ? run.agent : \"subagent\", typeof run.summary === \"string\" ? run.summary : \"\", { adopted: true, runId });\n }\n if (child.terminal) continue;\n const status = typeof run.status === \"string\" ? run.status : \"\";\n const state = status === \"completed\" ? \"completed\" : status === \"failed\" ? \"failed\" : status === \"stopped\" ? \"cancelled\" : undefined;\n if (!state) continue;\n child.terminal = true;\n const meta = { pi: { runId } };\n for (const key of [\"session\", \"resultPath\", \"asyncDir\"]) {\n if (typeof run[key] === \"string\" && run[key]) meta.pi[key] = run[key];\n }\n if (typeof run.summary === \"string\" && run.summary) {\n this.emitTo(child.childId, { sessionUpdate: \"agent_message_chunk\", content: { type: \"text\", text: run.summary.slice(0, 2000) } });\n }\n this.emitTo(this.session.sessionId, { sessionUpdate: \"subagent_state_update\", subagentSessionId: child.childId, state, _meta: meta });\n }\n }\n observe(ev) {\n const type = String(ev.type ?? \"\");\n if (type === \"tool_execution_start\") this.onToolStart(ev);\n else if (type === \"tool_execution_end\") this.onToolEnd(ev);\n else if (type === \"tool_execution_update\") return this.onToolUpdate(ev);\n else if (type === \"message_end\") {\n // pi delivers extension custom messages (pi.sendMessage) as message_start/message_end\n // pairs with role \"custom\"; pi-acp 0.0.34 has no case for them and drops them.\n const message = ev.message;\n if (message && typeof message === \"object\" && message.role === \"custom\" && message.customType === \"subagent-notify\") this.onNotify(message);\n }\n return false;\n }\n}\n\nvar PiAcpSession = class {\n axomSteering = new AxomPiSteering(this);\n axomSubagents = new AxomPiSubagents(this);"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"file": "dist/index.js",
|
|
17
|
+
"before_sha256": "228205f547edccd553d873a5f5c5dea6795ab6d33f989f14067e239153bbb2ef",
|
|
18
|
+
"after_sha256": "41e76175c97e4d0e042e6fa7cfabca0eb0fea757a4fcfec39d8d238fc2e9088e",
|
|
19
|
+
"before": " handlePiEvent(ev) {\n const type = String(ev.type ?? \"\");",
|
|
20
|
+
"after": " handlePiEvent(ev) {\n if (this.axomSubagents && this.axomSubagents.observe(ev)) return;\n const type = String(ev.type ?? \"\");"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -13,7 +13,11 @@ for (const state of ['completed', 'failed', 'stopped']) {
|
|
|
13
13
|
for (const closing of ['terminalState', 'terminalPromise']) {
|
|
14
14
|
const events = [];
|
|
15
15
|
const publish = async (notification) => { events.push(notification); };
|
|
16
|
-
|
|
16
|
+
// "air", not `true`: 0.90.0 renamed the first argument from `enabled` to `form` ("air" | "rfd"),
|
|
17
|
+
// and in the rfd form a finished child keeps receiving its own late updates by design. Axom
|
|
18
|
+
// declares the AIR extension meta, so "air" is the form it gets; on pre-0.90 versions the
|
|
19
|
+
// string is still truthy, so `enabled` behaves as before.
|
|
20
|
+
const native = new NativeSubagentRuntime('air', 'root', {}, publish, { log() {} });
|
|
17
21
|
const child = { sessionId: 'child', parentSessionId: 'root' };
|
|
18
22
|
native.childByToolCall.set('call', child);
|
|
19
23
|
const tasks = new AsyncTaskRuntime(true, 'root', publish, { routeOf: (call) => native.routeOfToolCall(call) });
|