@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
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { constants } from 'node:fs';
|
|
2
|
-
import { lstat, mkdir, mkdtemp, open, realpath, rmdir, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
-
export const OBSERVER_LIMITS = Object.freeze({
|
|
5
|
-
stdinBytes: 131072, frameBytes: 32768, queueBytes: 1048576, frames: 256,
|
|
6
|
-
});
|
|
7
|
-
const identifier = (value) => typeof value === 'string' &&
|
|
8
|
-
value.length > 0 && value.length <= 256 && !/[\x00-\x1f\x7f]/.test(value);
|
|
9
|
-
const sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino;
|
|
10
|
-
export async function validateObserverDirectory(plugin) {
|
|
11
|
-
const directory = await lstat(plugin.pluginDir);
|
|
12
|
-
if (!directory.isDirectory() || directory.isSymbolicLink() || !sameFile(directory, plugin.directory) ||
|
|
13
|
-
directory.uid !== process.getuid?.() || (directory.mode & 0o077) !== 0 ||
|
|
14
|
-
await realpath(plugin.pluginDir) !== plugin.pluginDir)
|
|
15
|
-
throw new Error('filesystem');
|
|
16
|
-
}
|
|
17
|
-
export async function openObserverFile(plugin, path, writable = false) {
|
|
18
|
-
await validateObserverDirectory(plugin);
|
|
19
|
-
const file = await open(path, (writable ? constants.O_RDWR : constants.O_RDONLY) | constants.O_NOFOLLOW);
|
|
20
|
-
try {
|
|
21
|
-
const info = await file.stat();
|
|
22
|
-
if (!info.isFile() || info.nlink !== 1 || info.uid !== process.getuid?.() ||
|
|
23
|
-
(info.mode & 0o077) !== 0 || (plugin.files[path] && !sameFile(info, plugin.files[path])))
|
|
24
|
-
throw new Error('filesystem');
|
|
25
|
-
await validateObserverDirectory(plugin);
|
|
26
|
-
return file;
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
await file.close();
|
|
30
|
-
throw error;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export async function acquireObserverLock(plugin) {
|
|
34
|
-
await validateObserverDirectory(plugin);
|
|
35
|
-
for (let attempt = 0; attempt < 10; attempt++) {
|
|
36
|
-
try {
|
|
37
|
-
await mkdir(plugin.lockPath, { mode: 0o700 });
|
|
38
|
-
const identity = await lstat(plugin.lockPath);
|
|
39
|
-
await validateObserverDirectory(plugin);
|
|
40
|
-
return async () => {
|
|
41
|
-
const current = await lstat(plugin.lockPath);
|
|
42
|
-
if (!current.isDirectory() || !sameFile(current, identity))
|
|
43
|
-
throw new Error('filesystem');
|
|
44
|
-
await rmdir(plugin.lockPath);
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
if (error.code !== 'EEXIST')
|
|
49
|
-
throw error;
|
|
50
|
-
const lock = await lstat(plugin.lockPath);
|
|
51
|
-
if (!lock.isDirectory() || lock.isSymbolicLink())
|
|
52
|
-
throw new Error('filesystem');
|
|
53
|
-
await new Promise((done) => setTimeout(done, 10));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
throw new Error('lock-busy');
|
|
57
|
-
}
|
|
58
|
-
// The generated CJS helper resolves only Node built-ins, independent of cwd or installed packages.
|
|
59
|
-
const HELPER = String.raw `'use strict';
|
|
60
|
-
const fs = require('node:fs'), path = require('node:path');
|
|
61
|
-
const C = fs.constants;
|
|
62
|
-
let config, directory, degraded;
|
|
63
|
-
function same(a,b) { return a.dev === b.dev && a.ino === b.ino; }
|
|
64
|
-
function id(x) { return typeof x === 'string' && x.length > 0 && x.length <= 256 && !/[\x00-\x1f\x7f]/.test(x); }
|
|
65
|
-
function checkDir() {
|
|
66
|
-
const s = fs.lstatSync(directory);
|
|
67
|
-
if (!s.isDirectory() || s.isSymbolicLink() || !same(s,config.directory) || s.uid !== process.getuid() ||
|
|
68
|
-
(s.mode & 63) || fs.realpathSync(directory) !== directory) throw Error('filesystem');
|
|
69
|
-
}
|
|
70
|
-
function file(name, write) {
|
|
71
|
-
checkDir();
|
|
72
|
-
const p = path.join(directory,name), fd = fs.openSync(p,(write ? C.O_RDWR : C.O_RDONLY)|C.O_NOFOLLOW);
|
|
73
|
-
try {
|
|
74
|
-
const s = fs.fstatSync(fd);
|
|
75
|
-
if (!s.isFile() || s.nlink !== 1 || s.uid !== process.getuid() || (s.mode & 63) ||
|
|
76
|
-
(config.files[p] && !same(s,config.files[p]))) throw Error('filesystem');
|
|
77
|
-
checkDir(); return fd;
|
|
78
|
-
} catch(e) { fs.closeSync(fd); throw e; }
|
|
79
|
-
}
|
|
80
|
-
function degrade(reason) {
|
|
81
|
-
try {
|
|
82
|
-
if (!config) return;
|
|
83
|
-
checkDir();
|
|
84
|
-
const fd = fs.openSync(degraded,C.O_WRONLY|C.O_CREAT|C.O_EXCL|C.O_NOFOLLOW,384);
|
|
85
|
-
try { fs.writeSync(fd,reason.slice(0,128)); } finally { fs.closeSync(fd); }
|
|
86
|
-
} catch (_) {}
|
|
87
|
-
}
|
|
88
|
-
async function lock() {
|
|
89
|
-
const p = path.join(directory,'lock');
|
|
90
|
-
for(let n=0;n<10;n++) {
|
|
91
|
-
checkDir();
|
|
92
|
-
try {
|
|
93
|
-
fs.mkdirSync(p,{mode:448}); const held = fs.lstatSync(p); checkDir();
|
|
94
|
-
return () => { const now=fs.lstatSync(p); if(!now.isDirectory() || !same(now,held)) throw Error('filesystem'); fs.rmdirSync(p); };
|
|
95
|
-
} catch(e) {
|
|
96
|
-
if(e.code !== 'EEXIST') throw e;
|
|
97
|
-
const s=fs.lstatSync(p); if(!s.isDirectory() || s.isSymbolicLink()) throw Error('filesystem');
|
|
98
|
-
await new Promise(r=>setTimeout(r,10));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
throw Error('lock-busy');
|
|
102
|
-
}
|
|
103
|
-
async function input(limit) {
|
|
104
|
-
return new Promise((done,reject)=>{
|
|
105
|
-
const chunks=[]; let length=0;
|
|
106
|
-
const timer=setTimeout(()=>{ process.stdin.destroy(); reject(Error('input-timeout')); },1000);
|
|
107
|
-
process.stdin.on('data',chunk=>{
|
|
108
|
-
length += chunk.length;
|
|
109
|
-
if(length > limit) { clearTimeout(timer); process.stdin.destroy(); reject(Error('stdin-limit')); }
|
|
110
|
-
else chunks.push(chunk);
|
|
111
|
-
});
|
|
112
|
-
process.stdin.on('end',()=>{
|
|
113
|
-
clearTimeout(timer);
|
|
114
|
-
try { done(new TextDecoder('utf-8',{fatal:true}).decode(Buffer.concat(chunks))); }
|
|
115
|
-
catch(_) { reject(Error('input')); }
|
|
116
|
-
});
|
|
117
|
-
process.stdin.on('error',()=>{ clearTimeout(timer); reject(Error('stdin')); });
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
async function main() {
|
|
121
|
-
const configPath=process.argv[2];
|
|
122
|
-
if(!configPath || !path.isAbsolute(configPath) || path.basename(configPath)!=='binding.json') return;
|
|
123
|
-
directory=path.dirname(configPath); degraded=path.join(directory,'degraded');
|
|
124
|
-
const fd=fs.openSync(configPath,C.O_RDONLY|C.O_NOFOLLOW);
|
|
125
|
-
try {
|
|
126
|
-
const s=fs.fstatSync(fd); if(!s.isFile() || s.nlink!==1 || s.uid!==process.getuid() || (s.mode&63) || s.size>8192) return;
|
|
127
|
-
const b=Buffer.alloc(8193), size=fs.readSync(fd,b,0,b.length,0);
|
|
128
|
-
if(size>8192) return; config=JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(b.subarray(0,size)));
|
|
129
|
-
} finally { fs.closeSync(fd); }
|
|
130
|
-
if(!id(config.rootSessionId)||!id(config.runtimeGeneration)||!id(config.token)||config.token.length<16) return;
|
|
131
|
-
checkDir();
|
|
132
|
-
const limits=config.limits, maxima={stdinBytes:131072,frameBytes:32768,queueBytes:1048576,frames:256};
|
|
133
|
-
for(const k of Object.keys(maxima)) if(!Number.isInteger(limits[k])||limits[k]<1||limits[k]>maxima[k]) return;
|
|
134
|
-
let raw;
|
|
135
|
-
try { raw=JSON.parse(await input(limits.stdinBytes)); } catch(e) { degrade(e.message==='stdin-limit'?'stdin-limit':'input'); return; }
|
|
136
|
-
if(!raw || typeof raw!=='object' || raw.session_id!==config.rootSessionId) { degrade('root-mismatch'); return; }
|
|
137
|
-
const names=['SubagentStart','SubagentStop','PreToolUse','PostToolUse','PermissionRequest'];
|
|
138
|
-
if(!names.includes(raw.hook_event_name)) { degrade('unknown-event'); return; }
|
|
139
|
-
const event={hook_event_name:raw.hook_event_name,session_id:raw.session_id};
|
|
140
|
-
for(const k of ['tool_use_id','agent_id','tool_name']) {
|
|
141
|
-
if(raw[k]!==undefined) { if(!id(raw[k])) { degrade('invalid-identity'); return; } event[k]=raw[k]; }
|
|
142
|
-
}
|
|
143
|
-
if(raw.hook_event_name==='PostToolUse' && raw.tool_name==='Agent') {
|
|
144
|
-
const r=raw.tool_response;
|
|
145
|
-
if(r && typeof r==='object' && !Array.isArray(r)) {
|
|
146
|
-
event.tool_response={};
|
|
147
|
-
for(const k of ['kind','agentId','agent_id','status']) if(r[k]!==undefined) {
|
|
148
|
-
if(!id(r[k])) { degrade('invalid-identity'); return; } event.tool_response[k]=r[k];
|
|
149
|
-
}
|
|
150
|
-
if(r.isAsync!==undefined) {
|
|
151
|
-
if(typeof r.isAsync!=='boolean') { degrade('invalid-identity'); return; }
|
|
152
|
-
event.tool_response.isAsync=r.isAsync;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const line=JSON.stringify({version:1,rootSessionId:config.rootSessionId,runtimeGeneration:config.runtimeGeneration,token:config.token,event})+'\n';
|
|
157
|
-
if(Buffer.byteLength(line)>limits.frameBytes) { degrade('frame-limit'); return; }
|
|
158
|
-
const release=await lock();
|
|
159
|
-
try {
|
|
160
|
-
const q=file('queue.jsonl',true);
|
|
161
|
-
try {
|
|
162
|
-
const s=fs.fstatSync(q); if(s.size>limits.queueBytes) throw Error('queue-limit');
|
|
163
|
-
const b=Buffer.alloc(limits.queueBytes+1), size=fs.readSync(q,b,0,b.length,0);
|
|
164
|
-
if(size>limits.queueBytes) throw Error('queue-limit');
|
|
165
|
-
const previous=new TextDecoder('utf-8',{fatal:true}).decode(b.subarray(0,size));
|
|
166
|
-
if(previous && !previous.endsWith('\n')) throw Error('queue-invalid');
|
|
167
|
-
if(size+Buffer.byteLength(line)>limits.queueBytes || previous.split('\n').length-1>=limits.frames) throw Error('queue-limit');
|
|
168
|
-
fs.writeSync(q,line,size,'utf8');
|
|
169
|
-
} finally { fs.closeSync(q); }
|
|
170
|
-
} finally { release(); }
|
|
171
|
-
}
|
|
172
|
-
main().catch(e=>degrade(['lock-busy','queue-limit','queue-invalid'].includes(e.message)?e.message:'filesystem')).finally(()=>process.exit(0));
|
|
173
|
-
`;
|
|
174
|
-
export async function createObserverPlugin(runDir, binding, options = {}) {
|
|
175
|
-
if (process.release.name !== 'node' || Number(process.versions.node.split('.')[0]) < 20 ||
|
|
176
|
-
!['linux', 'darwin'].includes(process.platform) || !isAbsolute(process.execPath))
|
|
177
|
-
throw new Error('Unsupported observer interpreter');
|
|
178
|
-
if (!identifier(binding.rootSessionId) || !identifier(binding.runtimeGeneration) ||
|
|
179
|
-
!identifier(binding.token) || binding.token.length < 16)
|
|
180
|
-
throw new Error('Invalid observer binding');
|
|
181
|
-
const limits = { ...OBSERVER_LIMITS, ...options.limits };
|
|
182
|
-
for (const key of Object.keys(OBSERVER_LIMITS)) {
|
|
183
|
-
if (!Number.isInteger(limits[key]) || limits[key] < 1 || limits[key] > OBSERVER_LIMITS[key])
|
|
184
|
-
throw new Error('Invalid observer limit');
|
|
185
|
-
}
|
|
186
|
-
const parent = await lstat(resolve(runDir));
|
|
187
|
-
if (!parent.isDirectory() || parent.isSymbolicLink())
|
|
188
|
-
throw new Error('Invalid observer directory');
|
|
189
|
-
const pluginDir = await mkdtemp(join(await realpath(runDir), 'observer-'));
|
|
190
|
-
const helperPath = join(pluginDir, 'observer.cjs'), configPath = join(pluginDir, 'binding.json');
|
|
191
|
-
const queuePath = join(pluginDir, 'queue.jsonl'), lockPath = join(pluginDir, 'lock');
|
|
192
|
-
const degradedPath = join(pluginDir, 'degraded');
|
|
193
|
-
const directories = [join(pluginDir, 'hooks'), join(pluginDir, '.qoder-plugin')];
|
|
194
|
-
for (const path of directories)
|
|
195
|
-
await mkdir(path, { mode: 0o700 });
|
|
196
|
-
const directory = await lstat(pluginDir);
|
|
197
|
-
const files = {};
|
|
198
|
-
const put = async (path, text) => {
|
|
199
|
-
await writeFile(path, text, { mode: 0o600, flag: 'wx' });
|
|
200
|
-
const info = await lstat(path);
|
|
201
|
-
files[path] = { dev: info.dev, ino: info.ino };
|
|
202
|
-
};
|
|
203
|
-
await put(helperPath, HELPER);
|
|
204
|
-
await put(queuePath, '');
|
|
205
|
-
const command = process.execPath, args = [helperPath, configPath];
|
|
206
|
-
const hooks = Object.fromEntries(['SubagentStart', 'SubagentStop', 'PreToolUse', 'PostToolUse', 'PermissionRequest']
|
|
207
|
-
.map((name) => [name, [{ hooks: [{ type: 'command', command, args, timeout: 2 }] }]]));
|
|
208
|
-
await put(join(pluginDir, 'hooks/hooks.json'), JSON.stringify({ hooks }));
|
|
209
|
-
await put(join(pluginDir, '.qoder-plugin/plugin.json'), JSON.stringify({ name: 'axom-observer', version: '1.0.0' }));
|
|
210
|
-
await put(configPath, JSON.stringify({ ...binding, directory: { dev: directory.dev, ino: directory.ino }, files, limits }));
|
|
211
|
-
let disposed = false;
|
|
212
|
-
const plugin = { ...binding, pluginDir, helperPath, configPath, queuePath, lockPath, degradedPath,
|
|
213
|
-
command, args, limits, directory: { dev: directory.dev, ino: directory.ino }, files,
|
|
214
|
-
async dispose() {
|
|
215
|
-
if (disposed)
|
|
216
|
-
return;
|
|
217
|
-
disposed = true;
|
|
218
|
-
const release = await acquireObserverLock(plugin);
|
|
219
|
-
try {
|
|
220
|
-
for (const path of [configPath, ...Object.keys(files).filter((path) => path !== configPath)]) {
|
|
221
|
-
const info = await lstat(path);
|
|
222
|
-
if (!info.isFile() || !sameFile(info, files[path]))
|
|
223
|
-
throw new Error('filesystem');
|
|
224
|
-
await unlink(path);
|
|
225
|
-
}
|
|
226
|
-
try {
|
|
227
|
-
const marker = await openObserverFile(plugin, degradedPath);
|
|
228
|
-
await marker.close();
|
|
229
|
-
await unlink(degradedPath);
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
if (error.code !== 'ENOENT')
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
finally {
|
|
237
|
-
await release();
|
|
238
|
-
}
|
|
239
|
-
for (const path of directories)
|
|
240
|
-
await rmdir(path);
|
|
241
|
-
await rmdir(pluginDir);
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
return plugin;
|
|
245
|
-
}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { acquireObserverLock, openObserverFile } from './observerPlugin.js';
|
|
2
|
-
export class ObserverReceiver {
|
|
3
|
-
plugin;
|
|
4
|
-
options;
|
|
5
|
-
rejected = [];
|
|
6
|
-
closed = false;
|
|
7
|
-
failed = false;
|
|
8
|
-
observed = false;
|
|
9
|
-
started = false;
|
|
10
|
-
inFlight;
|
|
11
|
-
timer;
|
|
12
|
-
get diagnostics() { return [...this.rejected]; }
|
|
13
|
-
get healthy() { return !this.closed && !this.failed && this.observed; }
|
|
14
|
-
constructor(plugin, options) {
|
|
15
|
-
this.plugin = plugin;
|
|
16
|
-
this.options = options;
|
|
17
|
-
}
|
|
18
|
-
degrade(reason) {
|
|
19
|
-
if (this.closed)
|
|
20
|
-
return;
|
|
21
|
-
this.failed = true;
|
|
22
|
-
this.rejected.push(reason);
|
|
23
|
-
if (this.rejected.length > 32)
|
|
24
|
-
this.rejected.shift();
|
|
25
|
-
try {
|
|
26
|
-
this.options.onDegraded?.(reason);
|
|
27
|
-
}
|
|
28
|
-
catch { /* Observation never interrupts native work. */ }
|
|
29
|
-
}
|
|
30
|
-
drain() {
|
|
31
|
-
if (this.closed)
|
|
32
|
-
return Promise.resolve();
|
|
33
|
-
if (!this.inFlight)
|
|
34
|
-
this.inFlight = this.readBatch().finally(() => { this.inFlight = undefined; });
|
|
35
|
-
return this.inFlight;
|
|
36
|
-
}
|
|
37
|
-
start() {
|
|
38
|
-
if (this.closed || this.started)
|
|
39
|
-
return;
|
|
40
|
-
this.started = true;
|
|
41
|
-
const poll = async () => {
|
|
42
|
-
this.timer = undefined;
|
|
43
|
-
await this.drain();
|
|
44
|
-
if (!this.closed)
|
|
45
|
-
this.timer = setTimeout(poll, 100);
|
|
46
|
-
};
|
|
47
|
-
this.timer = setTimeout(poll, 0);
|
|
48
|
-
}
|
|
49
|
-
async readBatch() {
|
|
50
|
-
let release;
|
|
51
|
-
let batch = [];
|
|
52
|
-
try {
|
|
53
|
-
const config = await openObserverFile(this.plugin, this.plugin.configPath);
|
|
54
|
-
await config.close();
|
|
55
|
-
try {
|
|
56
|
-
const marker = await openObserverFile(this.plugin, this.plugin.degradedPath);
|
|
57
|
-
try {
|
|
58
|
-
if ((await marker.stat()).size > 128)
|
|
59
|
-
throw new Error('filesystem');
|
|
60
|
-
this.degrade('helper-degraded');
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
await marker.close();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
if (error.code !== 'ENOENT')
|
|
68
|
-
throw error;
|
|
69
|
-
}
|
|
70
|
-
release = await acquireObserverLock(this.plugin);
|
|
71
|
-
const file = await openObserverFile(this.plugin, this.plugin.queuePath, true);
|
|
72
|
-
try {
|
|
73
|
-
if ((await file.stat()).size > this.plugin.limits.queueBytes)
|
|
74
|
-
throw new Error('queue-limit');
|
|
75
|
-
const bytes = Buffer.alloc(this.plugin.limits.queueBytes + 1);
|
|
76
|
-
const { bytesRead } = await file.read(bytes, 0, bytes.length, 0);
|
|
77
|
-
if (bytesRead > this.plugin.limits.queueBytes)
|
|
78
|
-
throw new Error('queue-limit');
|
|
79
|
-
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, bytesRead));
|
|
80
|
-
if (text && !text.endsWith('\n'))
|
|
81
|
-
throw new Error('queue-invalid');
|
|
82
|
-
const lines = text ? text.slice(0, -1).split('\n') : [];
|
|
83
|
-
if (lines.length > this.plugin.limits.frames)
|
|
84
|
-
throw new Error('queue-limit');
|
|
85
|
-
batch = lines.map((line) => {
|
|
86
|
-
if (Buffer.byteLength(line) + 1 > this.plugin.limits.frameBytes)
|
|
87
|
-
throw new Error('frame-limit');
|
|
88
|
-
return this.normalize(JSON.parse(line));
|
|
89
|
-
});
|
|
90
|
-
await file.truncate(0);
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
await file.close();
|
|
94
|
-
}
|
|
95
|
-
await release();
|
|
96
|
-
release = undefined;
|
|
97
|
-
for (const observation of batch) {
|
|
98
|
-
if (this.closed)
|
|
99
|
-
break;
|
|
100
|
-
this.observed = true;
|
|
101
|
-
if (observation.conflict)
|
|
102
|
-
this.degrade('identity-conflict');
|
|
103
|
-
try {
|
|
104
|
-
this.options.onObservation(observation);
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
this.degrade('callback');
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
const reason = error instanceof Error && ['queue-limit', 'frame-limit', 'queue-invalid', 'binding', 'event', 'identity', 'lock-busy'].includes(error.message)
|
|
113
|
-
? error.message : 'filesystem';
|
|
114
|
-
this.degrade(reason);
|
|
115
|
-
}
|
|
116
|
-
finally {
|
|
117
|
-
if (release)
|
|
118
|
-
try {
|
|
119
|
-
await release();
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
this.degrade('filesystem');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
normalize(input) {
|
|
127
|
-
const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
128
|
-
const id = (value) => typeof value === 'string' && value.length > 0 &&
|
|
129
|
-
value.length <= 256 && !/[\x00-\x1f\x7f]/.test(value);
|
|
130
|
-
if (!object(input) || input.version !== 1 || input.rootSessionId !== this.plugin.rootSessionId ||
|
|
131
|
-
input.runtimeGeneration !== this.plugin.runtimeGeneration || input.token !== this.plugin.token)
|
|
132
|
-
throw new Error('binding');
|
|
133
|
-
const event = input.event;
|
|
134
|
-
if (!object(event) || event.session_id !== this.plugin.rootSessionId ||
|
|
135
|
-
!['PreToolUse', 'PostToolUse', 'SubagentStart', 'SubagentStop', 'PermissionRequest'].includes(event.hook_event_name))
|
|
136
|
-
throw new Error('event');
|
|
137
|
-
for (const key of ['agent_id', 'tool_use_id', 'tool_name']) {
|
|
138
|
-
if (event[key] !== undefined && !id(event[key]))
|
|
139
|
-
throw new Error('identity');
|
|
140
|
-
}
|
|
141
|
-
const eventName = event.hook_event_name;
|
|
142
|
-
const root = { rootSessionId: this.plugin.rootSessionId, runtimeGeneration: this.plugin.runtimeGeneration };
|
|
143
|
-
if (eventName === 'PermissionRequest')
|
|
144
|
-
return { eventName };
|
|
145
|
-
if (eventName === 'SubagentStart' || eventName === 'SubagentStop') {
|
|
146
|
-
if (!id(event.agent_id))
|
|
147
|
-
throw new Error('identity');
|
|
148
|
-
return { eventName, origin: { ...root, agentId: event.agent_id } };
|
|
149
|
-
}
|
|
150
|
-
if (!id(event.tool_use_id))
|
|
151
|
-
throw new Error('identity');
|
|
152
|
-
if (eventName === 'PostToolUse' && event.tool_name === 'Agent') {
|
|
153
|
-
const response = event.tool_response;
|
|
154
|
-
if (!object(response))
|
|
155
|
-
throw new Error('identity');
|
|
156
|
-
const background = response.kind === undefined && response.isAsync === true && response.status === 'async_launched';
|
|
157
|
-
const foreground = response.kind === 'agent-result' && response.isAsync === undefined && response.status === undefined;
|
|
158
|
-
if (!foreground && !background)
|
|
159
|
-
throw new Error('identity');
|
|
160
|
-
const camel = response.agentId, snake = response.agent_id;
|
|
161
|
-
if ((camel !== undefined && !id(camel)) || (snake !== undefined && !id(snake)) || (!camel && !snake))
|
|
162
|
-
throw new Error('identity');
|
|
163
|
-
if (camel && snake && camel !== snake)
|
|
164
|
-
return { eventName, conflict: { ...root, launchingToolUseId: event.tool_use_id } };
|
|
165
|
-
return { eventName, launch: { ...root, launchingToolUseId: event.tool_use_id, agentId: (camel ?? snake) } };
|
|
166
|
-
}
|
|
167
|
-
return { eventName, ...(id(event.agent_id) ? { origin: { ...root, toolUseId: event.tool_use_id, agentId: event.agent_id } } : {}) };
|
|
168
|
-
}
|
|
169
|
-
async dispose() {
|
|
170
|
-
if (this.closed)
|
|
171
|
-
return;
|
|
172
|
-
this.closed = true;
|
|
173
|
-
clearTimeout(this.timer);
|
|
174
|
-
this.timer = undefined;
|
|
175
|
-
await this.inFlight;
|
|
176
|
-
try {
|
|
177
|
-
await this.plugin.dispose();
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
this.failed = true;
|
|
181
|
-
try {
|
|
182
|
-
this.options.onDegraded?.('cleanup');
|
|
183
|
-
}
|
|
184
|
-
catch { /* Best-effort observer cleanup. */ }
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
export class PermissionBridge {
|
|
3
|
-
options;
|
|
4
|
-
stopped = false;
|
|
5
|
-
pending = new Map();
|
|
6
|
-
constructor(options) {
|
|
7
|
-
this.options = options;
|
|
8
|
-
}
|
|
9
|
-
canUseTool = async (name, input, options) => {
|
|
10
|
-
const denied = (message) => ({ behavior: 'deny', message, toolUseID: options.toolUseID });
|
|
11
|
-
if (this.stopped)
|
|
12
|
-
return denied('Session closed');
|
|
13
|
-
if (options.signal.aborted)
|
|
14
|
-
return denied('Permission request cancelled');
|
|
15
|
-
if (name === 'AskUserQuestion')
|
|
16
|
-
return denied('AskUserQuestion answer mapping is unsupported');
|
|
17
|
-
if (!options.toolUseID || Buffer.byteLength(options.toolUseID, 'utf8') > 256 || /[\x00-\x1f\x7f]/.test(options.toolUseID) ||
|
|
18
|
-
(options.agentID !== undefined && (!options.agentID || Buffer.byteLength(options.agentID, 'utf8') > 256 || /[\x00-\x1f\x7f]/.test(options.agentID)))) {
|
|
19
|
-
return denied('Invalid permission identity');
|
|
20
|
-
}
|
|
21
|
-
if (this.pending.size >= 64 || [...this.pending.values()].some((entry) => entry.toolUseID === options.toolUseID)) {
|
|
22
|
-
return denied('Permission request capacity or identity conflict');
|
|
23
|
-
}
|
|
24
|
-
if (Buffer.byteLength(JSON.stringify(input), 'utf8') > 32768)
|
|
25
|
-
return denied('Permission input capacity exceeded');
|
|
26
|
-
const requestId = randomUUID();
|
|
27
|
-
const allowed = options.permissionOptions === undefined || options.permissionOptions.some((option) => option.outcome === 'proceed_once' && option.allowed === true);
|
|
28
|
-
const controller = new AbortController();
|
|
29
|
-
return new Promise((resolve) => {
|
|
30
|
-
let settled = false;
|
|
31
|
-
const settle = (result) => {
|
|
32
|
-
if (settled)
|
|
33
|
-
return;
|
|
34
|
-
settled = true;
|
|
35
|
-
options.signal.removeEventListener('abort', cancelled);
|
|
36
|
-
this.pending.delete(requestId);
|
|
37
|
-
controller.abort();
|
|
38
|
-
resolve(result);
|
|
39
|
-
void this.options.closed?.(requestId).catch(() => { });
|
|
40
|
-
};
|
|
41
|
-
const cancelled = () => settle(denied('Permission request cancelled'));
|
|
42
|
-
options.signal.addEventListener('abort', cancelled, { once: true });
|
|
43
|
-
this.pending.set(requestId, { toolUseID: options.toolUseID, deny: (message) => settle(denied(message)) });
|
|
44
|
-
const origin = { rootSessionId: this.options.rootSessionId, runtimeGeneration: this.options.runtimeGeneration,
|
|
45
|
-
toolUseId: options.toolUseID, ...(options.agentID ? { agentId: options.agentID } : {}) };
|
|
46
|
-
const request = { sessionId: this.options.rootSessionId,
|
|
47
|
-
toolCall: { toolCallId: options.toolUseID, title: name, rawInput: input,
|
|
48
|
-
_meta: { qoderSdk: { version: 1, requestId, origin, blockingScope: 'root', callbackOnly: true } } },
|
|
49
|
-
options: [...(allowed ? [{ optionId: 'allow', name: 'Allow Once', kind: 'allow_once' }] : []),
|
|
50
|
-
{ optionId: 'reject', name: 'Reject', kind: 'reject_once' }] };
|
|
51
|
-
Promise.resolve().then(() => this.options.request(request, controller.signal)).then((response) => {
|
|
52
|
-
settle(response.outcome.outcome === 'selected' && response.outcome.optionId === 'allow' && allowed
|
|
53
|
-
? { behavior: 'allow', permissionScope: 'once', updatedInput: input, toolUseID: options.toolUseID }
|
|
54
|
-
: denied('Rejected by user'));
|
|
55
|
-
}, () => settle(denied('Permission delivery unavailable')));
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
onElicitation = async () => ({ action: 'cancel' });
|
|
59
|
-
close() { this.stopped = true; for (const request of this.pending.values())
|
|
60
|
-
request.deny('Session closed'); }
|
|
61
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// H7 must add independently certified records. Runtime declarations cannot populate this list.
|
|
2
|
-
export const QODER_CERTIFICATIONS = [];
|
|
3
|
-
const certificationKeys = ['cliVersion', 'sdkVersion', 'mode', 'persistent', 'agentMessaging',
|
|
4
|
-
'transport', 'executable', 'protocolVersion', 'platform', 'runtimeSource',
|
|
5
|
-
'completedContinuationEnabled', 'sendMessageExposed'];
|
|
6
|
-
export function sameProfileIdentity(previous, next) {
|
|
7
|
-
return ['cliVersion', 'sdkVersion', 'mode', 'persistent', 'agentMessaging', 'transport',
|
|
8
|
-
'executable', 'platform', 'runtimeSource', 'completedContinuationEnabled']
|
|
9
|
-
.every((key) => previous[key] === undefined || previous[key] === 'unknown' || previous[key] === next[key]);
|
|
10
|
-
}
|
|
11
|
-
export function capabilitiesFor(profile, task, certifications = QODER_CERTIFICATIONS, binding) {
|
|
12
|
-
const bound = profile.rootConfirmed === true && profile.initialized === true &&
|
|
13
|
-
!!profile.rootSessionId && profile.rootSessionId === task.origin.rootSessionId &&
|
|
14
|
-
!!profile.runtimeGeneration && profile.runtimeGeneration === task.origin.runtimeGeneration;
|
|
15
|
-
const profileFingerprint = [profile.executable, profile.cliVersion, profile.sdkVersion].join('\u001e');
|
|
16
|
-
const bindingMatches = !binding || (binding.mode === 'SDK_ADAPTER' &&
|
|
17
|
-
binding.launchFingerprint === profileFingerprint && binding.agentKey === 'qoder' &&
|
|
18
|
-
binding.registryAgent === 'qoder');
|
|
19
|
-
const certified = bound && bindingMatches && profile.mode === 'sdk-adapter' && profile.observerHealthy && !task.stale &&
|
|
20
|
-
certifications.some((entry) => certificationKeys.every((key) => entry[key] === profile[key]));
|
|
21
|
-
const exact = !!task.origin.taskId?.trim();
|
|
22
|
-
return {
|
|
23
|
-
inspect: !!task.origin.rootSessionId.trim() && !task.stale,
|
|
24
|
-
stopActiveRun: certified && exact && task.live && task.state === 'running' &&
|
|
25
|
-
profile.capabilities.includes('background_tasks_v1'),
|
|
26
|
-
continueCompleted: certified && exact && task.state === 'completed' && !task.live &&
|
|
27
|
-
profile.persistent && profile.agentMessaging && profile.completedContinuationEnabled && profile.sendMessageExposed,
|
|
28
|
-
continueStopped: false,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
export function parseQoderProfile(value) {
|
|
32
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
33
|
-
return undefined;
|
|
34
|
-
const input = value;
|
|
35
|
-
const strings = ['cliVersion', 'sdkVersion', 'executable'];
|
|
36
|
-
const booleans = ['persistent', 'agentMessaging', 'observerHealthy', 'completedContinuationEnabled', 'sendMessageExposed'];
|
|
37
|
-
if (strings.some((key) => typeof input[key] !== 'string' || !input[key].trim() ||
|
|
38
|
-
input[key].length > (key === 'executable' ? 4096 : 64) ||
|
|
39
|
-
/[\x00-\x1f\x7f]/.test(input[key])) ||
|
|
40
|
-
!/^[a-zA-Z0-9][a-zA-Z0-9._+-]*$/.test(input.cliVersion) ||
|
|
41
|
-
!/^[a-zA-Z0-9][a-zA-Z0-9._+-]*$/.test(input.sdkVersion) ||
|
|
42
|
-
/^[a-zA-Z]+:\/\//.test(input.executable) ||
|
|
43
|
-
booleans.some((key) => typeof input[key] !== 'boolean') ||
|
|
44
|
-
!['native-acp', 'sdk-adapter'].includes(input.mode) ||
|
|
45
|
-
!['process', 'worker'].includes(input.transport) ||
|
|
46
|
-
!Array.isArray(input.capabilities) || input.capabilities.length > 256 ||
|
|
47
|
-
input.capabilities.some((item) => typeof item !== 'string' || item.length > 128 || !/^[a-zA-Z0-9_.-]+$/.test(item)))
|
|
48
|
-
return undefined;
|
|
49
|
-
const result = Object.fromEntries([...strings, ...booleans, 'mode', 'transport']
|
|
50
|
-
.map((key) => [key, input[key]]));
|
|
51
|
-
for (const key of ['initialized', 'rootConfirmed']) {
|
|
52
|
-
if (input[key] !== undefined) {
|
|
53
|
-
if (typeof input[key] !== 'boolean')
|
|
54
|
-
return undefined;
|
|
55
|
-
result[key] = input[key];
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
for (const key of ['rootSessionId', 'runtimeGeneration', 'platform']) {
|
|
59
|
-
if (input[key] !== undefined) {
|
|
60
|
-
if (typeof input[key] !== 'string' || !input[key].trim() ||
|
|
61
|
-
input[key].length > 256 || /[\x00-\x1f\x7f]/.test(input[key]))
|
|
62
|
-
return undefined;
|
|
63
|
-
result[key] = input[key];
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (input.protocolVersion !== undefined) {
|
|
67
|
-
if (!Number.isSafeInteger(input.protocolVersion) || input.protocolVersion < 0 ||
|
|
68
|
-
input.protocolVersion > 2_147_483_647)
|
|
69
|
-
return undefined;
|
|
70
|
-
result.protocolVersion = input.protocolVersion;
|
|
71
|
-
}
|
|
72
|
-
if (input.runtimeSource !== undefined) {
|
|
73
|
-
if (!['explicit-local', 'sdk-bundled', 'downloaded', 'unknown'].includes(input.runtimeSource))
|
|
74
|
-
return undefined;
|
|
75
|
-
result.runtimeSource = input.runtimeSource;
|
|
76
|
-
}
|
|
77
|
-
return { ...result, capabilities: [...new Set(input.capabilities)] };
|
|
78
|
-
}
|
|
79
|
-
export function isRuntimeDiagnostics(value) {
|
|
80
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
81
|
-
return false;
|
|
82
|
-
const input = value;
|
|
83
|
-
const bounded = (value, max) => typeof value === 'string' &&
|
|
84
|
-
value.trim().length > 0 && value.length <= max && !/[\x00-\x1f\x7f]/.test(value);
|
|
85
|
-
if (!bounded(input.generation, 256) || !Number.isSafeInteger(input.revision) ||
|
|
86
|
-
input.revision < 0 || typeof input.available !== 'boolean' ||
|
|
87
|
-
!input.launch || typeof input.launch !== 'object')
|
|
88
|
-
return false;
|
|
89
|
-
const launch = input.launch;
|
|
90
|
-
return ['command-override', 'registry', 'registry-fallback', 'direct'].includes(launch.source) &&
|
|
91
|
-
bounded(launch.executable, 4096) && bounded(launch.platform, 64) && bounded(launch.architecture, 64) &&
|
|
92
|
-
launch.transport === 'stdio' && (input.profile === undefined || parseQoderProfile(input.profile) !== undefined);
|
|
93
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { realpath, stat } from 'node:fs/promises';
|
|
3
|
-
import { isAbsolute } from 'node:path';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
|
-
/**
|
|
6
|
-
* Versions whose SDK stream contract has been exercised with live steering.
|
|
7
|
-
* Keep this allowlist explicit: a future Qoder release must first prove that
|
|
8
|
-
* `priority: "now"` still emits an abort receipt followed by the replacement
|
|
9
|
-
* result before the adapter advertises the extension. Retirement condition:
|
|
10
|
-
* once ACP exposes a native steering capability with the same lifecycle
|
|
11
|
-
* semantics, remove this adapter gate and route through that capability.
|
|
12
|
-
*/
|
|
13
|
-
export const QODER_SDK_STEERING_CLI_VERSIONS = ['1.1.61', '1.1.64'];
|
|
14
|
-
export function supportsQoderSdkSteering(cliVersion) {
|
|
15
|
-
return QODER_SDK_STEERING_CLI_VERSIONS.includes(cliVersion);
|
|
16
|
-
}
|
|
17
|
-
export async function loadSdkDriver(executable, sdk, queryFactory) {
|
|
18
|
-
if (!isAbsolute(executable))
|
|
19
|
-
throw new Error('Qoder executable must be an absolute local path');
|
|
20
|
-
let selected;
|
|
21
|
-
try {
|
|
22
|
-
selected = await realpath(executable);
|
|
23
|
-
if (!(await stat(selected)).isFile())
|
|
24
|
-
throw new Error('not a file');
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
throw new Error('Qoder executable is unavailable');
|
|
28
|
-
}
|
|
29
|
-
let version;
|
|
30
|
-
try {
|
|
31
|
-
const result = await promisify(execFile)(selected, ['--version'], { timeout: 5000, maxBuffer: 1024, encoding: 'utf8' });
|
|
32
|
-
version = result.stdout.trim();
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
throw new Error('Qoder executable version check failed');
|
|
36
|
-
}
|
|
37
|
-
if (!supportsQoderSdkSteering(version))
|
|
38
|
-
throw new Error(`Qoder executable must report a supported version (${QODER_SDK_STEERING_CLI_VERSIONS.join(' or ')})`);
|
|
39
|
-
return { executable: selected, sdkVersion: '1.0.50', cliVersion: version,
|
|
40
|
-
create: ({ prompt, options }) => (queryFactory ?? sdk.query)({ prompt, options: {
|
|
41
|
-
cwd: options.cwd, sessionId: options.sessionId, plugins: options.plugins,
|
|
42
|
-
canUseTool: options.canUseTool, onElicitation: options.onElicitation,
|
|
43
|
-
mcpServers: options.mcpServers, model: options.model, permissionMode: options.permissionMode,
|
|
44
|
-
sessionStore: options.sessionStore, transport: sdk.ProcessTransport.default,
|
|
45
|
-
auth: sdk.qodercliAuth(), pathToQoderCLIExecutable: selected,
|
|
46
|
-
} }),
|
|
47
|
-
};
|
|
48
|
-
}
|