@platless/axom-cli 0.1.0 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/authScopes.js +19 -0
- package/dist/axom-patch/cli/README.md +132 -0
- package/dist/axom-patch/cli/account-limits/README.md +27 -0
- package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
- package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
- package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
- package/dist/axom-patch/cli/account-limits/generate.py +120 -0
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +64 -0
- package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
- package/dist/axom-patch/cli/claude-account-limits.json +37 -0
- package/dist/axom-patch/cli/codex-account-limits.json +37 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/nightly-task.md +152 -0
- package/dist/axom-patch/cli/pi-steering.json +37 -0
- package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
- package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +38 -0
- package/dist/axom-patch/cli/probes/pi-steering.mjs +80 -0
- package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +89 -0
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +61 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +82 -0
- package/dist/axom-patch/cli/qoder-foreground-output.md +42 -0
- package/dist/axom-patch/cli/qoder-steering.json +52 -0
- package/dist/axom-patch/cli/qoder-steering.md +30 -0
- package/dist/axom-patch/cli/qoder-subagent-origin.json +73 -0
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +73 -0
- package/dist/axom-patch/cli/runtime.json +63 -0
- package/dist/axom-patch/cli/steering/claude-steering-js.patch +89 -0
- package/dist/axom-patch/cli/steering/claude-steering-types.patch +28 -0
- package/dist/axom-patch/cli/steering/codex-steering.patch +57 -0
- package/dist/axom-patch/cli/steering/pi-steering.js +79 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +31 -0
- package/dist/axom-patch/cli/zcode-models.json +38 -0
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +73 -0
- package/dist/cli.js +36 -1
- package/dist/commands/auth.js +4 -13
- package/dist/commands/execute.js +4 -4
- package/dist/commands/record.js +6 -6
- package/dist/commands/struct.js +4 -4
- package/dist/commands/workstation.js +2 -2
- package/dist/credentials.js +2 -2
- package/dist/install/claude/commands.js +13 -13
- package/dist/keepers/acp/keeper.mjs +3083 -0
- package/dist/keepers/acp/manifest.json +9 -0
- package/dist/keepers/terminal/keeper.mjs +8469 -0
- package/dist/keepers/terminal/manifest.json +9 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
- package/dist/mcp.js +32 -14
- package/dist/runtime-deps/qoder-sdk/package-lock.json +1189 -0
- package/dist/runtime-deps/qoder-sdk/package.json +8 -0
- package/dist/test/keeperKit.js +67 -0
- package/dist/workstation/acp/acpAgents.js +2514 -681
- package/dist/workstation/acp/acpClient.js +95 -318
- package/dist/workstation/acp/activityBusy.js +43 -0
- package/dist/workstation/acp/activityReporting.js +54 -0
- package/dist/workstation/acp/adoption.js +28 -356
- package/dist/workstation/acp/authUrlSniffer.js +36 -0
- package/dist/workstation/acp/binaryInstall.js +100 -27
- package/dist/workstation/acp/builds/gc.js +245 -0
- package/dist/workstation/acp/builds/install/binary.js +28 -0
- package/dist/workstation/acp/builds/install/npm.js +235 -0
- package/dist/workstation/acp/builds/install/uv.js +254 -0
- package/dist/workstation/acp/builds/migrate.js +195 -0
- package/dist/workstation/acp/builds/model.js +128 -0
- package/dist/workstation/acp/builds/nodeGate.js +100 -0
- package/dist/workstation/acp/builds/store.js +352 -0
- package/dist/workstation/acp/commandTableProbe.js +68 -0
- package/dist/workstation/acp/configOptions.js +103 -1
- package/dist/workstation/acp/delegationMcp.js +24 -0
- package/dist/workstation/acp/envSecrets.js +27 -0
- package/dist/workstation/acp/initializeResponse.js +285 -0
- package/dist/workstation/acp/installErrors.js +12 -2
- package/dist/workstation/acp/installProbe.js +1 -1
- package/dist/workstation/acp/managedPatches.js +144 -0
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +80 -0
- package/dist/workstation/acp/permission.js +335 -37
- package/dist/workstation/acp/processor/checkpoint.js +87 -0
- package/dist/workstation/acp/processor/commands.js +35 -0
- package/dist/workstation/acp/processor/control.js +355 -0
- package/dist/workstation/acp/processor/emit.js +26 -0
- package/dist/workstation/acp/processor/jsonrpc.js +172 -0
- package/dist/workstation/acp/processor/memoryLog.js +49 -0
- package/dist/workstation/acp/processor/origins.js +48 -0
- package/dist/workstation/acp/processor/output.js +67 -0
- package/dist/workstation/acp/processor/projection.js +42 -0
- package/dist/workstation/acp/processor/record.js +128 -0
- package/dist/workstation/acp/processor/runtime.js +181 -0
- package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
- package/dist/workstation/acp/processor/session.js +525 -0
- package/dist/workstation/acp/processor/sideChannel.js +80 -0
- package/dist/workstation/acp/processor/state.js +74 -0
- package/dist/workstation/acp/processor/step.js +459 -0
- package/dist/workstation/acp/processor/test/fixtureLog.js +65 -0
- package/dist/workstation/acp/processor/test/harness.js +120 -0
- package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
- package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
- package/dist/workstation/acp/processor/transport/generations.js +76 -0
- package/dist/workstation/acp/processor/transport/keeper.js +130 -0
- package/dist/workstation/acp/processor/transport/local.js +269 -0
- package/dist/workstation/acp/processor/transport/types.js +78 -0
- package/dist/workstation/acp/processor/updates.js +364 -0
- package/dist/workstation/acp/processorSession.js +1512 -0
- package/dist/workstation/acp/qoderStartup.js +45 -0
- package/dist/workstation/acp/registry/catalog.js +385 -4
- package/dist/workstation/acp/registry/detect.js +55 -0
- package/dist/workstation/acp/registry/launch.js +61 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +56 -27
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +114 -0
- package/dist/workstation/acp/terminalLogin.js +171 -0
- package/dist/workstation/acp/terminalLoginSpec.js +41 -0
- package/dist/workstation/acp/test/adapterFixtures.js +256 -0
- package/dist/workstation/acp/test/daemonLife.js +106 -0
- package/dist/workstation/acp/test/fakeAgent.js +185 -9
- package/dist/workstation/acp/test/keeperWorld.js +267 -0
- package/dist/workstation/acp/test/memoryOutbox.js +55 -0
- package/dist/workstation/acp/test/raw/fixtures.js +196 -0
- package/dist/workstation/acp/test/raw/qoderCaptures.js +125 -0
- package/dist/workstation/acp/test/raw/sanitize.js +344 -0
- package/dist/workstation/acp/test/raw/sources.js +249 -0
- package/dist/workstation/acp/test/realAdapter.js +377 -0
- package/dist/workstation/acp/test/scriptAgent.js +206 -0
- package/dist/workstation/acp/test/sessionFrames.js +42 -0
- package/dist/workstation/acp/timedCommand.js +4 -2
- package/dist/workstation/acp/uvxInstall.js +72 -122
- package/dist/workstation/acp/validate.js +33 -5
- package/dist/workstation/agentFlow.js +103 -0
- package/dist/workstation/agentSessions.js +1686 -348
- package/dist/workstation/channels/agent.js +14 -6
- package/dist/workstation/channels/computer.js +203 -7
- package/dist/workstation/channels/computerExecs.js +200 -0
- package/dist/workstation/channels/management.js +86 -6
- package/dist/workstation/config.js +3 -1
- package/dist/workstation/connection/encryptedChannel.js +88 -21
- package/dist/workstation/daemon.js +309 -6
- package/dist/workstation/daemonctl.js +28 -2
- package/dist/workstation/desktop/binding.js +36 -0
- package/dist/workstation/desktop/bridge.js +212 -0
- package/dist/workstation/desktop/flow-control.js +52 -0
- package/dist/workstation/desktop/focused-text.js +130 -0
- package/dist/workstation/desktop/protocol.js +35 -0
- package/dist/workstation/exec/attach.js +126 -0
- package/dist/workstation/exec/command.js +40 -0
- package/dist/workstation/exec/durable.js +517 -0
- package/dist/workstation/exec/fallback.js +106 -0
- package/dist/workstation/exec/policy.js +837 -0
- package/dist/workstation/exec/shaping.js +211 -0
- package/dist/workstation/exec.js +3 -112
- package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
- package/dist/workstation/files/adapters/toolRequests.js +121 -0
- package/dist/workstation/files/client.js +455 -0
- package/dist/workstation/files/errors.js +28 -0
- package/dist/workstation/files/fence.js +350 -0
- package/dist/workstation/files/find/fileIndex.js +153 -0
- package/dist/workstation/files/find/fuzzy.js +155 -0
- package/dist/workstation/files/host.js +122 -0
- package/dist/workstation/files/hostProtocol.js +2 -0
- package/dist/workstation/files/media/cache.js +620 -0
- package/dist/workstation/files/media/encoding.js +308 -0
- package/dist/workstation/files/media/estimator.js +100 -0
- package/dist/workstation/files/media/media.js +325 -0
- package/dist/workstation/files/media/packedAudio.js +23 -0
- package/dist/workstation/files/media/scheduler.js +551 -0
- package/dist/workstation/files/media/types.js +5 -0
- package/dist/workstation/files/scope.js +45 -0
- package/dist/workstation/files/structured.js +284 -0
- package/dist/workstation/files/text.js +356 -0
- package/dist/workstation/files/uploads.js +351 -0
- package/dist/workstation/journal.js +65 -236
- package/dist/workstation/keepers/acp/client.js +101 -0
- package/dist/workstation/keepers/acp/envelope.js +62 -0
- package/dist/workstation/keepers/acp/host.js +419 -0
- package/dist/workstation/keepers/acp/main.js +18 -0
- package/dist/workstation/keepers/acp/memoryStream.js +121 -0
- package/dist/workstation/keepers/acp/params.js +137 -0
- package/dist/workstation/keepers/acp/protocol.js +66 -0
- package/dist/workstation/keepers/acp/recover.js +120 -0
- package/dist/workstation/keepers/acp/session.js +584 -0
- package/dist/workstation/keepers/acp/stderr.js +154 -0
- package/dist/workstation/keepers/kit/activity.js +103 -0
- package/dist/workstation/keepers/kit/bootstrap.js +164 -0
- package/dist/workstation/keepers/kit/buildVersion.js +51 -0
- package/dist/workstation/keepers/kit/bundle.js +120 -0
- package/dist/workstation/keepers/kit/client.js +432 -0
- package/dist/workstation/keepers/kit/identity.js +21 -0
- package/dist/workstation/keepers/kit/ipc.js +112 -0
- package/dist/workstation/keepers/kit/launchConfig.js +8 -0
- package/dist/workstation/keepers/kit/launcher.js +249 -0
- package/dist/workstation/keepers/kit/lock.js +123 -0
- package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
- package/dist/workstation/keepers/kit/paths.js +75 -0
- package/dist/workstation/keepers/kit/protocol.js +43 -0
- package/dist/workstation/keepers/kit/server.js +518 -0
- package/dist/workstation/keepers/kit/streamLog.js +697 -0
- package/dist/workstation/keepers/legacy.js +296 -0
- package/dist/workstation/keepers/policy.js +576 -0
- package/dist/workstation/keepers/stop.js +34 -0
- package/dist/workstation/keepers/terminal/client.js +213 -0
- package/dist/workstation/keepers/terminal/exec.js +981 -0
- package/dist/workstation/keepers/terminal/generations.js +129 -0
- package/dist/workstation/keepers/terminal/keeper.js +62 -0
- package/dist/workstation/keepers/terminal/main.js +43 -0
- package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
- package/dist/workstation/keepers/terminal/params.js +27 -0
- package/dist/workstation/keepers/terminal/protocol.js +56 -0
- package/dist/workstation/keepers/terminal/pty.js +330 -0
- package/dist/workstation/oauth/deviceAuthorization.js +21 -12
- package/dist/workstation/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +201 -8
- package/dist/workstation/protocol/common.js +36 -0
- package/dist/workstation/protocol/computer.js +107 -7
- package/dist/workstation/protocol/management.js +50 -2
- package/dist/workstation/qoderAdapter/backgroundProcess.js +118 -0
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +193 -0
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +372 -0
- package/dist/workstation/qoderAdapter/callbackContext.js +76 -0
- package/dist/workstation/qoderAdapter/completedContinuation.js +139 -0
- package/dist/workstation/qoderAdapter/contract.js +78 -0
- package/dist/workstation/qoderAdapter/foregroundOutput.js +36 -0
- package/dist/workstation/qoderAdapter/history.js +85 -0
- package/dist/workstation/qoderAdapter/identityIndex.js +191 -0
- package/dist/workstation/qoderAdapter/inputQueue.js +42 -0
- package/dist/workstation/qoderAdapter/main.js +95 -0
- package/dist/workstation/qoderAdapter/mapper.js +117 -0
- package/dist/workstation/qoderAdapter/observerPlugin.js +245 -0
- package/dist/workstation/qoderAdapter/observerReceiver.js +187 -0
- package/dist/workstation/qoderAdapter/permissions.js +61 -0
- package/dist/workstation/qoderAdapter/profile.js +93 -0
- package/dist/workstation/qoderAdapter/sdkDriver.js +48 -0
- package/dist/workstation/qoderAdapter/sdkRuntime.js +93 -0
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +145 -0
- package/dist/workstation/qoderAdapter/session.js +319 -0
- package/dist/workstation/qoderAdapter/subagentMeta.js +28 -0
- package/dist/workstation/qoderAdapter/subwork.js +85 -0
- package/dist/workstation/qoderAdapter/taskControl.js +38 -0
- package/dist/workstation/qoderAdapter/taskLedger.js +163 -0
- package/dist/workstation/qoderAdapter/types.js +1 -0
- package/dist/workstation/qoderAdapter/usage.js +37 -0
- package/dist/workstation/register.js +4 -0
- package/dist/workstation/systemMetrics.js +110 -1
- package/dist/workstation/tools/browser.js +394 -0
- package/dist/workstation/tools/client.js +277 -0
- package/dist/workstation/tools/info.js +50 -0
- package/dist/workstation/tools/keeperProcess.js +124 -0
- package/dist/workstation/tools/router.js +55 -0
- package/dist/workstation/tools/terminalRouter.js +222 -0
- package/dist/workstation/tools/validation.js +86 -0
- package/dist/workstation/update/coordinator.js +32 -0
- package/dist/workstation/update/npmInstaller.js +2 -0
- package/dist/workstation/update/ptyPreflight.js +45 -0
- package/dist/workstation/update/taskRegistry.js +141 -23
- package/dist/workstation/update/watchdog.js +66 -0
- package/package.json +20 -7
- package/skills/axom/SKILL.md +55 -17
- package/skills/axom-definition/SKILL.md +32 -32
- package/skills/axom-platform/SKILL.md +39 -39
- package/dist/workstation/acp/acpSession.js +0 -1047
- package/dist/workstation/acp/contract.js +0 -494
- package/dist/workstation/acp/draftInterceptor.js +0 -154
|
@@ -2,6 +2,8 @@ import WebSocket from 'ws';
|
|
|
2
2
|
import { RetryPolicy } from '../connection/retryPolicy.js';
|
|
3
3
|
import { tryOpenAuthorizationUrl } from './browser.js';
|
|
4
4
|
import { loadDeviceId } from '../../config.js';
|
|
5
|
+
import { DEFAULT_CLI_SCOPES, grantedCliScopes } from '../../authScopes.js';
|
|
6
|
+
import { revokeRefreshToken } from '../../credentials.js';
|
|
5
7
|
export const DEVICE_CODE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
6
8
|
export class DeviceAuthorizationError extends Error {
|
|
7
9
|
}
|
|
@@ -87,7 +89,7 @@ function waitForNotification(socket, sessionId, notificationTicket, deadline, no
|
|
|
87
89
|
socket.on('error', () => finish('reconnect'));
|
|
88
90
|
});
|
|
89
91
|
}
|
|
90
|
-
async function exchangeDeviceCode(server, clientId, deviceCode, fetch_) {
|
|
92
|
+
async function exchangeDeviceCode(server, clientId, deviceCode, fetch_, requestedScopes) {
|
|
91
93
|
const response = await requestJson(fetch_, new URL('/oauth/token', server), {
|
|
92
94
|
method: 'POST',
|
|
93
95
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
@@ -98,14 +100,25 @@ async function exchangeDeviceCode(server, clientId, deviceCode, fetch_) {
|
|
|
98
100
|
device_id: await loadDeviceId(),
|
|
99
101
|
}),
|
|
100
102
|
}, 200, 'Device token exchange');
|
|
103
|
+
return validateTokenResponse(server, response, requestedScopes, fetch_);
|
|
104
|
+
}
|
|
105
|
+
async function validateTokenResponse(server, response, requestedScopes, fetch_) {
|
|
101
106
|
const accessToken = requireString(response.access_token, 'access_token', 'Device token response');
|
|
102
107
|
const refreshToken = requireString(response.refresh_token, 'refresh_token', 'Device token response');
|
|
103
108
|
if (!Number.isFinite(response.expires_in) || response.expires_in <= 0) {
|
|
104
109
|
throw new DeviceAuthorizationError('Device token response has invalid expires_in');
|
|
105
110
|
}
|
|
106
|
-
|
|
111
|
+
let scopes;
|
|
112
|
+
try {
|
|
113
|
+
scopes = grantedCliScopes(response.scope, requestedScopes);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
await revokeRefreshToken(server, refreshToken, fetch_);
|
|
117
|
+
throw new DeviceAuthorizationError(error instanceof Error ? error.message : 'Device token response has invalid scopes');
|
|
118
|
+
}
|
|
119
|
+
return { accessToken, refreshToken, expiresInSeconds: response.expires_in, scopes };
|
|
107
120
|
}
|
|
108
|
-
async function pollDeviceCode(server, clientId, deviceCode, fetch_) {
|
|
121
|
+
async function pollDeviceCode(server, clientId, deviceCode, fetch_, requestedScopes) {
|
|
109
122
|
const response = await fetch_(new URL('/oauth/token', server), {
|
|
110
123
|
method: 'POST',
|
|
111
124
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
@@ -128,17 +141,13 @@ async function pollDeviceCode(server, clientId, deviceCode, fetch_) {
|
|
|
128
141
|
if (response.status !== 200)
|
|
129
142
|
throw new DeviceAuthorizationError(`Device token polling failed: ${response.status}`);
|
|
130
143
|
const token = await response.json();
|
|
131
|
-
return
|
|
132
|
-
accessToken: requireString(token.access_token, 'access_token', 'Device token response'),
|
|
133
|
-
refreshToken: requireString(token.refresh_token, 'refresh_token', 'Device token response'),
|
|
134
|
-
expiresInSeconds: token.expires_in,
|
|
135
|
-
};
|
|
144
|
+
return validateTokenResponse(server, token, requestedScopes, fetch_);
|
|
136
145
|
}
|
|
137
146
|
/**
|
|
138
147
|
* Uses OAuth Device Authorization for both desktop and headless clients. The notification socket
|
|
139
148
|
* only carries state; the tokens exist only in this call's return value.
|
|
140
149
|
*/
|
|
141
|
-
export async function authorizeDevice(server, clientName, hostname, deps = {}) {
|
|
150
|
+
export async function authorizeDevice(server, clientName, hostname, deps = {}, requestedScopes = DEFAULT_CLI_SCOPES) {
|
|
142
151
|
const baseUrl = normalizedServer(server);
|
|
143
152
|
const fetch_ = deps.fetch ?? globalThis.fetch;
|
|
144
153
|
const print = deps.print ?? console.log;
|
|
@@ -151,7 +160,7 @@ export async function authorizeDevice(server, clientName, hostname, deps = {}) {
|
|
|
151
160
|
const authorization = await requestJson(fetch_, new URL('/oauth/device_authorization', baseUrl), {
|
|
152
161
|
method: 'POST',
|
|
153
162
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
154
|
-
body: new URLSearchParams({ client_id: clientId, hostname }),
|
|
163
|
+
body: new URLSearchParams({ client_id: clientId, hostname, scope: requestedScopes.join(' ') }),
|
|
155
164
|
}, 200, 'Device authorization request');
|
|
156
165
|
const deviceCode = requireString(authorization.device_code, 'device_code', 'Device authorization response');
|
|
157
166
|
const verificationUrl = requireString(authorization.verification_uri_complete, 'verification_uri_complete', 'Device authorization response');
|
|
@@ -178,14 +187,14 @@ export async function authorizeDevice(server, clientName, hostname, deps = {}) {
|
|
|
178
187
|
}
|
|
179
188
|
const status = await waitForNotification(socket, sessionId, notificationTicket, deadline, now, () => retry.recordSuccess());
|
|
180
189
|
if (status === 'approved') {
|
|
181
|
-
const tokens = await exchangeDeviceCode(baseUrl, clientId, deviceCode, fetch_);
|
|
190
|
+
const tokens = await exchangeDeviceCode(baseUrl, clientId, deviceCode, fetch_, requestedScopes);
|
|
182
191
|
return { clientId, ...tokens };
|
|
183
192
|
}
|
|
184
193
|
if (status === 'denied')
|
|
185
194
|
throw new DeviceAuthorizationError('Device authorization was denied');
|
|
186
195
|
if (status === 'expired')
|
|
187
196
|
break;
|
|
188
|
-
const polled = await pollDeviceCode(baseUrl, clientId, deviceCode, fetch_);
|
|
197
|
+
const polled = await pollDeviceCode(baseUrl, clientId, deviceCode, fetch_, requestedScopes);
|
|
189
198
|
if (polled)
|
|
190
199
|
return { clientId, ...polled };
|
|
191
200
|
const pollingFloor = Math.max(1, authorization.interval ?? 5) * 1_000;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Bounded, plain-JS wire validation: usable inside the deterministic ACP processor. */
|
|
2
|
+
export const ACCOUNT_LIMITS_READ_TIMEOUT_MS = 180_000;
|
|
3
|
+
const object = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
4
|
+
const text = (v, min = 0, max = 160) => typeof v === 'string' && v.length >= min && v.length <= max;
|
|
5
|
+
const time = (v) => typeof v === 'string' && /^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d{2}:\d{2})$/.test(v) && Number.isFinite(new Date(v).getTime());
|
|
6
|
+
const oneOf = (v, values) => typeof v === 'string' && values.includes(v);
|
|
7
|
+
function parse(value) {
|
|
8
|
+
const valid = object(value) && value.schemaVersion === 1 && text(value.provider, 1, 64)
|
|
9
|
+
&& (value.updateMode === undefined || oneOf(value.updateMode, ['snapshot', 'delta']))
|
|
10
|
+
&& oneOf(value.billingMode, ['subscription', 'non_subscription', 'unknown'])
|
|
11
|
+
&& oneOf(value.availability, ['available', 'unavailable', 'unknown', 'not_applicable']) && time(value.observedAt)
|
|
12
|
+
&& (value.plan === null || object(value.plan) && text(value.plan.id) && text(value.plan.label))
|
|
13
|
+
&& (value.accountKey === undefined || text(value.accountKey, 0, 128))
|
|
14
|
+
&& Array.isArray(value.limits) && value.limits.length <= 32 && value.limits.every(row => object(row)
|
|
15
|
+
&& text(row.id, 1) && text(row.label) && oneOf(row.kind, ['subscription', 'spend', 'rate'])
|
|
16
|
+
&& (row.windowSeconds === null || typeof row.windowSeconds === 'number' && Number.isInteger(row.windowSeconds) && row.windowSeconds > 0)
|
|
17
|
+
&& (row.period == null || text(row.period))
|
|
18
|
+
&& (row.key === undefined || row.key === null || text(row.key, 1, 64))
|
|
19
|
+
&& (row.usedPercent === null || typeof row.usedPercent === 'number' && Number.isFinite(row.usedPercent) && row.usedPercent >= 0 && row.usedPercent <= 100)
|
|
20
|
+
&& (row.resetsAt === null || time(row.resetsAt)) && oneOf(row.status, ['allowed', 'warning', 'blocked', 'unknown'])
|
|
21
|
+
&& (row.active == null || typeof row.active === 'boolean') && (row.scope == null || object(row.scope))
|
|
22
|
+
&& ['used', 'limit', 'unit', 'currency'].every(key => row[key] === undefined || text(row[key]))
|
|
23
|
+
&& (row.exponent === undefined || typeof row.exponent === 'number' && Number.isInteger(row.exponent) && row.exponent >= 0 && row.exponent <= 12))
|
|
24
|
+
&& new Set(value.limits.map(row => row.id)).size === value.limits.length
|
|
25
|
+
&& Array.isArray(value.balances) && value.balances.length <= 16 && value.balances.every(row => object(row)
|
|
26
|
+
&& text(row.id, 1) && text(row.label) && text(row.unit) && (row.remaining === null || text(row.remaining))
|
|
27
|
+
&& (row.currency === undefined || text(row.currency)) && (row.unlimited === undefined || typeof row.unlimited === 'boolean')
|
|
28
|
+
&& (row.exponent === undefined || typeof row.exponent === 'number' && Number.isInteger(row.exponent) && row.exponent >= 0 && row.exponent <= 12))
|
|
29
|
+
&& new Set(value.balances.map(row => row.id)).size === value.balances.length
|
|
30
|
+
&& (value._meta === undefined || object(value._meta)) && JSON.stringify(value).length <= 65_536;
|
|
31
|
+
if (!valid)
|
|
32
|
+
throw new Error('Invalid account quota snapshot');
|
|
33
|
+
return { ...value, _meta: value._meta ?? {} };
|
|
34
|
+
}
|
|
35
|
+
export const accountLimitsSchema = {
|
|
36
|
+
parse,
|
|
37
|
+
safeParse(value) {
|
|
38
|
+
try {
|
|
39
|
+
return { success: true, data: parse(value) };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { success: false, error: new Error('Invalid account quota snapshot') };
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
export const ACCOUNT_LIMITS_READ = '_axom/account/rateLimits/read';
|
|
47
|
+
export const ACCOUNT_LIMITS_UPDATED = '_axom/account/rateLimits/updated';
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
+
import { accountLimitsSchema } from './accountLimits.js';
|
|
2
|
+
import { isRuntimeDiagnostics } from '../qoderAdapter/profile.js';
|
|
1
3
|
import { decodeChannelFrame, hasOptional, hasRequired, isBoolean, isBridgeInfoArray, isNullableAuthMethodInfoArray, isNullableString, isNumber, isObject, isString, isStringArray, isStringRecord, } from './common.js';
|
|
2
4
|
/** Current version and feature floors for the independently negotiated agent protocol. */
|
|
3
|
-
export const AGENT_WIRE_VERSION =
|
|
5
|
+
export const AGENT_WIRE_VERSION = 27;
|
|
6
|
+
/**
|
|
7
|
+
* `agent_switch_idle` / `agent_switch_idle_result` (spec 2026-10-11 §6.4, Task 25): "move idle sessions" of one agent to another
|
|
8
|
+
* adapter build. A server-to-client request: the server sends it only to a client advertising this version (an older one has no
|
|
9
|
+
* case for it and the request would only die at its timeout).
|
|
10
|
+
*/
|
|
11
|
+
export const AGENT_WIRE_ACCOUNT_LIMITS_VERSION = 27;
|
|
12
|
+
export const AGENT_WIRE_SWITCH_IDLE_VERSION = 26;
|
|
13
|
+
/** `agent_delete_native` / `agent_native_deleted`: purge a session Axom created on an external agent. */
|
|
14
|
+
export const AGENT_WIRE_NATIVE_DELETE_VERSION = 24;
|
|
15
|
+
export const AGENT_WIRE_PROVIDER_TASK_VERSION = 23;
|
|
16
|
+
export const AGENT_WIRE_RUNTIME_PROFILE_VERSION = 22;
|
|
17
|
+
export const AGENT_WIRE_DELEGATION_RENEW_VERSION = 21;
|
|
18
|
+
export const AGENT_WIRE_TURN_IDENTITY_VERSION = 20;
|
|
19
|
+
export const AGENT_WIRE_DELEGATION_MCP_VERSION = 19;
|
|
20
|
+
/** Scoped replay/history is enabled only when both peers preserve native origin. */
|
|
21
|
+
export const AGENT_WIRE_SCOPED_ORIGIN_VERSION = 18;
|
|
22
|
+
/** agent_open.requireSessionLoad: reject unsupported/failed load without session/new fallback. */
|
|
23
|
+
export const AGENT_WIRE_STRICT_LOAD_VERSION = 17;
|
|
4
24
|
export const AGENT_WIRE_MIN_VERSION = 1;
|
|
5
25
|
export const AGENT_WIRE_SESSION_LOST_VERSION = 2;
|
|
6
26
|
export const AGENT_WIRE_ADOPTION_VERSION = 3;
|
|
@@ -137,12 +157,65 @@ export const AGENT_WIRE_RELOADED_VERSION = 13;
|
|
|
137
157
|
* is byte-for-byte the frame it always was and still goes out at {@link AGENT_WIRE_STEER_VERSION}.
|
|
138
158
|
*/
|
|
139
159
|
export const AGENT_WIRE_STEER_BLOCKS_VERSION = 14;
|
|
160
|
+
/**
|
|
161
|
+
* The session activity-state push (`agent_session_state`) and the single-background-task stop
|
|
162
|
+
* request (`agent_async_task_stop`). **Both must be checked before sending — the check just lives
|
|
163
|
+
* in a different process for each, because the two frames do not share a sender.**
|
|
164
|
+
*
|
|
165
|
+
* ## Why both need a floor, not just one
|
|
166
|
+
*
|
|
167
|
+
* A version floor answers one question: *before sending, does the peer understand this frame?*
|
|
168
|
+
* These two frames travel opposite directions, so the check lives on opposite sides:
|
|
169
|
+
*
|
|
170
|
+
* - `agent_async_task_stop` is the **server's** request to this client. A v14 *client* has no case
|
|
171
|
+
* for it in `AGENT_FRAME_SHAPES` — the same shape as {@link AGENT_WIRE_STEER_VERSION} and
|
|
172
|
+
* {@link AGENT_WIRE_INSTALL_VERSION} — so the check lives on the Kotlin server (this constant's
|
|
173
|
+
* twin, `AgentChannelSession`, a later task), before every `agent_async_task_stop` it sends. There
|
|
174
|
+
* is nothing for this file to check on receipt: this daemon only ever handles the frame, never
|
|
175
|
+
* sends it.
|
|
176
|
+
* - `agent_session_state` is the **client's** push to the server, and getting this half wrong is
|
|
177
|
+
* what an earlier revision of this comment did — it argued "an old server just never sees the
|
|
178
|
+
* frame" was enough, which is only half the compatibility matrix and not the dangerous half. The
|
|
179
|
+
* dangerous direction is **a newer client talking to an older server**: this daemon is a
|
|
180
|
+
* separately-installed npm package on the user's machine, so the client upgrading ahead of the
|
|
181
|
+
* server is the ordinary update order here, not an edge case. A v15 client sending
|
|
182
|
+
* `agent_session_state` to a v14 server hits a decoder that throws on the unrecognised
|
|
183
|
+
* discriminator, and because the frame carries a top-level `sessionRef` and no `id`, the server's
|
|
184
|
+
* own recovery (`classifyUndecodableAgentFrame`) charges it to `FailSession` — declaring that
|
|
185
|
+
* session lost and forcing a reopen. This frame exists to be sent over and over, so on an
|
|
186
|
+
* unupgraded server every single push would force a session loss, and a run of them closes the
|
|
187
|
+
* whole agent channel — worse than the `agent_auth_url` incident this file already documents,
|
|
188
|
+
* since that frame carries no `sessionRef` and only ever reaches `Unattributable`.
|
|
189
|
+
*
|
|
190
|
+
* The correct precedent is {@link AGENT_WIRE_AUTH_URL_VERSION}, not
|
|
191
|
+
* {@link AGENT_WIRE_RELOADED_VERSION}: `agent_opened.reloaded` is an **optional key on a frame the
|
|
192
|
+
* peer already decodes**, safely absorbed by `ignoreUnknownKeys`, so nothing on the sending side
|
|
193
|
+
* needs to check anything. `agent_session_state` is a **new discriminator**, and a decoder throws
|
|
194
|
+
* on an unrecognised one before `ignoreUnknownKeys` ever gets a say — the same shape
|
|
195
|
+
* `agent_auth_url` is, and that constant's own floor is checked client-side (`serverAgentWire <
|
|
196
|
+
* AGENT_WIRE_AUTH_URL_VERSION`, see `agentSessions.ts`'s `onAuthenticate`/`present`/`dismiss`) for
|
|
197
|
+
* exactly this reason. `agent_session_state` follows the same rule: the client checks
|
|
198
|
+
* `this.serverAgentWire` before sending and **discards** the push below this floor, rather than
|
|
199
|
+
* erroring — a level nobody downstream can read is no worse than one that was never collected.
|
|
200
|
+
*
|
|
201
|
+
* So both frames are gated, on opposite sides: this file's own send path (a later task) must check
|
|
202
|
+
* `serverAgentWire` before every `agent_session_state`, and the Kotlin twin must check
|
|
203
|
+
* `clientAgentWire` before every `agent_async_task_stop`.
|
|
204
|
+
*/
|
|
205
|
+
export const AGENT_WIRE_SESSION_STATE_VERSION = 15;
|
|
206
|
+
/**
|
|
207
|
+
* Elicitation answers (`agent_elicitation_answer`). Checked before sending, like
|
|
208
|
+
* {@link AGENT_WIRE_INSTALL_VERSION} and {@link AGENT_WIRE_CONFIG_OPTIONS_VERSION}: a peer below
|
|
209
|
+
* this floor has no case for the frame at all and would drop it, leaving the agent process blocked
|
|
210
|
+
* inside `elicitation/create` rather than ever reaching the user's answer.
|
|
211
|
+
*/
|
|
212
|
+
export const AGENT_WIRE_ELICITATION_VERSION = 16;
|
|
140
213
|
/**
|
|
141
214
|
* Every agent frame's shape, as data rather than as a `switch`.
|
|
142
215
|
*
|
|
143
216
|
* Declarative on purpose: the required-field half of this table is the *only* statement anywhere of
|
|
144
217
|
* which fields the Kotlin twin will refuse to decode without, and the contract suite
|
|
145
|
-
* (`
|
|
218
|
+
* (`protocol/agentRequiredFields.test.ts`'s `assertRequiredFieldsEnforced`) reads it directly to prove each one is really
|
|
146
219
|
* enforced. Left as inline `hasRequired({...})` calls inside a `switch`, that list is unreadable from
|
|
147
220
|
* a test, so the only way to check it would be a hand-maintained copy in the test file — which is
|
|
148
221
|
* precisely how `agent_authenticate.methodId` came to have no guard at all while a report still
|
|
@@ -164,6 +237,7 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
164
237
|
optional: { sessions: (sessions) => Array.isArray(sessions) && sessions.every(isAgentResumeSession) },
|
|
165
238
|
},
|
|
166
239
|
heartbeat_ack: { required: { seq: isNumber } },
|
|
240
|
+
agent_reopen_request: { required: { sessionRef: isString, reason: isString } },
|
|
167
241
|
agent_open: {
|
|
168
242
|
required: {
|
|
169
243
|
id: isString, sessionRef: isString, agentKey: isString, bridge: isString, cwd: isString, shell: isString,
|
|
@@ -171,7 +245,10 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
171
245
|
optional: {
|
|
172
246
|
env: isStringRecord, pathPrepend: isStringArray, agentEnv: isStringRecord, command: isNullableString,
|
|
173
247
|
args: isStringArray, nativeSessionId: isNullableString,
|
|
174
|
-
resumeFromSeq: isNumber, backfill: isBoolean,
|
|
248
|
+
resumeFromSeq: isNumber, backfill: isBoolean, requireSessionLoad: isBoolean,
|
|
249
|
+
delegationMcp: (value) => isObject(value) &&
|
|
250
|
+
isString(value.url) && isStringRecord(value.headers) && isNumber(value.expiresAtEpochMs) &&
|
|
251
|
+
(value.bindingId === undefined || isString(value.bindingId)),
|
|
175
252
|
},
|
|
176
253
|
},
|
|
177
254
|
agent_opened: {
|
|
@@ -180,6 +257,7 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
180
257
|
// `false` would be a second spelling of what its absence already says.
|
|
181
258
|
optional: {
|
|
182
259
|
nativeSessionId: isNullableString, error: isNullableString, supportsSteering: isVerifiedTrue,
|
|
260
|
+
runtimeDiagnostics: isRuntimeDiagnostics,
|
|
183
261
|
// Plain strings, not a closed vocabulary: the sender already dropped everything it did not
|
|
184
262
|
// recognise, and validating the values again here would make a future timing fail the frame
|
|
185
263
|
// rather than be ignored — the opposite of what ruling N4 asks for.
|
|
@@ -187,16 +265,38 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
187
265
|
// `reloaded` is `true`-or-absent for the same reason as `supportsSteering`: a `false` would
|
|
188
266
|
// be a second spelling of what its absence already says.
|
|
189
267
|
reloaded: isVerifiedTrue,
|
|
268
|
+
delegationExpiresAtEpochMs: isNumber,
|
|
269
|
+
delegationRefreshPending: isVerifiedTrue,
|
|
270
|
+
delegationInstanceId: isString, delegationBindingId: isString, delegationRenewalRevision: isNumber,
|
|
190
271
|
agentInfo: isAgentInfo, supportsImageInput: isVerifiedTrue, configOptions: isNullableSessionConfigOptionArray,
|
|
191
272
|
// Same guard as `agent_verified.authMethods`, and the same absent-vs-`[]` reading: a
|
|
192
273
|
// half-decoded method table is worse than none, because the reader would offer a login the
|
|
193
274
|
// agent never named.
|
|
194
275
|
authMethods: isNullableAuthMethodInfoArray,
|
|
195
276
|
errorCode: isNullableJsonRpcErrorCode,
|
|
277
|
+
// `true`-or-absent, the same rule as `supportsSteering` above: absence already means "no
|
|
278
|
+
// collector wired up", so a `false` would be a second spelling of it.
|
|
279
|
+
reportsActivity: isVerifiedTrue,
|
|
280
|
+
supportsAsyncTaskStop: isVerifiedTrue,
|
|
281
|
+
// Vendor-verbatim and uninterpreted here (decision L1) -- any object shape is accepted, the
|
|
282
|
+
// same lenience `agent_event.payload` gets via `isRecord`.
|
|
283
|
+
activityRaw: isRecord,
|
|
284
|
+
// Task 21 (spec §5.5.4): `true`-or-absent, and the two id lists.
|
|
285
|
+
durable: isVerifiedTrue, pendingRequestIds: isStringArray, steeredIds: isStringArray,
|
|
196
286
|
},
|
|
197
287
|
},
|
|
198
|
-
agent_prompt: { required: { sessionRef: isString, text: isString }, optional: { images: isImageBlockArray } },
|
|
288
|
+
agent_prompt: { required: { sessionRef: isString, text: isString }, optional: { images: isImageBlockArray, platformTurnId: isString } },
|
|
289
|
+
agent_delegation_renew: { required: { id: isString, sessionRef: isString, nativeSessionId: isString,
|
|
290
|
+
instanceId: isString, bindingId: isString, renewalRevision: isNumber, expiresAtEpochMs: isNumber } },
|
|
291
|
+
agent_delegation_renewed: { required: { id: isString, ok: isBoolean }, optional: { error: isString,
|
|
292
|
+
nativeSessionId: isString, instanceId: isString, bindingId: isString, renewalRevision: isNumber, expiresAtEpochMs: isNumber } },
|
|
199
293
|
agent_answer: { required: { sessionRef: isString, requestId: isString, outcome: isString } },
|
|
294
|
+
// `content`'s interior is never validated here -- it is ACP's free-form elicitation payload, and
|
|
295
|
+
// this table is transport, not translator. `isRecord` only rejects a non-object.
|
|
296
|
+
agent_elicitation_answer: {
|
|
297
|
+
required: { sessionRef: isString, requestId: isString, action: isString },
|
|
298
|
+
optional: { content: isRecord },
|
|
299
|
+
},
|
|
200
300
|
agent_interrupt: { required: { sessionRef: isString } },
|
|
201
301
|
agent_close: { required: { sessionRef: isString } },
|
|
202
302
|
agent_event: { required: { sessionRef: isString, seq: isNumber, eventName: isString, payload: isRecord } },
|
|
@@ -206,6 +306,8 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
206
306
|
stopReason: isString, nativeSessionId: isNullableString, error: isNullableString,
|
|
207
307
|
authMethods: isNullableAuthMethodInfoArray,
|
|
208
308
|
errorCode: isNullableJsonRpcErrorCode,
|
|
309
|
+
platformTurnId: isString,
|
|
310
|
+
executionEnded: isVerifiedTrue,
|
|
209
311
|
},
|
|
210
312
|
},
|
|
211
313
|
agent_event_ack: { required: { sessionRef: isString, throughSeq: isNumber } },
|
|
@@ -214,6 +316,12 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
214
316
|
// to return its protocol-level `ok: false` response. The transport only requires that the
|
|
215
317
|
// field is present; semantic validation remains at the request handler boundary.
|
|
216
318
|
agent_list_native: { required: { id: isString, bridge: isString, query: () => true } },
|
|
319
|
+
// `query` is as lenient as `agent_list_native`'s, for the same reason.
|
|
320
|
+
agent_delete_native: { required: { id: isString, bridge: isString, query: () => true, nativeSessionIds: isStringArray } },
|
|
321
|
+
agent_native_deleted: {
|
|
322
|
+
required: { id: isString, ok: isBoolean },
|
|
323
|
+
optional: { results: isNativeDeleteResults, error: isNullableString },
|
|
324
|
+
},
|
|
217
325
|
agent_native_sessions: {
|
|
218
326
|
required: { id: isString, ok: isBoolean },
|
|
219
327
|
optional: {
|
|
@@ -222,6 +330,9 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
222
330
|
},
|
|
223
331
|
// Same lenience as `agent_list_native` above, for the same reason: the session manager wants to
|
|
224
332
|
// answer a malformed query with its own `ok: false` rather than have the transport drop it.
|
|
333
|
+
agent_account_read: { required: { id: isString, bridge: isString, agentKey: isString, query: () => true } },
|
|
334
|
+
agent_account_read_result: { required: { id: isString, ok: isBoolean }, optional: { snapshot: (value) => accountLimitsSchema.safeParse(value).success, error: isString } },
|
|
335
|
+
agent_account_update: { required: { agentKey: isString, snapshot: (value) => accountLimitsSchema.safeParse(value).success } },
|
|
225
336
|
agent_verify: {
|
|
226
337
|
required: { id: isString, bridge: isString, query: () => true },
|
|
227
338
|
// 与 `query` 那条**刻意的宽松**相反,这里是严格的:一份畸形的 query 还有个能回
|
|
@@ -290,17 +401,75 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
290
401
|
required: { id: isString, ok: isBoolean },
|
|
291
402
|
optional: { configOptions: isNullableSessionConfigOptionArray, error: isNullableString },
|
|
292
403
|
},
|
|
404
|
+
agent_session_state: { required: { sessionRef: isString, vendor: isString, raw: isRecord } },
|
|
405
|
+
agent_runtime_profile: { required: { sessionRef: isString, diagnostics: isRuntimeDiagnostics } },
|
|
406
|
+
agent_provider_task_event: { required: { sessionRef: isString, rootSessionId: isTaskId,
|
|
407
|
+
runtimeGeneration: isTaskId, taskId: isTaskId, uuid: isTaskId, state: isProviderTaskState,
|
|
408
|
+
live: isBoolean, stale: isBoolean }, optional: { taskType: isTaskType, background: isBoolean,
|
|
409
|
+
agentId: isTaskId, launchingToolUseId: isTaskId, status: isProviderTerminalState, summary: isTaskSummary } },
|
|
410
|
+
agent_provider_task_stop: { required: { id: isTaskId, sessionRef: isTaskId, rootSessionId: isTaskId,
|
|
411
|
+
runtimeGeneration: isTaskId, taskId: isTaskId, requestId: isTaskId } },
|
|
412
|
+
agent_provider_task_stopped: { required: { id: isTaskId, state: isProviderStopState },
|
|
413
|
+
optional: { error: isTaskSummary } },
|
|
414
|
+
agent_async_task_stop: { required: { sessionRef: isString, asyncTaskId: isString } },
|
|
415
|
+
agent_switch_idle: { required: { id: isString, bridge: isString }, optional: { toBuild: isString } },
|
|
416
|
+
agent_switch_idle_result: {
|
|
417
|
+
required: { id: isString, ok: isBoolean },
|
|
418
|
+
optional: { registered: isCount, immediate: isCount, unmovable: isSwitchIdleUnmovableArray, error: isNullableString },
|
|
419
|
+
},
|
|
293
420
|
};
|
|
294
421
|
const DISCRIMINATORS = new Set(Object.keys(AGENT_FRAME_SHAPES));
|
|
422
|
+
/**
|
|
423
|
+
* `agent_reopen_request{sessionRef, reason}` (spec 2026-10-11 §6.3, C→S): a session that is idle and has a pending switch (an adapter
|
|
424
|
+
* update, a keeper-acp generation change) asks the server to send `agent_open{requireSessionLoad:true}` with the session's fresh
|
|
425
|
+
* environment — a switch needs the spawn secrets only an `agent_open` carries. Task 24 decides when; the manager's
|
|
426
|
+
* `requestReopen` sends it, and only to a server advertising {@link AGENT_WIRE_REOPEN_REQUEST_VERSION}: an older server cannot
|
|
427
|
+
* decode it, and eight undecodable frames in a row close the agent channel (spec §10: there the switch waits for a natural reopen).
|
|
428
|
+
*/
|
|
429
|
+
export const AGENT_WIRE_REOPEN_REQUEST_VERSION = 25;
|
|
430
|
+
/**
|
|
431
|
+
* The same version (Task 22): the server acknowledges as soon as a subagent relation releases the watermark it capped, and stops
|
|
432
|
+
* wasting its 32-event ack count on capped attempts — so a sent relation-pinned outbox entry may count against the sending
|
|
433
|
+
* window again (`SessionOutbox.inflight`). Below it the entries keep not counting once sent (fix round 1 of Task 20).
|
|
434
|
+
*/
|
|
435
|
+
export const AGENT_WIRE_RELATION_ACK_VERSION = 25;
|
|
436
|
+
export const AGENT_REOPEN_REQUEST_SHAPE = AGENT_FRAME_SHAPES.agent_reopen_request;
|
|
437
|
+
export function agentReopenRequest(sessionRef, reason) {
|
|
438
|
+
return { t: 'agent_reopen_request', sessionRef, reason };
|
|
439
|
+
}
|
|
440
|
+
function isCount(value) { return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0; }
|
|
441
|
+
function isSwitchIdleUnmovableArray(value) {
|
|
442
|
+
return Array.isArray(value) && value.every((entry) => isObject(entry) && isString(entry.sessionRef) && isString(entry.reason));
|
|
443
|
+
}
|
|
295
444
|
function isRecord(value) { return isObject(value); }
|
|
445
|
+
function isTaskId(value) {
|
|
446
|
+
return typeof value === 'string' && value.length > 0 &&
|
|
447
|
+
Buffer.byteLength(value, 'utf8') <= 256 && !/[\x00-\x1f\x7f]/.test(value);
|
|
448
|
+
}
|
|
449
|
+
function isTaskType(value) { return isTaskId(value) && Buffer.byteLength(value, 'utf8') <= 128; }
|
|
450
|
+
function isTaskSummary(value) {
|
|
451
|
+
return typeof value === 'string' &&
|
|
452
|
+
Buffer.byteLength(value, 'utf8') <= 8192;
|
|
453
|
+
}
|
|
454
|
+
function isProviderTaskState(value) {
|
|
455
|
+
return value === 'pending' || value === 'paused' || value === 'running' || value === 'completed' ||
|
|
456
|
+
value === 'stopped' || value === 'failed' || value === 'unknown';
|
|
457
|
+
}
|
|
458
|
+
function isProviderTerminalState(value) {
|
|
459
|
+
return value === 'completed' || value === 'failed' || value === 'stopped';
|
|
460
|
+
}
|
|
461
|
+
function isProviderStopState(value) {
|
|
462
|
+
return value === 'accepted' || value === 'unknown' || value === 'rejected';
|
|
463
|
+
}
|
|
296
464
|
function isAgentResumeSession(value) {
|
|
297
465
|
return isObject(value)
|
|
298
|
-
&& hasRequired(value, { sessionRef: isString, journalMaxSeq: isNumber, agentAlive: isBoolean })
|
|
466
|
+
&& hasRequired(value, { sessionRef: isString, journalMaxSeq: isNumber, agentAlive: isBoolean })
|
|
467
|
+
&& (value.pendingRequestIds === undefined || isStringArray(value.pendingRequestIds));
|
|
299
468
|
}
|
|
300
469
|
/**
|
|
301
470
|
* Accepts only the two outcomes this client can produce. `startedNewTurn` is refused on purpose: it
|
|
302
471
|
* is the extension's third outcome, and receiving one means a peer invented a disposition for a
|
|
303
|
-
* message the server has already put on its timeline — see `
|
|
472
|
+
* message the server has already put on its timeline — see `acp/steering.ts`'s `mapSteerOutcome`.
|
|
304
473
|
*/
|
|
305
474
|
function isSteerOutcome(value) { return value === 'injected' || value === 'promptRequired'; }
|
|
306
475
|
/** Accepts `true` and nothing else — the whole point of `authenticated` having no `false`. */
|
|
@@ -448,8 +617,17 @@ function isNativeSession(value) {
|
|
|
448
617
|
nativeSessionId: isString, title: isString, updatedAtEpochMs: isNumber, cwd: isString,
|
|
449
618
|
});
|
|
450
619
|
}
|
|
620
|
+
function isNativeDeleteResult(value) {
|
|
621
|
+
return isObject(value)
|
|
622
|
+
&& hasRequired(value, { nativeSessionId: isString, outcome: (o) => o === 'deleted' || o === 'unsupported' || o === 'failed' })
|
|
623
|
+
&& hasOptional(value, { error: isNullableString });
|
|
624
|
+
}
|
|
625
|
+
function isNativeDeleteResults(value) {
|
|
626
|
+
return Array.isArray(value) && value.every(isNativeDeleteResult);
|
|
627
|
+
}
|
|
451
628
|
function isEvent(value) {
|
|
452
|
-
return isObject(value) && hasRequired(value, { eventName: isString, payload: isRecord })
|
|
629
|
+
return isObject(value) && hasRequired(value, { eventName: isString, payload: isRecord })
|
|
630
|
+
&& hasOptional(value, { acpSessionId: isNullableString });
|
|
453
631
|
}
|
|
454
632
|
function isAgentFrame(value) {
|
|
455
633
|
// Cast, not a narrowed lookup: `value.t` is unvetted wire input, so it may not be one of
|
|
@@ -588,11 +766,16 @@ export function outboundFailureReply(frame, reason) {
|
|
|
588
766
|
switch (frame.t) {
|
|
589
767
|
case 'agent_opened': return { t: 'agent_opened', id, ok: false, error };
|
|
590
768
|
case 'agent_native_sessions': return { t: 'agent_native_sessions', id, ok: false, error };
|
|
769
|
+
case 'agent_native_deleted': return { t: 'agent_native_deleted', id, ok: false, error };
|
|
591
770
|
case 'agent_verified': return { t: 'agent_verified', id, ok: false, error };
|
|
592
771
|
case 'agent_authenticated': return { t: 'agent_authenticated', id, ok: false, error };
|
|
593
772
|
case 'agent_installed': return { t: 'agent_installed', id, ok: false, error };
|
|
594
773
|
case 'agent_steered': return { t: 'agent_steered', id, ok: false, error };
|
|
774
|
+
case 'agent_account_read_result': return { t: 'agent_account_read_result', id, ok: false, error };
|
|
595
775
|
case 'agent_config_option_set': return { t: 'agent_config_option_set', id, ok: false, error };
|
|
776
|
+
case 'agent_delegation_renewed': return { t: 'agent_delegation_renewed', id, ok: false, error };
|
|
777
|
+
case 'agent_switch_idle_result': return { t: 'agent_switch_idle_result', id, ok: false, error };
|
|
778
|
+
case 'agent_provider_task_stopped': return { t: 'agent_provider_task_stopped', id, state: 'unknown', error };
|
|
596
779
|
case 'agent_backfill_done':
|
|
597
780
|
case 'agent_backfill_events': return { t: 'agent_backfill_done', id, ok: false, count: 0, error };
|
|
598
781
|
default: return null;
|
|
@@ -602,5 +785,15 @@ export function encodeAgentFrame(frame) {
|
|
|
602
785
|
return JSON.stringify(frame);
|
|
603
786
|
}
|
|
604
787
|
export function decodeAgentFrame(text) {
|
|
605
|
-
|
|
788
|
+
const frame = decodeChannelFrame(text, DISCRIMINATORS, isAgentFrame);
|
|
789
|
+
if (frame.t === 'agent_provider_task_event') {
|
|
790
|
+
if ((frame.state === 'completed' || frame.state === 'failed' || frame.state === 'stopped') && frame.live)
|
|
791
|
+
throw new TypeError('Invalid provider task terminal live state');
|
|
792
|
+
if (frame.status !== undefined &&
|
|
793
|
+
(frame.status !== frame.state || !['completed', 'failed', 'stopped'].includes(frame.status)))
|
|
794
|
+
throw new TypeError('Invalid provider task terminal status');
|
|
795
|
+
if (frame.summary !== undefined && frame.status === undefined)
|
|
796
|
+
throw new TypeError('Provider task summary requires terminal status');
|
|
797
|
+
}
|
|
798
|
+
return frame;
|
|
606
799
|
}
|
|
@@ -23,6 +23,21 @@ export function isAuthMethodInfo(value) {
|
|
|
23
23
|
&& hasOptional(value, { type: isNullableString, description: isNullableString, args: isNullableStringArray });
|
|
24
24
|
}
|
|
25
25
|
export const isNullableAuthMethodInfoArray = (value) => value === null || (Array.isArray(value) && value.every(isAuthMethodInfo));
|
|
26
|
+
export function isMissingPrerequisite(value) {
|
|
27
|
+
return isObject(value) && hasRequired(value, { label: isString }) && hasOptional(value, { hint: isString });
|
|
28
|
+
}
|
|
29
|
+
const isCount = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
30
|
+
export function isBridgeCurrentBuild(value) {
|
|
31
|
+
return isObject(value) && hasRequired(value, { id: isString, version: isString }) && hasOptional(value, { patched: isTrueOnly });
|
|
32
|
+
}
|
|
33
|
+
export function isBridgeBuild(value) {
|
|
34
|
+
return isObject(value)
|
|
35
|
+
&& hasRequired(value, { id: isString, version: isString, sessions: isCount, idle: isCount, busy: isCount, unmovable: isCount })
|
|
36
|
+
&& hasOptional(value, { patched: isTrueOnly, current: isTrueOnly, busyReasons: isStringArray, unmovableReasons: isStringArray });
|
|
37
|
+
}
|
|
38
|
+
export function isBridgeSwitchPending(value) {
|
|
39
|
+
return isObject(value) && hasRequired(value, { remaining: isCount }) && hasOptional(value, { toBuild: isString });
|
|
40
|
+
}
|
|
26
41
|
export function isBridgeInfo(value) {
|
|
27
42
|
return isObject(value)
|
|
28
43
|
&& hasRequired(value, { type: isString })
|
|
@@ -49,7 +64,10 @@ export function isBridgeInfo(value) {
|
|
|
49
64
|
// string is all a peer sending it without the flag is asked to satisfy.
|
|
50
65
|
platformTriple: isString,
|
|
51
66
|
// `true` and nothing else, like `supportsSteering`: see `BridgeInfo.recommended`.
|
|
67
|
+
curated: isTrueOnly,
|
|
52
68
|
recommended: isTrueOnly,
|
|
69
|
+
detected: isTrueOnly,
|
|
70
|
+
missingPrerequisite: isMissingPrerequisite,
|
|
53
71
|
defaultName: isNullableString,
|
|
54
72
|
defaultLabel: isNullableString,
|
|
55
73
|
// Optional on purpose: see BridgeInfo's doc comment — absent is "unknown", and a guard that
|
|
@@ -60,6 +78,11 @@ export function isBridgeInfo(value) {
|
|
|
60
78
|
// A peer that sent one would be publishing "we looked and it cannot steer" through a key
|
|
61
79
|
// whose absence already says exactly that, so refusing it keeps one spelling per fact.
|
|
62
80
|
supportsSteering: isTrueOnly,
|
|
81
|
+
supportsAccountLimits: isTrueOnly,
|
|
82
|
+
// Task 25 (spec §6.4): scan-time build data. Optional: absent is "not looked at".
|
|
83
|
+
currentBuild: isBridgeCurrentBuild,
|
|
84
|
+
builds: (value) => Array.isArray(value) && value.every(isBridgeBuild),
|
|
85
|
+
switchPending: isBridgeSwitchPending,
|
|
63
86
|
});
|
|
64
87
|
}
|
|
65
88
|
export function isBridgeInfoArray(value) {
|
|
@@ -97,6 +120,19 @@ export function bridgesForWire(bridges, frame) {
|
|
|
97
120
|
}
|
|
98
121
|
return kept;
|
|
99
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* The roster as a hello carries it: without the scan-only build fields (`currentBuild`/`builds`/`switchPending`, spec §6.4). The
|
|
125
|
+
* daemon keeps one cached roster, which a scan refreshes with those fields; a later reconnect's hello would otherwise carry a
|
|
126
|
+
* snapshot of sessions and pending moves that is stale by then. Only the scan answers them, fresh.
|
|
127
|
+
*/
|
|
128
|
+
export function helloRoster(bridges) {
|
|
129
|
+
return bridges.map((bridge) => {
|
|
130
|
+
if (bridge.currentBuild === undefined && bridge.builds === undefined && bridge.switchPending === undefined)
|
|
131
|
+
return bridge;
|
|
132
|
+
const { currentBuild: _current, builds: _builds, switchPending: _pending, ...rest } = bridge;
|
|
133
|
+
return rest;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
100
136
|
function partitionWireSafeBridges(bridges) {
|
|
101
137
|
let parsed;
|
|
102
138
|
try {
|