@platless/axom-cli 0.5.9 → 0.6.52
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 +25 -6
- package/dist/axom-patch/cli/README.md +129 -12
- package/dist/axom-patch/cli/account-limits/generate.py +15 -12
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
- package/dist/axom-patch/cli/claude-account-limits.json +9 -9
- package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
- package/dist/axom-patch/cli/codex-account-limits.json +11 -11
- package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
- 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 +55 -13
- package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
- 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/zcode-env-account.mjs +81 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
- 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 +19 -19
- package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
- package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
- package/dist/axom-patch/cli/zcode-env-account.json +58 -0
- package/dist/axom-patch/cli/zcode-models.json +9 -10
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
- package/dist/cli.js +11 -45
- package/dist/commands/auth.js +4 -1
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +126 -3
- package/dist/commands/workstation.js +101 -164
- package/dist/commands/workstationRegistrations.js +116 -0
- package/dist/credentialBroker.js +13 -2
- package/dist/credentials.js +16 -0
- package/dist/install/claude/commands.js +15 -15
- package/dist/keepers/terminal/keeper.mjs +1089 -673
- package/dist/keepers/terminal/manifest.json +4 -4
- package/dist/mcp.js +1 -1
- package/dist/native/darwin-x64/axom-quic +0 -0
- package/dist/workstation/acp/acpAgents.js +113 -102
- package/dist/workstation/acp/acpClient.js +26 -0
- package/dist/workstation/acp/builds/install/npm.js +99 -10
- package/dist/workstation/acp/initializeResponse.js +6 -1
- package/dist/workstation/acp/managedPatches.js +189 -25
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +12 -5
- package/dist/workstation/acp/patchAssets.js +40 -0
- package/dist/workstation/acp/permission.js +2 -2
- package/dist/workstation/acp/processor/checkpoint.js +26 -34
- package/dist/workstation/acp/processor/control.js +7 -80
- package/dist/workstation/acp/processor/jsonrpc.js +8 -0
- package/dist/workstation/acp/processor/runtime.js +5 -81
- package/dist/workstation/acp/processor/session.js +54 -82
- package/dist/workstation/acp/processor/state.js +7 -13
- package/dist/workstation/acp/processor/step.js +3 -20
- package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
- package/dist/workstation/acp/processor/transport/local.js +19 -3
- package/dist/workstation/acp/processor/updates.js +156 -105
- package/dist/workstation/acp/processorSession.js +20 -173
- package/dist/workstation/acp/registry/catalog.js +65 -39
- package/dist/workstation/acp/registry/launch.js +16 -1
- package/dist/workstation/acp/switching.js +0 -3
- package/dist/workstation/acp/test/fakeAgent.js +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +32 -3
- package/dist/workstation/acp/test/raw/sources.js +16 -130
- package/dist/workstation/acp/test/realAdapter.js +1 -47
- package/dist/workstation/acp/test/scriptAgent.js +11 -3
- package/dist/workstation/agentFlow.js +1 -1
- package/dist/workstation/agentSessions.js +114 -166
- package/dist/workstation/channels/agent.js +9 -1
- package/dist/workstation/channels/computer.js +27 -4
- package/dist/workstation/channels/management.js +130 -15
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +33 -9
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +204 -0
- package/dist/workstation/control/commands.js +125 -0
- package/dist/workstation/control/daemonIdentity.js +143 -0
- package/dist/workstation/control/deviceCommands.js +191 -0
- package/dist/workstation/control/deviceProtocol.js +68 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +110 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/rootFormat.js +16 -0
- package/dist/workstation/control/runtimeClient.js +152 -0
- package/dist/workstation/control/runtimeHost.js +235 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/control/updateTransition.js +91 -0
- package/dist/workstation/daemon.js +266 -264
- package/dist/workstation/daemonctl.js +226 -25
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/automationDevice.js +219 -0
- package/dist/workstation/desktopControl/automationJson.js +74 -0
- package/dist/workstation/desktopControl/automationLocalState.js +23 -0
- package/dist/workstation/desktopControl/automationProtocol.js +253 -0
- package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
- package/dist/workstation/desktopControl/automationSession.js +81 -0
- package/dist/workstation/desktopControl/automationTransport.js +138 -0
- package/dist/workstation/desktopControl/capabilities.js +93 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
- package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +602 -0
- package/dist/workstation/desktopControl/hostGrant.js +65 -0
- package/dist/workstation/desktopControl/lobby.js +131 -0
- package/dist/workstation/desktopControl/macProvider.js +156 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
- package/dist/workstation/desktopControl/protocol.js +311 -0
- package/dist/workstation/desktopControl/provider.js +1 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1774 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +98 -0
- package/dist/workstation/exec/policy.js +5 -0
- package/dist/workstation/files/client.js +10 -1
- package/dist/workstation/files/credentials.js +142 -0
- package/dist/workstation/files/host.js +9 -2
- package/dist/workstation/files/text.js +42 -8
- package/dist/workstation/journal.js +5 -2
- package/dist/workstation/keepers/acp/client.js +38 -2
- package/dist/workstation/keepers/kit/buildVersion.js +35 -11
- package/dist/workstation/keepers/kit/bundle.js +21 -17
- package/dist/workstation/keepers/kit/client.js +2 -2
- package/dist/workstation/keepers/kit/launcher.js +123 -7
- package/dist/workstation/keepers/kit/paths.js +2 -1
- package/dist/workstation/keepers/legacy.js +16 -2
- package/dist/workstation/keepers/policy.js +5 -34
- package/dist/workstation/keepers/terminal/client.js +115 -23
- package/dist/workstation/keepers/terminal/exec.js +6 -3
- package/dist/workstation/keepers/terminal/frames.js +73 -0
- package/dist/workstation/keepers/terminal/generations.js +5 -8
- package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
- package/dist/workstation/keepers/terminal/input.js +146 -0
- package/dist/workstation/keepers/terminal/keeper.js +22 -8
- package/dist/workstation/keepers/terminal/main.js +1 -1
- package/dist/workstation/keepers/terminal/metadata.js +138 -0
- package/dist/workstation/keepers/terminal/protocol.js +4 -28
- package/dist/workstation/keepers/terminal/pty.js +1 -330
- package/dist/workstation/keepers/terminal/screen.js +246 -0
- package/dist/workstation/keepers/terminal/sessions.js +526 -0
- package/dist/workstation/managedInstall.js +57 -0
- package/dist/workstation/oauth/deviceAuthorization.js +31 -17
- package/dist/workstation/protocol/agent.js +2 -15
- package/dist/workstation/protocol/management.js +113 -5
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +120 -0
- package/dist/workstation/rd/control.js +117 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/host.js +213 -0
- package/dist/workstation/rd/migrationReadiness.js +46 -0
- package/dist/workstation/rd/provider.js +256 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +57 -0
- package/dist/workstation/rd/update.js +168 -0
- package/dist/workstation/rd/upkeep.js +80 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/registrations/enrollment.js +250 -0
- package/dist/workstation/registrations/enrollmentPending.js +361 -0
- package/dist/workstation/registrations/migration.js +356 -0
- package/dist/workstation/registrations/model.js +34 -0
- package/dist/workstation/registrations/removal.js +222 -0
- package/dist/workstation/registrations/store.js +737 -0
- package/dist/workstation/runtime/desktopBinding.js +20 -0
- package/dist/workstation/runtime/deviceDesktop.js +193 -0
- package/dist/workstation/runtime/deviceRuntime.js +568 -0
- package/dist/workstation/runtime/deviceServices.js +117 -0
- package/dist/workstation/runtime/events.js +20 -0
- package/dist/workstation/runtime/ownership.js +100 -0
- package/dist/workstation/runtime/registrationRuntime.js +575 -0
- package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
- package/dist/workstation/runtime/removalActivity.js +184 -0
- package/dist/workstation/service/installer.js +197 -14
- package/dist/workstation/service/launchd.js +2 -0
- package/dist/workstation/service/systemd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +47 -92
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- package/dist/workstation/tools/client.js +8 -27
- package/dist/workstation/tools/info.js +6 -13
- package/dist/workstation/tools/terminalRouter.js +17 -172
- package/dist/workstation/tools/validation.js +55 -1
- package/dist/workstation/update/archiveEvidence.js +206 -0
- package/dist/workstation/update/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +34 -13
- package/dist/workstation/update/deviceCompatibility.js +119 -0
- package/dist/workstation/update/deviceCoordinator.js +369 -0
- package/dist/workstation/update/deviceHealth.js +104 -0
- package/dist/workstation/update/deviceHealthProtocol.js +26 -0
- package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
- package/dist/workstation/update/deviceRollback.js +78 -0
- package/dist/workstation/update/globalInstall.js +186 -0
- package/dist/workstation/update/legacyBridge.js +414 -0
- package/dist/workstation/update/legacyOwner.js +39 -0
- package/dist/workstation/update/npmInstaller.js +51 -25
- package/dist/workstation/update/privateDurability.js +61 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/recoveryPlan.js +124 -0
- package/dist/workstation/update/runUpdate.js +10 -3
- package/dist/workstation/update/taskRegistry.js +161 -3
- package/dist/workstation/update/watchdog.js +29806 -333
- package/dist/workstation/update/watchdogProcess.js +72 -0
- package/package.json +11 -7
- 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 -61
- 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/runtime-deps/qoder-sdk/package-lock.json +0 -1189
- package/dist/runtime-deps/qoder-sdk/package.json +0 -8
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/processor/sideChannel.js +0 -80
- package/dist/workstation/acp/qoderStartup.js +0 -45
- package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
- package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
- package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
- package/dist/workstation/qoderAdapter/history.js +0 -85
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -95
- 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 -48
- package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -85
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/** Bundled release certification is the trust root; writable receipts never authorize a runtime. */
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { lstat, mkdtemp, open, readFile, readdir, realpath, rm } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import semver from 'semver';
|
|
9
|
+
import { inside, assertSafeAgentId, assertSafePackageName } from './builds/model.js';
|
|
10
|
+
import { nodeVersionOf } from './builds/nodeGate.js';
|
|
11
|
+
import { expectedPatchedFiles, patchDigest, verifyPatchedFiles } from './managedPatches.js';
|
|
12
|
+
import { managedRuntimePolicies, packagePatches, patchAssetRoot, validatePackagePatch, validatePatchPath } from './patchAssets.js';
|
|
13
|
+
export const REQUIRED_RUNTIME_CHECKS = ['initialize', 'ordinary-input', 'permissions', 'images-settings', 'parallel-subagents', 'background-reconnect', 'next-append', 'next-button', 'stop', 'cached-resume', 'legacy-migration', 'explicit-load', 'replay', 'install-gates', 'platform'];
|
|
14
|
+
const hashPattern = /^[a-f0-9]{64}$/;
|
|
15
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
16
|
+
function requireThat(condition, message) {
|
|
17
|
+
if (!condition)
|
|
18
|
+
throw new Error(`Managed runtime release: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
function exactVersion(value) { return typeof value === 'string' && semver.valid(value) === value && semver.prerelease(value) === null; }
|
|
21
|
+
export function managedRuntimeFor(agent, policies = managedRuntimePolicies) {
|
|
22
|
+
const found = policies.filter(policy => policy.agent === agent);
|
|
23
|
+
requireThat(found.length <= 1, 'duplicate agent policy');
|
|
24
|
+
return found[0];
|
|
25
|
+
}
|
|
26
|
+
export function assertRuntimeRequest(policy, name, version) {
|
|
27
|
+
assertSafeAgentId(policy.agent);
|
|
28
|
+
assertSafePackageName(policy.package);
|
|
29
|
+
validatePatchPath(policy.release);
|
|
30
|
+
requireThat(exactVersion(policy.version) && policy.ignore_scripts === true, 'invalid exact version or script policy');
|
|
31
|
+
requireThat(Array.isArray(policy.required_patches) && policy.required_patches.length > 0 && policy.required_patches.every(id => typeof id === 'string' && id.length > 0) && new Set(policy.required_patches).size === policy.required_patches.length, 'invalid required patches');
|
|
32
|
+
requireThat(name === policy.package && version === policy.version, `only certified ${policy.package}@${policy.version} may be installed`);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reject aliases in every component; use nonblocking nofollow opens so a FIFO cannot hang
|
|
36
|
+
* verification. Root-owned system symlinks (`/var -> /private/var` on macOS, in front of every
|
|
37
|
+
* per-user tmpdir) are crossed, not refused: a root-owned link to a root-owned, non-group-writable
|
|
38
|
+
* directory is not plantable by the running user. Every user-owned symlink is still refused.
|
|
39
|
+
*/
|
|
40
|
+
async function ownedFile(filename) {
|
|
41
|
+
const full = path.resolve(filename);
|
|
42
|
+
let current = path.parse(full).root;
|
|
43
|
+
const systemSymlink = async (uid) => {
|
|
44
|
+
if (uid !== 0 || process.getuid?.() === 0)
|
|
45
|
+
return false;
|
|
46
|
+
try {
|
|
47
|
+
const target = await realpath(current);
|
|
48
|
+
const stat = await lstat(target);
|
|
49
|
+
return stat.isDirectory() && stat.uid === 0 && (stat.mode & 0o022) === 0;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
for (const part of full.slice(current.length).split(path.sep).filter(Boolean)) {
|
|
56
|
+
current = path.join(current, part);
|
|
57
|
+
const info = await lstat(current);
|
|
58
|
+
requireThat(!info.isSymbolicLink() || (await systemSymlink(info.uid)), 'symlink in runtime path');
|
|
59
|
+
}
|
|
60
|
+
const file = await open(full, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
61
|
+
if (!(await file.stat()).isFile()) {
|
|
62
|
+
await file.close();
|
|
63
|
+
throw new Error('Managed runtime requires a regular file');
|
|
64
|
+
}
|
|
65
|
+
return file;
|
|
66
|
+
}
|
|
67
|
+
async function readReleaseAsset(relative, assets) {
|
|
68
|
+
validatePatchPath(relative);
|
|
69
|
+
const base = assets.assetRoot ?? patchAssetRoot;
|
|
70
|
+
const handle = await ownedFile(inside(base instanceof URL ? fileURLToPath(base) : base, relative));
|
|
71
|
+
try {
|
|
72
|
+
requireThat((await handle.stat()).size <= 8 * 1024 * 1024, 'manifest too large');
|
|
73
|
+
return JSON.parse(await handle.readFile('utf8'));
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
await handle.close();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export async function loadRuntimeRelease(policy, assets = {}) {
|
|
80
|
+
assertRuntimeRequest(policy, policy.package, policy.version);
|
|
81
|
+
return validateRuntimeRelease(await readReleaseAsset(policy.release, assets), policy, assets);
|
|
82
|
+
}
|
|
83
|
+
function validateRuntimeRelease(r, policy, assets, retained = false) {
|
|
84
|
+
assertRuntimeRequest(policy, policy.package, policy.version);
|
|
85
|
+
requireThat(r?.schema === 1 && typeof r.id === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(r.id), 'invalid schema/id');
|
|
86
|
+
requireThat(r.package === policy.package && r.version === policy.version, 'policy/package/version mismatch');
|
|
87
|
+
requireThat(typeof r.integrity === 'string' && /^sha512-[A-Za-z0-9+/]{86}==$/.test(r.integrity), 'invalid archive integrity');
|
|
88
|
+
requireThat(r.bin && typeof r.bin.name === 'string' && r.bin.name.length > 0, 'invalid bin');
|
|
89
|
+
validatePatchPath(r.bin.path);
|
|
90
|
+
requireThat(r.bin.args === undefined || (Array.isArray(r.bin.args) && r.bin.args.every(arg => typeof arg === 'string' && !arg.includes('\0'))), 'invalid static argv');
|
|
91
|
+
requireThat(r.agentInfo && typeof r.agentInfo.name === 'string' && r.agentInfo.name.length > 0 && exactVersion(r.agentInfo.version), 'invalid agent identity');
|
|
92
|
+
requireThat(r.node && typeof r.node.range === 'string' && semver.validRange(r.node.range), 'invalid Node range');
|
|
93
|
+
requireThat(Array.isArray(r.historyVersions) && r.historyVersions.includes(r.version) && r.historyVersions.every(exactVersion) && new Set(r.historyVersions).size === r.historyVersions.length, 'invalid history versions');
|
|
94
|
+
requireThat(same(r.patches, policy.required_patches), 'ordered required patches mismatch');
|
|
95
|
+
requireThat(r.files && typeof r.files === 'object' && !Array.isArray(r.files) && Object.keys(r.files).length > 0, 'missing trusted files');
|
|
96
|
+
for (const [file, hash] of Object.entries(r.files)) {
|
|
97
|
+
validatePatchPath(file);
|
|
98
|
+
requireThat(typeof hash === 'string' && hashPattern.test(hash), 'invalid trusted file digest');
|
|
99
|
+
}
|
|
100
|
+
requireThat(r.files['package.json'] && r.files[r.bin.path], 'manifest and executable must be trusted files');
|
|
101
|
+
requireThat(r.capabilities?.cachedReconnect === 'resume' && same(r.capabilities.steering, ['next']) && r.capabilities.subagents === true, 'invalid capabilities');
|
|
102
|
+
const c = r.certification;
|
|
103
|
+
requireThat(c && typeof c.suite === 'string' && c.suite.length > 0 && Array.isArray(c.checks) && Array.isArray(c.platforms), 'missing certification');
|
|
104
|
+
requireThat(c.checks.length === REQUIRED_RUNTIME_CHECKS.length && new Set(c.checks.map(v => v.name)).size === c.checks.length && REQUIRED_RUNTIME_CHECKS.every(name => c.checks.some(v => v.name === name)), 'incomplete or duplicate required checks');
|
|
105
|
+
requireThat(c.checks.every(v => ['PASS', 'NOT_PASS'].includes(v.status) && hashPattern.test(v.evidenceSha256)), 'invalid certification check status');
|
|
106
|
+
requireThat(c.platforms.every(v => typeof v.platform === 'string' && typeof v.arch === 'string' && ['PASS', 'NOT_PASS'].includes(v.status) && hashPattern.test(v.evidenceSha256)) && new Set(c.platforms.map(v => `${v.platform}/${v.arch}`)).size === c.platforms.length, 'invalid platform records');
|
|
107
|
+
requireThat(c.platforms.some(v => v.platform === process.platform && v.arch === process.arch), 'platform record missing');
|
|
108
|
+
requireThat(r.runtimeTools === undefined || Array.isArray(r.runtimeTools), 'invalid runtime tools');
|
|
109
|
+
const tools = r.runtimeTools ?? [];
|
|
110
|
+
requireThat(new Set(tools.map(v => `${v.package?.name ?? r.package}/${v.platform}/${v.arch}/${v.path}`)).size === tools.length, 'duplicate runtime tools');
|
|
111
|
+
for (const tool of tools) {
|
|
112
|
+
validatePatchPath(tool.path);
|
|
113
|
+
if (tool.package !== undefined) {
|
|
114
|
+
requireThat(tool.package && typeof tool.package.name === 'string' && typeof tool.package.version === 'string' && semver.valid(tool.package.version) === tool.package.version, 'invalid exact tool package identity');
|
|
115
|
+
assertSafePackageName(tool.package.name);
|
|
116
|
+
}
|
|
117
|
+
const primary = tool.package === undefined || tool.package.name === r.package;
|
|
118
|
+
requireThat(typeof tool.platform === 'string' && typeof tool.arch === 'string' && hashPattern.test(tool.sha256) && (!primary || r.files[tool.path] === tool.sha256) && c.platforms.some(v => v.platform === tool.platform && v.arch === tool.arch), 'tool must match a trusted digest and declared platform');
|
|
119
|
+
}
|
|
120
|
+
if (retained)
|
|
121
|
+
requireThat(typeof r.patchDigest === 'string' && hashPattern.test(r.patchDigest), 'retained release requires trusted patch digest');
|
|
122
|
+
else {
|
|
123
|
+
const digest = patchDigest(runtimeRecipes(policy, r, assets.recipes));
|
|
124
|
+
requireThat(r.patchDigest === undefined || r.patchDigest === digest, 'release patch digest disagrees with bundled recipes');
|
|
125
|
+
}
|
|
126
|
+
return r;
|
|
127
|
+
}
|
|
128
|
+
export function runtimeRecipes(policy, release, recipes = packagePatches) {
|
|
129
|
+
const selected = recipes.filter(recipe => recipe.package === policy.package && recipe.versions?.includes(policy.version));
|
|
130
|
+
requireThat(same(selected.map(v => v.id), policy.required_patches), 'missing or unordered exact patch recipe');
|
|
131
|
+
for (const recipe of selected) {
|
|
132
|
+
validatePackagePatch(recipe);
|
|
133
|
+
requireThat(recipe.bin === undefined || recipe.bin === release.bin.name, 'patch bin disagrees with release');
|
|
134
|
+
}
|
|
135
|
+
for (const [qualified, hash] of Object.entries(expectedPatchedFiles(selected))) {
|
|
136
|
+
requireThat(release.files[qualified.slice(policy.package.length + 1)] === hash, 'patch digest disagrees with trusted release files');
|
|
137
|
+
}
|
|
138
|
+
return selected;
|
|
139
|
+
}
|
|
140
|
+
/** Reverse the declared edits to derive trusted pristine hashes, without trusting package metadata. */
|
|
141
|
+
function pristineFiles(release, recipes) {
|
|
142
|
+
const files = { ...release.files };
|
|
143
|
+
for (const recipe of [...recipes].reverse()) {
|
|
144
|
+
for (const asset of recipe.assets ?? [])
|
|
145
|
+
delete files[asset.target];
|
|
146
|
+
for (const edit of [...recipe.edits].reverse()) {
|
|
147
|
+
requireThat(files[edit.file] === edit.after_sha256 && hashPattern.test(edit.before_sha256), 'inconsistent patch hash chain');
|
|
148
|
+
files[edit.file] = edit.before_sha256;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return files;
|
|
152
|
+
}
|
|
153
|
+
export async function verifyRuntimeTree(options) {
|
|
154
|
+
const r = await loadRuntimeRelease(options.policy, options);
|
|
155
|
+
return verifyReleaseTree(options, r);
|
|
156
|
+
}
|
|
157
|
+
async function verifyReleaseTree(options, r) {
|
|
158
|
+
const pkg = inside(path.join(options.treeRoot, 'node_modules'), r.package);
|
|
159
|
+
const files = options.pristine ? pristineFiles(r, runtimeRecipes(options.policy, r, options.recipes)) : r.files;
|
|
160
|
+
await verifyPatchedFiles(pkg, files);
|
|
161
|
+
// Extra executable code or links must not survive a cache adoption, even with matching package.json.
|
|
162
|
+
const actual = [];
|
|
163
|
+
const walk = async (dir, prefix = '') => {
|
|
164
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
165
|
+
const rel = prefix + entry.name;
|
|
166
|
+
if (entry.isDirectory())
|
|
167
|
+
await walk(path.join(dir, entry.name), rel + '/');
|
|
168
|
+
else {
|
|
169
|
+
requireThat(entry.isFile(), 'non-regular runtime asset');
|
|
170
|
+
actual.push(rel);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
await walk(pkg);
|
|
175
|
+
requireThat(same(actual.sort(), Object.keys(files).sort()), 'unexpected or missing runtime files');
|
|
176
|
+
const metadata = JSON.parse(await readFile(path.join(pkg, 'package.json'), 'utf8'));
|
|
177
|
+
requireThat(metadata.name === r.package && metadata.version === r.version && metadata.bin?.[r.bin.name] === r.bin.path, 'package identity/bin mismatch');
|
|
178
|
+
for (const tool of r.runtimeTools ?? []) {
|
|
179
|
+
if (tool.platform !== process.platform || tool.arch !== process.arch)
|
|
180
|
+
continue;
|
|
181
|
+
const toolPackage = tool.package ?? { name: r.package, version: r.version };
|
|
182
|
+
const toolDir = inside(path.join(options.treeRoot, 'node_modules'), toolPackage.name);
|
|
183
|
+
const manifest = await ownedFile(path.join(toolDir, 'package.json'));
|
|
184
|
+
try {
|
|
185
|
+
const identity = JSON.parse(await manifest.readFile('utf8'));
|
|
186
|
+
requireThat(identity.name === toolPackage.name && identity.version === toolPackage.version, 'runtime tool package identity mismatch');
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await manifest.close();
|
|
190
|
+
}
|
|
191
|
+
// External tools are authenticated by this explicit record, not by a claim about their whole package.
|
|
192
|
+
await verifyPatchedFiles(toolDir, { [tool.path]: tool.sha256 });
|
|
193
|
+
const handle = await ownedFile(inside(toolDir, tool.path));
|
|
194
|
+
try {
|
|
195
|
+
if (options.prepareTools)
|
|
196
|
+
await handle.chmod(0o755);
|
|
197
|
+
requireThat(process.platform === 'win32' || ((await handle.stat()).mode & 0o111) !== 0, `runtime tool not executable: ${tool.path}`);
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
await handle.close();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return r;
|
|
204
|
+
}
|
|
205
|
+
export async function verifyManagedRuntime(options) {
|
|
206
|
+
const release = await loadRuntimeRelease(options.policy, options);
|
|
207
|
+
await verifyReleaseBuild(options, release, patchDigest(runtimeRecipes(options.policy, release, options.recipes)));
|
|
208
|
+
}
|
|
209
|
+
/** Explicit recorded bindings/rollback only. Every historical authority is bundled and listed, never a writable receipt. */
|
|
210
|
+
export async function verifyRecordedManagedRuntime(options) {
|
|
211
|
+
const active = await loadRuntimeRelease(options.policy, options);
|
|
212
|
+
if (options.build.runtimeReleaseId === active.id) {
|
|
213
|
+
await verifyReleaseBuild(options, active, patchDigest(runtimeRecipes(options.policy, active, options.recipes)));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const paths = options.policy.retained_releases ?? [];
|
|
217
|
+
requireThat(Array.isArray(paths) && new Set(paths).size === paths.length && !paths.includes(options.policy.release), 'duplicate or active retained release path');
|
|
218
|
+
const ids = new Set([active.id]);
|
|
219
|
+
let match;
|
|
220
|
+
for (const relative of paths) {
|
|
221
|
+
const release = await readReleaseAsset(relative, options);
|
|
222
|
+
requireThat(release?.schema === 1 && typeof release.id === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(release.id) && !ids.has(release.id), 'invalid, duplicate or active retained release identity');
|
|
223
|
+
ids.add(release.id);
|
|
224
|
+
if (release.id === options.build.runtimeReleaseId)
|
|
225
|
+
match = { release, path: relative };
|
|
226
|
+
}
|
|
227
|
+
requireThat(match, 'recorded release is not explicitly retained');
|
|
228
|
+
const policy = { agent: options.policy.agent, package: match.release.package, version: match.release.version, release: match.path, required_patches: match.release.patches, ignore_scripts: true };
|
|
229
|
+
const release = validateRuntimeRelease(match.release, policy, options, true);
|
|
230
|
+
requireThat(release.package !== options.policy.package || semver.lte(release.version, options.policy.version), 'retained release is a future version');
|
|
231
|
+
await verifyReleaseBuild({ ...options, policy }, release, release.patchDigest);
|
|
232
|
+
}
|
|
233
|
+
async function verifyReleaseBuild(options, release, digest) {
|
|
234
|
+
const { policy, build } = options;
|
|
235
|
+
requireThat(release.certification.checks.length === REQUIRED_RUNTIME_CHECKS.length && release.certification.checks.every(v => v.status === 'PASS'), 'release certification is NOT_PASS');
|
|
236
|
+
requireThat(release.certification.platforms.some(v => v.platform === process.platform && v.arch === process.arch && v.status === 'PASS'), 'platform is NOT_PASS');
|
|
237
|
+
assertRuntimeRequest(policy, build.package ?? '', build.version);
|
|
238
|
+
await verifyReleaseTree({ ...options, treeRoot: inside(options.storeRoot, build.dir), pristine: false }, release);
|
|
239
|
+
requireThat(build.agentId === policy.agent && build.runtimeReleaseId === release.id, 'build release identity mismatch');
|
|
240
|
+
requireThat(build.kind === 'npm-patched' && build.patchDigest === digest && same(build.patches, release.patches), 'build patch identity mismatch');
|
|
241
|
+
requireThat(build.launch.entry === `node_modules/${release.package}/${release.bin.path}` && build.launch.runtime === 'node', 'build executable/dispatcher mismatch');
|
|
242
|
+
requireThat(same(build.launch.args, release.bin.args ?? []), 'build static argv mismatch');
|
|
243
|
+
const nodeVersion = await nodeVersionOf(options.nodePath);
|
|
244
|
+
requireThat(semver.satisfies(nodeVersion, release.node.range, { includePrerelease: false }), `Node ${nodeVersion} outside ${release.node.range}`);
|
|
245
|
+
if (options.initialize)
|
|
246
|
+
await initializeProbe(build, options.storeRoot, options.nodePath, release);
|
|
247
|
+
}
|
|
248
|
+
/** One initialize only, no credentials or user HOME, bounded streams/time, awaited process cleanup. */
|
|
249
|
+
async function initializeProbe(build, storeRoot, nodePath, release) {
|
|
250
|
+
const home = await mkdtemp(path.join(tmpdir(), 'runtime-probe-'));
|
|
251
|
+
try {
|
|
252
|
+
await new Promise((resolve, reject) => {
|
|
253
|
+
const dir = inside(storeRoot, build.dir);
|
|
254
|
+
const child = spawn(nodePath, [inside(dir, build.launch.entry), ...build.launch.args], {
|
|
255
|
+
cwd: home, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'],
|
|
256
|
+
env: { PATH: [path.dirname(nodePath), ...(build.launch.pathDirs ?? []).map(p => inside(dir, p)), process.env.PATH ?? ''].join(path.delimiter), HOME: home, USERPROFILE: home, APPDATA: home, LOCALAPPDATA: home, XDG_CONFIG_HOME: home, XDG_CACHE_HOME: home, TMPDIR: home, TEMP: home, TMP: home, ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) },
|
|
257
|
+
});
|
|
258
|
+
let output = '';
|
|
259
|
+
let bytes = 0;
|
|
260
|
+
let stderrBytes = 0;
|
|
261
|
+
let response = false;
|
|
262
|
+
let failure;
|
|
263
|
+
const fail = (message) => { failure ??= new Error(`Managed runtime initialize: ${message}`); child.kill('SIGKILL'); };
|
|
264
|
+
const timeout = setTimeout(() => fail('timed out'), 30_000);
|
|
265
|
+
child.on('error', error => { failure ??= error; });
|
|
266
|
+
child.stdin.on('error', () => { });
|
|
267
|
+
child.stderr.on('data', (data) => { stderrBytes += data.length; if (stderrBytes > 1024 * 1024)
|
|
268
|
+
fail('stderr limit exceeded'); });
|
|
269
|
+
child.stdout.on('data', (data) => {
|
|
270
|
+
bytes += data.length;
|
|
271
|
+
if (bytes > 1024 * 1024) {
|
|
272
|
+
fail('stdout limit exceeded');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
output += data.toString('utf8');
|
|
276
|
+
let end;
|
|
277
|
+
while ((end = output.indexOf('\n')) >= 0) {
|
|
278
|
+
const line = output.slice(0, end);
|
|
279
|
+
output = output.slice(end + 1);
|
|
280
|
+
try {
|
|
281
|
+
requireThat(!response, 'extra initialize response');
|
|
282
|
+
const msg = JSON.parse(line);
|
|
283
|
+
const result = msg.result;
|
|
284
|
+
requireThat(msg.jsonrpc === '2.0' && msg.id === 'axom-runtime-initialize' && !Object.hasOwn(msg, 'error') && !Object.hasOwn(msg, 'method') && result?.protocolVersion === 1, 'invalid correlated initialize result');
|
|
285
|
+
requireThat(result.agentInfo?.name === release.agentInfo.name && result.agentInfo?.version === release.agentInfo.version, 'initialize agent identity mismatch');
|
|
286
|
+
requireThat(result._meta?.axom?.releaseId === release.id && result._meta?.axom?.cachedReconnect === 'resume', 'initialize release/resume mismatch');
|
|
287
|
+
requireThat(result.agentCapabilities?.loadSession === true && result.agentCapabilities?.sessionCapabilities?.resume && typeof result.agentCapabilities.sessionCapabilities.resume === 'object' && !Array.isArray(result.agentCapabilities.sessionCapabilities.resume), 'missing resume capability');
|
|
288
|
+
requireThat(result._meta?.steering?.supported === true && same(result._meta.steering.timings, ['next']), 'missing next capability');
|
|
289
|
+
response = true;
|
|
290
|
+
child.stdin.end();
|
|
291
|
+
// Await graceful EOF cleanup within the same overall deadline. Native agents may
|
|
292
|
+
// flush telemetry before exiting; a second, shorter deadline can kill a healthy one.
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
fail(error.message);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
child.once('close', (code) => {
|
|
300
|
+
clearTimeout(timeout);
|
|
301
|
+
if (!failure && (!response || output.trim() || code !== 0))
|
|
302
|
+
failure = new Error('Managed runtime initialize: incomplete response or startup failure');
|
|
303
|
+
if (failure)
|
|
304
|
+
reject(failure);
|
|
305
|
+
else
|
|
306
|
+
resolve();
|
|
307
|
+
});
|
|
308
|
+
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id: 'axom-runtime-initialize', method: 'initialize', params: { protocolVersion: 1, clientInfo: { name: 'axom-runtime-check', version: '1.0.0' }, clientCapabilities: { fs: { readTextFile: true, writeTextFile: true }, terminal: true } } }) + '\n');
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
await rm(home, { recursive: true, force: true });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* ## Residual (documented, not fixed)
|
|
23
23
|
*
|
|
24
24
|
* While the producing window is full — a server parked on an approval with ≥ 256 root frames held, a connection that is down, an
|
|
25
|
-
* applier that never acknowledges — the record loop folds nothing:
|
|
25
|
+
* applier that never acknowledges — the record loop folds nothing: an already-admitted steer or config RPC waits for the processor's reply and can
|
|
26
26
|
* time out on the server; an `agent_interrupt` note is folded (and the `session/cancel` written) only once the server unparks.
|
|
27
27
|
*/
|
|
28
28
|
/** Unacknowledged events per session (spec §5.6, `W_EVT`). `ACK_EVERY_EVENTS(32) < W_EVT ≤ HOLD_OFF_CAPACITY(512)`. */
|
|
@@ -278,16 +278,22 @@ export class SessionOutbox {
|
|
|
278
278
|
}
|
|
279
279
|
/**
|
|
280
280
|
* A new connection: everything recorded so far — sent on an earlier connection or produced while none was bound — is held until
|
|
281
|
-
* the server reopens the ref and says how far it got ({@link SENT_EARLIER})
|
|
282
|
-
*
|
|
281
|
+
* the server reopens the ref and says how far it got ({@link SENT_EARLIER}). Newly recorded frames wait behind those older
|
|
282
|
+
* entries so a higher seq cannot advance the server's watermark past retained output.
|
|
283
283
|
*/
|
|
284
284
|
connectionChanged() {
|
|
285
285
|
for (const item of this.items)
|
|
286
286
|
item.sentOn = SENT_EARLIER;
|
|
287
287
|
}
|
|
288
|
-
/** The first entry never sent
|
|
288
|
+
/** The first entry never sent, without overtaking an older entry held for reopen. */
|
|
289
289
|
nextUnsent() {
|
|
290
|
-
|
|
290
|
+
for (const item of this.items) {
|
|
291
|
+
if (item.sentOn === SENT_EARLIER)
|
|
292
|
+
return undefined;
|
|
293
|
+
if (item.sentOn === null)
|
|
294
|
+
return item;
|
|
295
|
+
}
|
|
296
|
+
return undefined;
|
|
291
297
|
}
|
|
292
298
|
/** Resolves when this life's unacknowledged backlog is below the window (immediately once {@link stopWaiting} ran). */
|
|
293
299
|
room() {
|
|
@@ -295,6 +301,7 @@ export class SessionOutbox {
|
|
|
295
301
|
return undefined;
|
|
296
302
|
return new Promise((resolve) => { this.roomWaiters.add(resolve); });
|
|
297
303
|
}
|
|
304
|
+
/** Whether another raw record can be folded now, without registering a waiter. */
|
|
298
305
|
hasRoom() {
|
|
299
306
|
if (this.stopped)
|
|
300
307
|
return true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import semver from 'semver';
|
|
4
|
+
import { assertSafePackageName } from './builds/model.js';
|
|
4
5
|
// Production reads only assets shipped in this CLI. The .ts path is for source-based tests.
|
|
5
6
|
export const patchAssetRoot = new URL(import.meta.url.endsWith('.ts') ? '../../../../axom-patch/cli/' : '../../axom-patch/cli/', import.meta.url);
|
|
6
7
|
const runtime = JSON.parse(readFileSync(new URL('runtime.json', patchAssetRoot), 'utf8'));
|
|
@@ -26,11 +27,27 @@ export function validateOverride(entry) {
|
|
|
26
27
|
throw new Error(`Axom patch ${entry.id}: pinned ${entry.distribution.version} must not sort above ${until}`);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
/** Portable relative paths only: no platform-specific interpretation or normalization. */
|
|
31
|
+
export function validatePatchPath(value) {
|
|
32
|
+
if (typeof value !== 'string' || !value || /[\\:\0]/.test(value) || value.split('/').some(part => !part || part === '.' || part === '..')) {
|
|
33
|
+
throw new Error('Axom patch path escapes its root or is not a portable relative path');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
29
36
|
export function validatePackagePatch(recipe) {
|
|
30
37
|
validateCondition(recipe.id, recipe.versions, recipe.until_version, 'versions', 'until_version');
|
|
31
38
|
if (recipe.versions === undefined && recipe.until_version === undefined) {
|
|
32
39
|
throw new Error(`Axom patch ${recipe.id}: needs versions or until_version`);
|
|
33
40
|
}
|
|
41
|
+
for (const edit of recipe.edits)
|
|
42
|
+
validatePatchPath(edit.file);
|
|
43
|
+
if (recipe.assets !== undefined && !Array.isArray(recipe.assets))
|
|
44
|
+
throw new Error('Axom patch assets must be an array');
|
|
45
|
+
for (const asset of recipe.assets ?? []) {
|
|
46
|
+
validatePatchPath(asset.source);
|
|
47
|
+
validatePatchPath(asset.target);
|
|
48
|
+
if (!/^[a-f0-9]{64}$/.test(asset.sha256))
|
|
49
|
+
throw new Error('Axom patch asset sha256 must be a lowercase SHA-256 digest');
|
|
50
|
+
}
|
|
34
51
|
}
|
|
35
52
|
function inRange(version, when, until) {
|
|
36
53
|
if (when)
|
|
@@ -78,3 +95,26 @@ export function nativeExtensionFor(agent, version) {
|
|
|
78
95
|
export function nativeAccountLimitsVersionFor(agent, version) {
|
|
79
96
|
return runtime.native_extensions?.find(entry => entry.agent === agent && entry.versions.includes(version))?.accountLimitsVersion ?? 2;
|
|
80
97
|
}
|
|
98
|
+
/** One entry per package; the only policy is skipping npm lifecycle scripts a package would run in the user's HOME. */
|
|
99
|
+
export function validateInstallPolicies(policies) {
|
|
100
|
+
if (!Array.isArray(policies))
|
|
101
|
+
throw new Error('Axom install_policies must be an array');
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
for (const policy of policies) {
|
|
104
|
+
if (typeof policy?.package !== 'string')
|
|
105
|
+
throw new Error('Axom install policy needs a package name');
|
|
106
|
+
assertSafePackageName(policy.package);
|
|
107
|
+
if (seen.has(policy.package))
|
|
108
|
+
throw new Error(`Axom install policy for ${policy.package} is duplicated`);
|
|
109
|
+
if (policy.ignore_scripts !== true)
|
|
110
|
+
throw new Error(`Axom install policy for ${policy.package} must set ignore_scripts: true`);
|
|
111
|
+
seen.add(policy.package);
|
|
112
|
+
}
|
|
113
|
+
return policies;
|
|
114
|
+
}
|
|
115
|
+
export const installPolicies = validateInstallPolicies(runtime.install_policies ?? []);
|
|
116
|
+
export function installPolicyFor(name, policies = installPolicies) {
|
|
117
|
+
return policies.find((policy) => policy.package === name);
|
|
118
|
+
}
|
|
119
|
+
/** Optional strict release policies; activation is an explicit bundled manifest change. */
|
|
120
|
+
export const managedRuntimePolicies = runtime.managed_runtimes ?? [];
|
|
@@ -174,7 +174,8 @@ export function buildPermissionRequestedEvent(input) {
|
|
|
174
174
|
* forced into a selection the user never made.
|
|
175
175
|
*/
|
|
176
176
|
export async function handleRequestPermission(input, sink) {
|
|
177
|
-
|
|
177
|
+
const optionIds = new Set(input.options.map((option) => option.optionId));
|
|
178
|
+
if (input.options.length === 0 || optionIds.size !== input.options.length) {
|
|
178
179
|
return { outcome: 'rejected' };
|
|
179
180
|
}
|
|
180
181
|
if (input.signal?.aborted) {
|
|
@@ -192,7 +193,6 @@ export async function handleRequestPermission(input, sink) {
|
|
|
192
193
|
console.error(`[acp] refusing to raise a permission card that fails validateWireEvent: ${rejection.reason}`);
|
|
193
194
|
return { outcome: 'rejected' };
|
|
194
195
|
}
|
|
195
|
-
const optionIds = new Set(input.options.map((option) => option.optionId));
|
|
196
196
|
return new Promise((resolve) => {
|
|
197
197
|
let settled = false;
|
|
198
198
|
const settle = (decision) => {
|
|
@@ -1,35 +1,17 @@
|
|
|
1
|
+
/** Checkpoint schema answers whether state is readable; processorVersion identifies output semantics. Readable older processor versions remain restorable; replay differences across versions do not count as divergence. Version 9 moves adapter-specific compatibility folds out and removes retired optional state. */
|
|
2
|
+
import { initialState, PROCESSOR_STATE_SCHEMA } from './state.js';
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* reader for the old one.
|
|
12
|
-
* - `processorVersion` answers "would **this** code have produced the same frames?". Bump it whenever
|
|
13
|
-
* `step` can emit different output for the same records. A readable checkpoint from another
|
|
14
|
-
* processor version is still restored — that is the whole point of cutting a head checkpoint at
|
|
15
|
-
* upgrade — but the caller must not count replay-vs-outbox differences as divergence then.
|
|
16
|
-
*
|
|
17
|
-
* Checkpoints are cut only on record boundaries (a synchronous `step` leaves no half-processed record)
|
|
18
|
-
* and carry no secrets (`state.ts`).
|
|
4
|
+
* Output history: 2 — turns, cards, seq and semantic-only request checks;
|
|
5
|
+
* 3 — session updates, binding, config, steering and validated events;
|
|
6
|
+
* 4 — spawn-bound commands and history dropped counts; prompt closes history;
|
|
7
|
+
* 5 — callback snapshots, runtime revisions and adapter compatibility folds;
|
|
8
|
+
* 6 — child status precedes process results; ended calls release data;
|
|
9
|
+
* 7 — install outcomes become step events;
|
|
10
|
+
* 8 — loaded-session levels fold live; output gaps become steps;
|
|
11
|
+
* 9 — adapter compatibility folds moved out and retired optional state removed;
|
|
12
|
+
* 10 — ended replay scopes cannot leak late level snapshots into the live journal.
|
|
19
13
|
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Bump when `step`'s output for the same records changes. 2: Task 17a (turns, cards, `seq`; prompt
|
|
23
|
-
* methods refused by `rpc.request`). 3: Task 17b (`session/update` mapped, the open session bound
|
|
24
|
-
* from its response, config/steer/levels; events validated before numbering). 4: commands carry
|
|
25
|
-
* their `spawnId`; `processor.history_end` carries `droppedCount`; a prompt closes open history.
|
|
26
|
-
* 5: Task 17c (the callback snapshot in the runtime diagnostics and its revision bumps; Qoder SDK
|
|
27
|
-
* cards carry a `callbackContext`; the Qoder adapter boundaries; side-channel notes). 6: a process call's
|
|
28
|
-
* result waits for its child's first status; ended/delivered calls keep no dead data.
|
|
29
|
-
* 7: Task 20 — the `agent_install_outcome` note becomes a `step` event (it was an unknown note).
|
|
30
|
-
* 8: Task 21 — a level update after a load's response is folded live, not as history; the `agent_output_gap` note is a `step`.
|
|
31
|
-
*/
|
|
32
|
-
export const PROCESSOR_VERSION = 8;
|
|
14
|
+
export const PROCESSOR_VERSION = 10;
|
|
33
15
|
export const CHECKPOINT_SCHEMA = 1;
|
|
34
16
|
export class CheckpointUnreadableError extends Error {
|
|
35
17
|
}
|
|
@@ -66,22 +48,32 @@ export function decodeCheckpoint(blob) {
|
|
|
66
48
|
throw new CheckpointUnreadableError('checkpoint has no processorVersion');
|
|
67
49
|
if (typeof offset !== 'number' || !Number.isSafeInteger(offset) || offset < -1)
|
|
68
50
|
throw new CheckpointUnreadableError('checkpoint offset is invalid');
|
|
69
|
-
if (!isRecord(state) || state.schema !== PROCESSOR_STATE_SCHEMA)
|
|
51
|
+
if (!isRecord(state) || (state.schema !== 4 && state.schema !== PROCESSOR_STATE_SCHEMA))
|
|
70
52
|
throw new CheckpointUnreadableError('checkpoint state schema is unknown');
|
|
71
53
|
if (state.offset !== offset)
|
|
72
54
|
throw new CheckpointUnreadableError('checkpoint offset disagrees with its state');
|
|
73
55
|
for (const key of ['process', 'initialize', 'outbound', 'inbound', 'diagnostics', 'permissions', 'elicitations',
|
|
74
56
|
'permissionOrigins', 'permissionParents', 'unresolvedPermissionOrigins', 'open', 'mapperScopes', 'message', 'steers',
|
|
75
|
-
'asyncTasks', 'runtime', 'callbacks'
|
|
57
|
+
'asyncTasks', 'runtime', 'callbacks']) {
|
|
76
58
|
if (!isRecord(state[key]))
|
|
77
59
|
throw new CheckpointUnreadableError(`checkpoint state has no ${key}`);
|
|
78
60
|
}
|
|
79
61
|
if (typeof state.seq !== 'number' || !Array.isArray(state.turns) || !Array.isArray(state.settledPermissions)) {
|
|
80
62
|
throw new CheckpointUnreadableError('checkpoint state has no session layer (seq/turns)');
|
|
81
63
|
}
|
|
82
|
-
if (!Array.isArray(state.steered) || !('configOptions' in state) || !('
|
|
64
|
+
if (!Array.isArray(state.steered) || !('configOptions' in state) || !('history' in state)
|
|
83
65
|
|| !('activity' in state)) {
|
|
84
66
|
throw new CheckpointUnreadableError('checkpoint state has no open-session layer (Task 17b)');
|
|
85
67
|
}
|
|
86
|
-
return { schema: CHECKPOINT_SCHEMA, processorVersion, offset, state: clone(state) };
|
|
68
|
+
return { schema: CHECKPOINT_SCHEMA, processorVersion, offset, state: clone({ ...(state.schema === 4 ? upgradeStateFromSchema4(state) : state), ...(!Object.hasOwn(state, 'draftSubagents') ? { draftSubagents: { children: {}, overflow: false } } : {}), ...(!Object.hasOwn(state, 'closedReplayScopes') ? { closedReplayScopes: {} } : {}) }) };
|
|
69
|
+
}
|
|
70
|
+
/** Read only the current neutral keys; retired adapter state is deliberately not copied. */
|
|
71
|
+
function upgradeStateFromSchema4(state) {
|
|
72
|
+
const keys = [...Object.keys(initialState()), 'closed', 'accountAuthBillingModes', 'accountLimits'];
|
|
73
|
+
const current = Object.fromEntries(keys.filter((key) => Object.hasOwn(state, key)).map((key) => [key, state[key]]));
|
|
74
|
+
const runtime = state.runtime;
|
|
75
|
+
return { ...current, schema: PROCESSOR_STATE_SCHEMA, runtime: {
|
|
76
|
+
revision: runtime.revision, available: runtime.available,
|
|
77
|
+
...(Object.hasOwn(runtime, 'launch') ? { launch: runtime.launch } : {}),
|
|
78
|
+
} };
|
|
87
79
|
}
|