@platless/axom-cli 0.3.13 → 0.6.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -5
- package/dist/axom-patch/cli/README.md +119 -9
- 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 +123 -0
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +81 -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/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
- package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
- package/dist/axom-patch/cli/nightly-task.md +194 -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/codex-terminal-output.mjs +33 -0
- package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
- package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -5
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -7
- package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
- package/dist/axom-patch/cli/qoder-steering.md +20 -25
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
- package/dist/axom-patch/cli/runtime.json +39 -15
- package/dist/axom-patch/cli/zcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/zcode-models.json +37 -0
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +72 -0
- package/dist/cli.js +4 -9
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +65 -3
- package/dist/commands/workstation.js +73 -7
- 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 +1 -1
- package/dist/native/darwin-arm64/axom-quic +0 -0
- package/dist/test/keeperKit.js +67 -0
- package/dist/workstation/acp/acpAgents.js +2192 -636
- package/dist/workstation/acp/acpClient.js +84 -495
- 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 +324 -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 +290 -0
- package/dist/workstation/acp/installErrors.js +12 -2
- package/dist/workstation/acp/installProbe.js +1 -1
- package/dist/workstation/acp/managedPatches.js +239 -159
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +60 -3
- package/dist/workstation/acp/permission.js +42 -10
- package/dist/workstation/acp/processor/checkpoint.js +78 -0
- package/dist/workstation/acp/processor/commands.js +35 -0
- package/dist/workstation/acp/processor/control.js +282 -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 +105 -0
- package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
- package/dist/workstation/acp/processor/session.js +496 -0
- package/dist/workstation/acp/processor/state.js +67 -0
- package/dist/workstation/acp/processor/step.js +442 -0
- package/dist/workstation/acp/processor/test/fixtureLog.js +58 -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 +390 -0
- package/dist/workstation/acp/processorSession.js +1348 -0
- package/dist/workstation/acp/registry/catalog.js +189 -41
- package/dist/workstation/acp/registry/launch.js +76 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +111 -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/fakeAgent.js +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +295 -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/sanitize.js +344 -0
- package/dist/workstation/acp/test/raw/sources.js +135 -0
- package/dist/workstation/acp/test/realAdapter.js +331 -0
- package/dist/workstation/acp/test/scriptAgent.js +214 -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 +1289 -544
- 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 +172 -8
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +16 -4
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +200 -0
- package/dist/workstation/control/commands.js +124 -0
- package/dist/workstation/control/daemonIdentity.js +53 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +63 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/runtimeClient.js +97 -0
- package/dist/workstation/control/runtimeHost.js +148 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/daemon.js +393 -12
- package/dist/workstation/daemonctl.js +103 -14
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/capabilities.js +92 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +353 -0
- package/dist/workstation/desktopControl/lobby.js +129 -0
- package/dist/workstation/desktopControl/macProvider.js +155 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/protocol.js +302 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1681 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +78 -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/credentials.js +142 -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} +152 -39
- 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 +124 -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 +289 -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 +76 -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/managedInstall.js +57 -0
- package/dist/workstation/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +50 -19
- package/dist/workstation/protocol/common.js +30 -0
- package/dist/workstation/protocol/computer.js +15 -4
- package/dist/workstation/protocol/management.js +141 -4
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +119 -0
- package/dist/workstation/rd/control.js +119 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/provider.js +222 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +45 -0
- package/dist/workstation/rd/update.js +171 -0
- package/dist/workstation/rd/upkeep.js +38 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/service/installer.js +104 -13
- package/dist/workstation/service/launchd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +154 -93
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- 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/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +3 -0
- package/dist/workstation/update/globalInstall.js +116 -0
- package/dist/workstation/update/npmInstaller.js +49 -23
- package/dist/workstation/update/ptyPreflight.js +21 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/runUpdate.js +3 -2
- package/dist/workstation/update/taskRegistry.js +105 -11
- package/dist/workstation/update/watchdog.js +16790 -334
- package/package.json +11 -8
- package/skills/axom/SKILL.md +11 -11
- package/skills/axom-definition/SKILL.md +3 -3
- package/skills/axom-platform/SKILL.md +3 -3
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -49
- package/dist/axom-patch/cli/qoder-steering.json +0 -52
- package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
- package/dist/commands/guide.js +0 -38
- package/dist/workstation/acp/acpSession.js +0 -1475
- package/dist/workstation/acp/contract.js +0 -499
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/draftInterceptor.js +0 -163
- package/dist/workstation/acp/qoderStartup.js +0 -45
- 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/backgroundProcess.js +0 -106
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -262
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -30
- package/dist/workstation/qoderAdapter/history.js +0 -87
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -93
- package/dist/workstation/qoderAdapter/mapper.js +0 -117
- package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
- package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
- package/dist/workstation/qoderAdapter/permissions.js +0 -61
- package/dist/workstation/qoderAdapter/profile.js +0 -93
- package/dist/workstation/qoderAdapter/sdkDriver.js +0 -49
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -77
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- package/dist/workstation/tools/core.js +0 -50
- package/dist/workstation/tools/files.js +0 -525
- package/dist/workstation/tools/terminal.js +0 -214
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { LocalControlError } from './control/protocol.js';
|
|
5
|
+
const schema = z.object({ schemaVersion: z.literal(1), source: z.literal('managed'), prefix: z.string(), nodePath: z.string(), npmPath: z.string(), cliPath: z.string(), entryPath: z.string(), runtimeVersion: z.string().regex(/^24\.\d+\.\d+$/), architecture: z.enum(['arm64', 'x64']) }).strict();
|
|
6
|
+
function plannedRealPath(value) {
|
|
7
|
+
try {
|
|
8
|
+
return realpathSync(value);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code !== 'ENOENT' || path.dirname(value) === value)
|
|
12
|
+
throw error;
|
|
13
|
+
return path.join(plannedRealPath(path.dirname(value)), path.basename(value));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** This synchronous variant is shared by the existing synchronous service-context constructors. */
|
|
17
|
+
export function loadManagedCliInstallSync(packageRoot) {
|
|
18
|
+
let root;
|
|
19
|
+
try {
|
|
20
|
+
root = realpathSync(packageRoot);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error.code === 'ENOENT')
|
|
24
|
+
return null;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
const prefix = path.resolve(root, '../../../..');
|
|
28
|
+
if (root !== path.join(prefix, 'lib/node_modules/@platless/axom-cli'))
|
|
29
|
+
return null;
|
|
30
|
+
const file = path.join(prefix, 'install.json');
|
|
31
|
+
let found = false;
|
|
32
|
+
try {
|
|
33
|
+
const stat = lstatSync(file);
|
|
34
|
+
found = true;
|
|
35
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16384 || (process.getuid && stat.uid !== process.getuid()) || (stat.mode & 0o077))
|
|
36
|
+
throw Error('Unsafe metadata');
|
|
37
|
+
const value = schema.parse(JSON.parse(readFileSync(file, 'utf8')));
|
|
38
|
+
const home = path.resolve(prefix, '../..');
|
|
39
|
+
const runtime = path.join(home, '.axom/runtime/node', `${value.runtimeVersion}-darwin-${value.architecture}`);
|
|
40
|
+
if (prefix !== path.join(home, '.axom/cli') || realpathSync(value.prefix) !== prefix || realpathSync(value.cliPath) !== path.join(root, 'dist/cli.js') || realpathSync(value.nodePath) !== path.join(runtime, 'bin/node') || realpathSync(value.npmPath) !== path.join(runtime, 'bin/npm') || plannedRealPath(value.entryPath) !== path.join(home, '.local/bin/axom'))
|
|
41
|
+
throw Error('Metadata does not describe this installation');
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (!found && error.code === 'ENOENT')
|
|
46
|
+
return null;
|
|
47
|
+
throw new LocalControlError('UNSUPPORTED_INSTALLATION', 'The managed CLI installation needs repair');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function loadManagedCliInstall(packageRoot) { return loadManagedCliInstallSync(packageRoot); }
|
|
51
|
+
export function npmExecutionContext(install, inherited) {
|
|
52
|
+
if (!install)
|
|
53
|
+
return { ...inherited };
|
|
54
|
+
const result = { ...inherited, NPM_CONFIG_PREFIX: install.prefix, PATH: `${path.dirname(install.nodePath)}:${inherited.PATH ?? '/usr/bin:/bin'}` };
|
|
55
|
+
delete result.npm_config_prefix;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
@@ -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,14 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { accountLimitsSchema } from './accountLimits.js';
|
|
2
|
+
import { isRuntimeDiagnostics } from './runtimeDiagnostics.js';
|
|
2
3
|
import { decodeChannelFrame, hasOptional, hasRequired, isBoolean, isBridgeInfoArray, isNullableAuthMethodInfoArray, isNullableString, isNumber, isObject, isString, isStringArray, isStringRecord, } from './common.js';
|
|
3
4
|
/** Current version and feature floors for the independently negotiated agent protocol. */
|
|
4
|
-
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;
|
|
5
13
|
/** `agent_delete_native` / `agent_native_deleted`: purge a session Axom created on an external agent. */
|
|
6
14
|
export const AGENT_WIRE_NATIVE_DELETE_VERSION = 24;
|
|
7
15
|
export const AGENT_WIRE_PROVIDER_TASK_VERSION = 23;
|
|
8
16
|
export const AGENT_WIRE_RUNTIME_PROFILE_VERSION = 22;
|
|
9
|
-
export const AGENT_WIRE_DELEGATION_RENEW_VERSION = 21;
|
|
10
17
|
export const AGENT_WIRE_TURN_IDENTITY_VERSION = 20;
|
|
11
|
-
export const AGENT_WIRE_DELEGATION_MCP_VERSION = 19;
|
|
12
18
|
/** Scoped replay/history is enabled only when both peers preserve native origin. */
|
|
13
19
|
export const AGENT_WIRE_SCOPED_ORIGIN_VERSION = 18;
|
|
14
20
|
/** agent_open.requireSessionLoad: reject unsupported/failed load without session/new fallback. */
|
|
@@ -207,7 +213,7 @@ export const AGENT_WIRE_ELICITATION_VERSION = 16;
|
|
|
207
213
|
*
|
|
208
214
|
* Declarative on purpose: the required-field half of this table is the *only* statement anywhere of
|
|
209
215
|
* which fields the Kotlin twin will refuse to decode without, and the contract suite
|
|
210
|
-
* (`
|
|
216
|
+
* (`protocol/agentRequiredFields.test.ts`'s `assertRequiredFieldsEnforced`) reads it directly to prove each one is really
|
|
211
217
|
* enforced. Left as inline `hasRequired({...})` calls inside a `switch`, that list is unreadable from
|
|
212
218
|
* a test, so the only way to check it would be a hand-maintained copy in the test file — which is
|
|
213
219
|
* precisely how `agent_authenticate.methodId` came to have no guard at all while a report still
|
|
@@ -229,6 +235,7 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
229
235
|
optional: { sessions: (sessions) => Array.isArray(sessions) && sessions.every(isAgentResumeSession) },
|
|
230
236
|
},
|
|
231
237
|
heartbeat_ack: { required: { seq: isNumber } },
|
|
238
|
+
agent_reopen_request: { required: { sessionRef: isString, reason: isString } },
|
|
232
239
|
agent_open: {
|
|
233
240
|
required: {
|
|
234
241
|
id: isString, sessionRef: isString, agentKey: isString, bridge: isString, cwd: isString, shell: isString,
|
|
@@ -237,9 +244,6 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
237
244
|
env: isStringRecord, pathPrepend: isStringArray, agentEnv: isStringRecord, command: isNullableString,
|
|
238
245
|
args: isStringArray, nativeSessionId: isNullableString,
|
|
239
246
|
resumeFromSeq: isNumber, backfill: isBoolean, requireSessionLoad: isBoolean,
|
|
240
|
-
delegationMcp: (value) => isObject(value) &&
|
|
241
|
-
isString(value.url) && isStringRecord(value.headers) && isNumber(value.expiresAtEpochMs) &&
|
|
242
|
-
(value.bindingId === undefined || isString(value.bindingId)),
|
|
243
247
|
},
|
|
244
248
|
},
|
|
245
249
|
agent_opened: {
|
|
@@ -256,9 +260,6 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
256
260
|
// `reloaded` is `true`-or-absent for the same reason as `supportsSteering`: a `false` would
|
|
257
261
|
// be a second spelling of what its absence already says.
|
|
258
262
|
reloaded: isVerifiedTrue,
|
|
259
|
-
delegationExpiresAtEpochMs: isNumber,
|
|
260
|
-
delegationRefreshPending: isVerifiedTrue,
|
|
261
|
-
delegationInstanceId: isString, delegationBindingId: isString, delegationRenewalRevision: isNumber,
|
|
262
263
|
agentInfo: isAgentInfo, supportsImageInput: isVerifiedTrue, configOptions: isNullableSessionConfigOptionArray,
|
|
263
264
|
// Same guard as `agent_verified.authMethods`, and the same absent-vs-`[]` reading: a
|
|
264
265
|
// half-decoded method table is worse than none, because the reader would offer a login the
|
|
@@ -272,13 +273,11 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
272
273
|
// Vendor-verbatim and uninterpreted here (decision L1) -- any object shape is accepted, the
|
|
273
274
|
// same lenience `agent_event.payload` gets via `isRecord`.
|
|
274
275
|
activityRaw: isRecord,
|
|
276
|
+
// Task 21 (spec §5.5.4): `true`-or-absent, and the two id lists.
|
|
277
|
+
durable: isVerifiedTrue, pendingRequestIds: isStringArray, steeredIds: isStringArray,
|
|
275
278
|
},
|
|
276
279
|
},
|
|
277
280
|
agent_prompt: { required: { sessionRef: isString, text: isString }, optional: { images: isImageBlockArray, platformTurnId: isString } },
|
|
278
|
-
agent_delegation_renew: { required: { id: isString, sessionRef: isString, nativeSessionId: isString,
|
|
279
|
-
instanceId: isString, bindingId: isString, renewalRevision: isNumber, expiresAtEpochMs: isNumber } },
|
|
280
|
-
agent_delegation_renewed: { required: { id: isString, ok: isBoolean }, optional: { error: isString,
|
|
281
|
-
nativeSessionId: isString, instanceId: isString, bindingId: isString, renewalRevision: isNumber, expiresAtEpochMs: isNumber } },
|
|
282
281
|
agent_answer: { required: { sessionRef: isString, requestId: isString, outcome: isString } },
|
|
283
282
|
// `content`'s interior is never validated here -- it is ACP's free-form elicitation payload, and
|
|
284
283
|
// this table is transport, not translator. `isRecord` only rejects a non-object.
|
|
@@ -319,6 +318,9 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
319
318
|
},
|
|
320
319
|
// Same lenience as `agent_list_native` above, for the same reason: the session manager wants to
|
|
321
320
|
// answer a malformed query with its own `ok: false` rather than have the transport drop it.
|
|
321
|
+
agent_account_read: { required: { id: isString, bridge: isString, agentKey: isString, query: () => true } },
|
|
322
|
+
agent_account_read_result: { required: { id: isString, ok: isBoolean }, optional: { snapshot: (value) => accountLimitsSchema.safeParse(value).success, error: isString } },
|
|
323
|
+
agent_account_update: { required: { agentKey: isString, snapshot: (value) => accountLimitsSchema.safeParse(value).success } },
|
|
322
324
|
agent_verify: {
|
|
323
325
|
required: { id: isString, bridge: isString, query: () => true },
|
|
324
326
|
// 与 `query` 那条**刻意的宽松**相反,这里是严格的:一份畸形的 query 还有个能回
|
|
@@ -398,8 +400,35 @@ export const AGENT_FRAME_SHAPES = {
|
|
|
398
400
|
agent_provider_task_stopped: { required: { id: isTaskId, state: isProviderStopState },
|
|
399
401
|
optional: { error: isTaskSummary } },
|
|
400
402
|
agent_async_task_stop: { required: { sessionRef: isString, asyncTaskId: isString } },
|
|
403
|
+
agent_switch_idle: { required: { id: isString, bridge: isString }, optional: { toBuild: isString } },
|
|
404
|
+
agent_switch_idle_result: {
|
|
405
|
+
required: { id: isString, ok: isBoolean },
|
|
406
|
+
optional: { registered: isCount, immediate: isCount, unmovable: isSwitchIdleUnmovableArray, error: isNullableString },
|
|
407
|
+
},
|
|
401
408
|
};
|
|
402
409
|
const DISCRIMINATORS = new Set(Object.keys(AGENT_FRAME_SHAPES));
|
|
410
|
+
/**
|
|
411
|
+
* `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
|
|
412
|
+
* update, a keeper-acp generation change) asks the server to send `agent_open{requireSessionLoad:true}` with the session's fresh
|
|
413
|
+
* environment — a switch needs the spawn secrets only an `agent_open` carries. Task 24 decides when; the manager's
|
|
414
|
+
* `requestReopen` sends it, and only to a server advertising {@link AGENT_WIRE_REOPEN_REQUEST_VERSION}: an older server cannot
|
|
415
|
+
* decode it, and eight undecodable frames in a row close the agent channel (spec §10: there the switch waits for a natural reopen).
|
|
416
|
+
*/
|
|
417
|
+
export const AGENT_WIRE_REOPEN_REQUEST_VERSION = 25;
|
|
418
|
+
/**
|
|
419
|
+
* The same version (Task 22): the server acknowledges as soon as a subagent relation releases the watermark it capped, and stops
|
|
420
|
+
* wasting its 32-event ack count on capped attempts — so a sent relation-pinned outbox entry may count against the sending
|
|
421
|
+
* window again (`SessionOutbox.inflight`). Below it the entries keep not counting once sent (fix round 1 of Task 20).
|
|
422
|
+
*/
|
|
423
|
+
export const AGENT_WIRE_RELATION_ACK_VERSION = 25;
|
|
424
|
+
export const AGENT_REOPEN_REQUEST_SHAPE = AGENT_FRAME_SHAPES.agent_reopen_request;
|
|
425
|
+
export function agentReopenRequest(sessionRef, reason) {
|
|
426
|
+
return { t: 'agent_reopen_request', sessionRef, reason };
|
|
427
|
+
}
|
|
428
|
+
function isCount(value) { return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0; }
|
|
429
|
+
function isSwitchIdleUnmovableArray(value) {
|
|
430
|
+
return Array.isArray(value) && value.every((entry) => isObject(entry) && isString(entry.sessionRef) && isString(entry.reason));
|
|
431
|
+
}
|
|
403
432
|
function isRecord(value) { return isObject(value); }
|
|
404
433
|
function isTaskId(value) {
|
|
405
434
|
return typeof value === 'string' && value.length > 0 &&
|
|
@@ -422,12 +451,13 @@ function isProviderStopState(value) {
|
|
|
422
451
|
}
|
|
423
452
|
function isAgentResumeSession(value) {
|
|
424
453
|
return isObject(value)
|
|
425
|
-
&& hasRequired(value, { sessionRef: isString, journalMaxSeq: isNumber, agentAlive: isBoolean })
|
|
454
|
+
&& hasRequired(value, { sessionRef: isString, journalMaxSeq: isNumber, agentAlive: isBoolean })
|
|
455
|
+
&& (value.pendingRequestIds === undefined || isStringArray(value.pendingRequestIds));
|
|
426
456
|
}
|
|
427
457
|
/**
|
|
428
458
|
* Accepts only the two outcomes this client can produce. `startedNewTurn` is refused on purpose: it
|
|
429
459
|
* is the extension's third outcome, and receiving one means a peer invented a disposition for a
|
|
430
|
-
* message the server has already put on its timeline — see `
|
|
460
|
+
* message the server has already put on its timeline — see `acp/steering.ts`'s `mapSteerOutcome`.
|
|
431
461
|
*/
|
|
432
462
|
function isSteerOutcome(value) { return value === 'injected' || value === 'promptRequired'; }
|
|
433
463
|
/** Accepts `true` and nothing else — the whole point of `authenticated` having no `false`. */
|
|
@@ -729,8 +759,9 @@ export function outboundFailureReply(frame, reason) {
|
|
|
729
759
|
case 'agent_authenticated': return { t: 'agent_authenticated', id, ok: false, error };
|
|
730
760
|
case 'agent_installed': return { t: 'agent_installed', id, ok: false, error };
|
|
731
761
|
case 'agent_steered': return { t: 'agent_steered', id, ok: false, error };
|
|
762
|
+
case 'agent_account_read_result': return { t: 'agent_account_read_result', id, ok: false, error };
|
|
732
763
|
case 'agent_config_option_set': return { t: 'agent_config_option_set', id, ok: false, error };
|
|
733
|
-
case '
|
|
764
|
+
case 'agent_switch_idle_result': return { t: 'agent_switch_idle_result', id, ok: false, error };
|
|
734
765
|
case 'agent_provider_task_stopped': return { t: 'agent_provider_task_stopped', id, state: 'unknown', error };
|
|
735
766
|
case 'agent_backfill_done':
|
|
736
767
|
case 'agent_backfill_events': return { t: 'agent_backfill_done', id, ok: false, count: 0, error };
|
|
@@ -746,7 +777,7 @@ export function decodeAgentFrame(text) {
|
|
|
746
777
|
if ((frame.state === 'completed' || frame.state === 'failed' || frame.state === 'stopped') && frame.live)
|
|
747
778
|
throw new TypeError('Invalid provider task terminal live state');
|
|
748
779
|
if (frame.status !== undefined &&
|
|
749
|
-
(frame.status !== frame.state || !['completed', 'failed', 'stopped'].
|
|
780
|
+
(frame.status !== frame.state || !['completed', 'failed', 'stopped'].some(status => status === frame.status)))
|
|
750
781
|
throw new TypeError('Invalid provider task terminal status');
|
|
751
782
|
if (frame.summary !== undefined && frame.status === undefined)
|
|
752
783
|
throw new TypeError('Provider task summary requires terminal status');
|
|
@@ -26,6 +26,18 @@ export const isNullableAuthMethodInfoArray = (value) => value === null || (Array
|
|
|
26
26
|
export function isMissingPrerequisite(value) {
|
|
27
27
|
return isObject(value) && hasRequired(value, { label: isString }) && hasOptional(value, { hint: isString });
|
|
28
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
|
+
}
|
|
29
41
|
export function isBridgeInfo(value) {
|
|
30
42
|
return isObject(value)
|
|
31
43
|
&& hasRequired(value, { type: isString })
|
|
@@ -66,6 +78,11 @@ export function isBridgeInfo(value) {
|
|
|
66
78
|
// A peer that sent one would be publishing "we looked and it cannot steer" through a key
|
|
67
79
|
// whose absence already says exactly that, so refusing it keeps one spelling per fact.
|
|
68
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,
|
|
69
86
|
});
|
|
70
87
|
}
|
|
71
88
|
export function isBridgeInfoArray(value) {
|
|
@@ -103,6 +120,19 @@ export function bridgesForWire(bridges, frame) {
|
|
|
103
120
|
}
|
|
104
121
|
return kept;
|
|
105
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
|
+
}
|
|
106
136
|
function partitionWireSafeBridges(bridges) {
|
|
107
137
|
let parsed;
|
|
108
138
|
try {
|
|
@@ -6,6 +6,7 @@ const DISCRIMINATORS = new Set([
|
|
|
6
6
|
'desktop_capability', 'desktop_open', 'desktop_opened', 'desktop_data', 'desktop_credit', 'desktop_close',
|
|
7
7
|
'computer_hello', 'computer_ready', 'heartbeat', 'heartbeat_ack',
|
|
8
8
|
'exec_request', 'exec_output', 'exec_exit', 'exec_cancel', 'tool_request', 'tool_result',
|
|
9
|
+
'exec_started', 'exec_ack', 'exec_attach', 'exec_status',
|
|
9
10
|
'file_read_request', 'file_read_result', 'file_write_request', 'file_write_result',
|
|
10
11
|
'file_ls_request', 'file_ls_result', 'file_mkdir_request', 'file_mkdir_result',
|
|
11
12
|
]);
|
|
@@ -16,6 +17,9 @@ const fileErrorCodes = new Set([
|
|
|
16
17
|
]);
|
|
17
18
|
const isNonnegativeInteger = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
18
19
|
const isPositiveInteger = (value) => Number.isSafeInteger(value) && value >= 1;
|
|
20
|
+
/** A record offset, or -1 for "none" (exec_status / exec_ack). */
|
|
21
|
+
const isOffsetOrNone = (value) => Number.isSafeInteger(value) && value >= -1;
|
|
22
|
+
const EXEC_STATES = new Set(['running', 'exited', 'unknown']);
|
|
19
23
|
const isSha256 = (value) => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
|
|
20
24
|
const onlyKeys = (value, keys) => Object.keys(value).every(key => keys.includes(key));
|
|
21
25
|
function isFileError(value) {
|
|
@@ -66,12 +70,19 @@ function isComputerFrame(value) {
|
|
|
66
70
|
case 'exec_request': return hasRequired(value, {
|
|
67
71
|
id: isString, command: isString, shell: isString, cwd: isString, env: isStringRecord,
|
|
68
72
|
pathPrepend: isStringArray, streaming: isBoolean,
|
|
69
|
-
}) && hasOptional(value, { timeoutSeconds: isNullableNumber });
|
|
70
|
-
case '
|
|
73
|
+
}) && hasOptional(value, { timeoutSeconds: isNullableNumber, label: isString, jobId: isString });
|
|
74
|
+
case 'exec_started': return hasRequired(value, { id: isString, durable: isBoolean, startedAtMs: isNumber })
|
|
75
|
+
&& hasOptional(value, { pid: isNonnegativeInteger });
|
|
76
|
+
case 'exec_output': return hasRequired(value, { id: isString, stream: isString, chunk: isString })
|
|
77
|
+
&& hasOptional(value, { offset: isNonnegativeInteger, recvAtMs: isNumber });
|
|
78
|
+
case 'exec_ack': return hasRequired(value, { id: isString, throughOffset: isOffsetOrNone });
|
|
71
79
|
case 'exec_exit': return hasRequired(value, {
|
|
72
80
|
id: isString, exitCode: isNullableNumber, stdout: isString, stderr: isString,
|
|
73
81
|
timedOut: isBoolean, truncated: isBoolean,
|
|
74
|
-
});
|
|
82
|
+
}) && hasOptional(value, { signal: isString, cancelled: isBoolean, gaps: isNonnegativeInteger });
|
|
83
|
+
case 'exec_attach': return hasRequired(value, { id: isString, fromOffset: isNonnegativeInteger });
|
|
84
|
+
case 'exec_status': return hasRequired(value, { id: isString, state: isString, headOffset: isOffsetOrNone, committedOffset: isOffsetOrNone })
|
|
85
|
+
&& EXEC_STATES.has(value.state);
|
|
75
86
|
case 'tool_request': return hasRequired(value, { id: isString, method: isString, params: isObject })
|
|
76
87
|
&& value.id.length > 0 && value.id.length <= 128
|
|
77
88
|
&& value.method.length > 0 && value.method.length <= 64
|
|
@@ -82,7 +93,7 @@ function isComputerFrame(value) {
|
|
|
82
93
|
&& (value.ok ? value.error === null : typeof value.error === 'string' && value.error.length > 0)
|
|
83
94
|
&& Buffer.byteLength(JSON.stringify(value)) <= TOOL_RESULT_BYTES
|
|
84
95
|
&& onlyKeys(value, ['t', 'id', 'ok', 'result', 'error']);
|
|
85
|
-
case 'exec_cancel': return hasRequired(value, { id: isString });
|
|
96
|
+
case 'exec_cancel': return hasRequired(value, { id: isString }) && hasOptional(value, { signal: isString });
|
|
86
97
|
case 'file_read_request': return hasRequired(value, {
|
|
87
98
|
id: isString, root: isString, path: isString, startLine: isPositiveInteger, maxLines: isPositiveInteger,
|
|
88
99
|
}) && value.maxLines <= 2000 && onlyKeys(value, ['t', 'id', 'root', 'path', 'startLine', 'maxLines']);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeDesktopManagementFrame, parseDesktopJson, DesktopProtocolError } from '../desktopControl/protocol.js';
|
|
1
2
|
import { decodeChannelFrame, hasOptional, hasRequired, isBoolean, isBridgeInfoArray, isNullableString, isNumber, isObject, isString, } from './common.js';
|
|
2
3
|
export const MANAGEMENT_WIRE_MIN_VERSION = 1;
|
|
3
4
|
/** Adds the `DRAINING` value to ClientUpdatePhase. */
|
|
@@ -8,11 +9,21 @@ export const MANAGEMENT_WIRE_METRICS_VERSION = 4;
|
|
|
8
9
|
export const MANAGEMENT_WIRE_PRESSURE_VERSION = 5;
|
|
9
10
|
/** Replaces normalized CPU load with raw 1/5/15-minute load averages and CPU capacity. */
|
|
10
11
|
export const MANAGEMENT_WIRE_LOAD_AVERAGES_VERSION = 6;
|
|
11
|
-
|
|
12
|
+
/** Adds per-GPU utilization and GPU memory to heartbeat metrics. */
|
|
13
|
+
export const MANAGEMENT_WIRE_GPU_VERSION = 7;
|
|
14
|
+
/** Adds `keepers` to heartbeats and the `keeper_control` / `keeper_control_result` pair (spec §8.1, Task 26). */
|
|
15
|
+
export const MANAGEMENT_WIRE_KEEPERS_VERSION = 8;
|
|
16
|
+
export const MANAGEMENT_WIRE_DESKTOP_VERSION = 9;
|
|
17
|
+
export const MANAGEMENT_WIRE_VERSION = MANAGEMENT_WIRE_DESKTOP_VERSION;
|
|
18
|
+
/** Bounds of one heartbeat's `keepers` (spec §8.1). */
|
|
19
|
+
export const KEEPER_STATUS_MAX_ITEMS = 32;
|
|
20
|
+
export const KEEPER_LABEL_MAX_CHARS = 64;
|
|
21
|
+
export const KEEPER_STATUS_MAX_ENTRIES = 16;
|
|
22
|
+
export const KEEPER_AFFECTED_MAX_ITEMS = 256;
|
|
12
23
|
const DISCRIMINATORS = new Set([
|
|
13
24
|
'management_hello', 'management_ready', 'heartbeat', 'heartbeat_ack',
|
|
14
25
|
'channel_ticket_request', 'channel_ticket', 'client_update', 'client_update_status',
|
|
15
|
-
'external_agents_scan', 'external_agents_scan_result',
|
|
26
|
+
'external_agents_scan', 'external_agents_scan_result', 'keeper_control', 'keeper_control_result',
|
|
16
27
|
]);
|
|
17
28
|
const CHANNEL_KINDS = new Set(['MANAGEMENT', 'COMPUTER', 'AGENT']);
|
|
18
29
|
const CHANNEL_STATES = new Set(['DISCONNECTED', 'CONNECTING', 'CONNECTED', 'RECONNECTING']);
|
|
@@ -63,6 +74,15 @@ function isStoragePoolMetrics(value) {
|
|
|
63
74
|
availableBytes: isNumber,
|
|
64
75
|
});
|
|
65
76
|
}
|
|
77
|
+
function isGpuMetrics(value) {
|
|
78
|
+
return isObject(value) && hasRequired(value, { id: isString, name: isString })
|
|
79
|
+
&& hasOptional(value, {
|
|
80
|
+
utilization: isNullable(isNumber),
|
|
81
|
+
memoryTotalBytes: isNullable(isNumber),
|
|
82
|
+
memoryUsedBytes: isNullable(isNumber),
|
|
83
|
+
coreCount: isNullable(isNumber),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
66
86
|
function isWorkstationMetrics(value) {
|
|
67
87
|
return isObject(value) && hasRequired(value, {
|
|
68
88
|
cpuUtilization: isNullable(isNumber),
|
|
@@ -76,8 +96,31 @@ function isWorkstationMetrics(value) {
|
|
|
76
96
|
cpuLoad15: isNullable(isNumber),
|
|
77
97
|
cpuLogicalCount: isNullable(isNumber),
|
|
78
98
|
memoryPressure: isNullable(isNumber),
|
|
99
|
+
gpus: isNullable((gpus) => Array.isArray(gpus) && gpus.every(isGpuMetrics)),
|
|
79
100
|
});
|
|
80
101
|
}
|
|
102
|
+
const KEEPER_KINDS = new Set(['terminal', 'acp']);
|
|
103
|
+
const KEEPER_STATES = new Set(['running', 'starting', 'stopped', 'unavailable']);
|
|
104
|
+
const KEEPER_REPLACEMENTS = new Set(['none', 'available', 'required', 'mandatory']);
|
|
105
|
+
const KEEPER_ITEM_TYPES = new Set(['pty', 'exec', 'session']);
|
|
106
|
+
const KEEPER_ACTIONS = new Set(['replace-when-idle', 'replace-now', 'cancel']);
|
|
107
|
+
const oneOf = (set) => (value) => typeof value === 'string' && set.has(value);
|
|
108
|
+
const isPending = (value) => value === null || value === 'when-idle';
|
|
109
|
+
function isKeeperItem(value) {
|
|
110
|
+
return isObject(value) && hasRequired(value, { type: oneOf(KEEPER_ITEM_TYPES), id: isString, label: isString, state: isString, sinceEpochMs: isNumber })
|
|
111
|
+
&& hasOptional(value, { jobId: isString });
|
|
112
|
+
}
|
|
113
|
+
function isKeeperStatus(value) {
|
|
114
|
+
return isObject(value) && hasRequired(value, {
|
|
115
|
+
kind: oneOf(KEEPER_KINDS), gen: isString, state: oneOf(KEEPER_STATES), protocolVersion: isNumber, buildVersion: isString,
|
|
116
|
+
replacement: oneOf(KEEPER_REPLACEMENTS),
|
|
117
|
+
metrics: (metrics) => isObject(metrics) && Object.values(metrics).every(isNumber),
|
|
118
|
+
items: (items) => Array.isArray(items) && items.length <= KEEPER_STATUS_MAX_ITEMS && items.every(isKeeperItem),
|
|
119
|
+
}) && hasOptional(value, { legacy: isBoolean, pending: isPending });
|
|
120
|
+
}
|
|
121
|
+
function isKeeperAffected(value) {
|
|
122
|
+
return isObject(value) && hasRequired(value, { type: oneOf(KEEPER_ITEM_TYPES), id: isString, label: isString, gen: isString });
|
|
123
|
+
}
|
|
81
124
|
function isManagementFrame(value) {
|
|
82
125
|
switch (value.t) {
|
|
83
126
|
case 'management_hello': return hasRequired(value, {
|
|
@@ -93,10 +136,14 @@ function isManagementFrame(value) {
|
|
|
93
136
|
channels: (channels) => isObject(channels) && Object.entries(channels).every(([kind, state]) => CHANNEL_KINDS.has(kind) && isChannelState(state)),
|
|
94
137
|
activeShellTasks: isNumber, activeAgentTasks: isNumber,
|
|
95
138
|
}) && hasOptional(value, {
|
|
139
|
+
playgroundCwd: isString,
|
|
96
140
|
protocols: (protocols) => isProtocolRangeMap(protocols, false),
|
|
97
141
|
os: isNullable(isOsInfo),
|
|
142
|
+
toolchain: (toolchain) => isObject(toolchain) && hasRequired(toolchain, { python: isNullableString, node: isNullableString }),
|
|
98
143
|
update: isNullable(isUpdateStatus),
|
|
99
144
|
metrics: isNullable(isWorkstationMetrics),
|
|
145
|
+
keepers: isNullable((keepers) => Array.isArray(keepers) && keepers.every(isKeeperStatus)),
|
|
146
|
+
capabilitiesRevision: value => Number.isSafeInteger(value) && value > 0,
|
|
100
147
|
});
|
|
101
148
|
case 'heartbeat_ack': return hasRequired(value, { seq: isNumber });
|
|
102
149
|
case 'channel_ticket_request': return hasRequired(value, { channel: isChannelKind });
|
|
@@ -105,12 +152,102 @@ function isManagementFrame(value) {
|
|
|
105
152
|
case 'client_update_status': return hasRequired(value, { status: isUpdateStatus });
|
|
106
153
|
case 'external_agents_scan': return hasRequired(value, { commandId: isString });
|
|
107
154
|
case 'external_agents_scan_result': return hasRequired(value, { commandId: isString, bridges: isBridgeInfoArray });
|
|
155
|
+
case 'keeper_control': return hasRequired(value, { commandId: isString, kind: oneOf(KEEPER_KINDS), action: oneOf(KEEPER_ACTIONS) });
|
|
156
|
+
case 'keeper_control_result': return hasRequired(value, {
|
|
157
|
+
commandId: isString, kind: oneOf(KEEPER_KINDS), action: oneOf(KEEPER_ACTIONS), ok: isBoolean,
|
|
158
|
+
affected: (affected) => Array.isArray(affected) && affected.every(isKeeperAffected),
|
|
159
|
+
}) && hasOptional(value, { pending: isPending, error: isString, affectedIncomplete: (flag) => flag === true });
|
|
108
160
|
default: return false;
|
|
109
161
|
}
|
|
110
162
|
}
|
|
111
163
|
export function encodeManagementFrame(frame) {
|
|
112
|
-
|
|
164
|
+
const text = JSON.stringify(frame);
|
|
165
|
+
if (isDesktopManagementType(frame.t))
|
|
166
|
+
decodeDesktopManagementFrame(text);
|
|
167
|
+
return text;
|
|
113
168
|
}
|
|
114
|
-
export function decodeManagementFrame(
|
|
169
|
+
export function decodeManagementFrame(source) {
|
|
170
|
+
const text = typeof source === 'string' ? source : new TextDecoder('utf-8', { fatal: true }).decode(source);
|
|
171
|
+
// Desktop extensions get the strict lexical/shape guard before any lossy object parsing.
|
|
172
|
+
const parsed = JSON.parse(text);
|
|
173
|
+
if (isObject(parsed) && isDesktopManagementType(parsed.t))
|
|
174
|
+
return decodeDesktopManagementFrame(text);
|
|
175
|
+
if (isObject(parsed) && parsed.t === 'heartbeat' && Object.hasOwn(parsed, 'capabilitiesRevision'))
|
|
176
|
+
validateHeartbeatRevision(text);
|
|
115
177
|
return decodeChannelFrame(text, DISCRIMINATORS, isManagementFrame);
|
|
116
178
|
}
|
|
179
|
+
/** Select only the new root field. JSON.parse already validates syntax; legacy nesting, size,
|
|
180
|
+
* unknown fields and duplicate legacy keys keep their established decoder behavior. */
|
|
181
|
+
function validateHeartbeatRevision(text) {
|
|
182
|
+
let depth = 0, count = 0;
|
|
183
|
+
for (let offset = 0; offset < text.length; offset++) {
|
|
184
|
+
const char = text[offset];
|
|
185
|
+
if (char === '"') {
|
|
186
|
+
const start = offset++;
|
|
187
|
+
while (offset < text.length) {
|
|
188
|
+
if (text[offset] === '\\')
|
|
189
|
+
offset += 2;
|
|
190
|
+
else if (text[offset++] === '"')
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
const end = offset--;
|
|
194
|
+
if (depth !== 1 || JSON.parse(text.slice(start, end)) !== 'capabilitiesRevision')
|
|
195
|
+
continue;
|
|
196
|
+
let tokenStart = end;
|
|
197
|
+
while (/\s/.test(text[tokenStart] ?? ''))
|
|
198
|
+
tokenStart++;
|
|
199
|
+
if (text[tokenStart] !== ':')
|
|
200
|
+
continue; // This string was a legacy field's value.
|
|
201
|
+
let tokenEnd = ++tokenStart;
|
|
202
|
+
while (tokenEnd < text.length && text[tokenEnd] !== ',' && text[tokenEnd] !== '}')
|
|
203
|
+
tokenEnd++;
|
|
204
|
+
const token = text.slice(tokenStart, tokenEnd).trim();
|
|
205
|
+
if (++count !== 1 || !/^[1-9][0-9]*$/.test(token) || !Number.isSafeInteger(Number(token))) {
|
|
206
|
+
throw new DesktopProtocolError('Invalid capabilities revision');
|
|
207
|
+
}
|
|
208
|
+
parseDesktopJson(`{"capabilitiesRevision":${token}}`);
|
|
209
|
+
}
|
|
210
|
+
else if (char === '{' || char === '[')
|
|
211
|
+
depth++;
|
|
212
|
+
else if (char === '}' || char === ']')
|
|
213
|
+
depth--;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function isDesktopManagementType(type) {
|
|
217
|
+
return typeof type === 'string' && ['desktop_background', 'desktop_lobby', 'desktop_lobby_result', 'workstation_capabilities', 'workstation_capabilities_request', 'desktop_prepare',
|
|
218
|
+
'desktop_prepare_result', 'desktop_cancel', 'desktop_session_state'].includes(type);
|
|
219
|
+
}
|
|
220
|
+
export class ManagementFrameAdmission {
|
|
221
|
+
isCurrent;
|
|
222
|
+
ready;
|
|
223
|
+
disposed = false;
|
|
224
|
+
constructor(isCurrent = () => true) {
|
|
225
|
+
this.isCurrent = isCurrent;
|
|
226
|
+
}
|
|
227
|
+
admit(frame) {
|
|
228
|
+
if (this.disposed || !this.isCurrent())
|
|
229
|
+
return false;
|
|
230
|
+
if (frame.t === 'management_ready') {
|
|
231
|
+
if (this.ready || !Number.isInteger(frame.protocols.MANAGEMENT) || frame.protocols.MANAGEMENT < MANAGEMENT_WIRE_MIN_VERSION
|
|
232
|
+
|| frame.protocols.MANAGEMENT > MANAGEMENT_WIRE_VERSION || !frame.generation)
|
|
233
|
+
return false;
|
|
234
|
+
this.ready = frame;
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
if (frame.t === 'heartbeat' && frame.capabilitiesRevision !== undefined)
|
|
238
|
+
return false;
|
|
239
|
+
if (!isDesktopManagementType(frame.t))
|
|
240
|
+
return true;
|
|
241
|
+
if (!this.ready || this.ready.protocols.MANAGEMENT < MANAGEMENT_WIRE_DESKTOP_VERSION)
|
|
242
|
+
return false;
|
|
243
|
+
switch (frame.t) {
|
|
244
|
+
case 'workstation_capabilities_request': return frame.generation === this.ready.generation;
|
|
245
|
+
case 'desktop_lobby':
|
|
246
|
+
case 'desktop_background':
|
|
247
|
+
case 'desktop_prepare': return frame.deviceGeneration === this.ready.generation;
|
|
248
|
+
case 'desktop_cancel': return true; // Current socket binding; T10 additionally fences its session/control generation.
|
|
249
|
+
default: return false; // Device-authored reports/results/states cannot be echoed as server activity.
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
dispose() { this.disposed = true; }
|
|
253
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function isRuntimeDiagnostics(value) {
|
|
2
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
3
|
+
return false;
|
|
4
|
+
const input = value;
|
|
5
|
+
const bounded = (value, max) => typeof value === 'string' &&
|
|
6
|
+
value.trim().length > 0 && value.length <= max && !/[\x00-\x1f\x7f]/.test(value);
|
|
7
|
+
if (!bounded(input.generation, 256) || !Number.isSafeInteger(input.revision) ||
|
|
8
|
+
input.revision < 0 || typeof input.available !== 'boolean' ||
|
|
9
|
+
!input.launch || typeof input.launch !== 'object')
|
|
10
|
+
return false;
|
|
11
|
+
const launch = input.launch;
|
|
12
|
+
return ['command-override', 'registry', 'registry-fallback', 'direct'].includes(launch.source) &&
|
|
13
|
+
bounded(launch.executable, 4096) && bounded(launch.platform, 64) && bounded(launch.architecture, 64) &&
|
|
14
|
+
launch.transport === 'stdio' && (input.profile === undefined || (typeof input.profile === 'object' && input.profile !== null && !Array.isArray(input.profile)));
|
|
15
|
+
}
|