@platless/axom-cli 0.1.0 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/authScopes.js +19 -0
- package/dist/axom-patch/cli/README.md +132 -0
- package/dist/axom-patch/cli/account-limits/README.md +27 -0
- package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
- package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
- package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
- package/dist/axom-patch/cli/account-limits/generate.py +120 -0
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +64 -0
- package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
- package/dist/axom-patch/cli/claude-account-limits.json +37 -0
- package/dist/axom-patch/cli/codex-account-limits.json +37 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/nightly-task.md +152 -0
- package/dist/axom-patch/cli/pi-steering.json +37 -0
- package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
- package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +38 -0
- package/dist/axom-patch/cli/probes/pi-steering.mjs +80 -0
- package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +89 -0
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +61 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +82 -0
- package/dist/axom-patch/cli/qoder-foreground-output.md +42 -0
- package/dist/axom-patch/cli/qoder-steering.json +52 -0
- package/dist/axom-patch/cli/qoder-steering.md +30 -0
- package/dist/axom-patch/cli/qoder-subagent-origin.json +73 -0
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +73 -0
- package/dist/axom-patch/cli/runtime.json +63 -0
- package/dist/axom-patch/cli/steering/claude-steering-js.patch +89 -0
- package/dist/axom-patch/cli/steering/claude-steering-types.patch +28 -0
- package/dist/axom-patch/cli/steering/codex-steering.patch +57 -0
- package/dist/axom-patch/cli/steering/pi-steering.js +79 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +31 -0
- package/dist/axom-patch/cli/zcode-models.json +38 -0
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +73 -0
- package/dist/cli.js +36 -1
- package/dist/commands/auth.js +4 -13
- package/dist/commands/execute.js +4 -4
- package/dist/commands/record.js +6 -6
- package/dist/commands/struct.js +4 -4
- package/dist/commands/workstation.js +2 -2
- package/dist/credentials.js +2 -2
- package/dist/install/claude/commands.js +13 -13
- package/dist/keepers/acp/keeper.mjs +3083 -0
- package/dist/keepers/acp/manifest.json +9 -0
- package/dist/keepers/terminal/keeper.mjs +8469 -0
- package/dist/keepers/terminal/manifest.json +9 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
- package/dist/mcp.js +32 -14
- package/dist/runtime-deps/qoder-sdk/package-lock.json +1189 -0
- package/dist/runtime-deps/qoder-sdk/package.json +8 -0
- package/dist/test/keeperKit.js +67 -0
- package/dist/workstation/acp/acpAgents.js +2514 -681
- package/dist/workstation/acp/acpClient.js +95 -318
- package/dist/workstation/acp/activityBusy.js +43 -0
- package/dist/workstation/acp/activityReporting.js +54 -0
- package/dist/workstation/acp/adoption.js +28 -356
- package/dist/workstation/acp/authUrlSniffer.js +36 -0
- package/dist/workstation/acp/binaryInstall.js +100 -27
- package/dist/workstation/acp/builds/gc.js +245 -0
- package/dist/workstation/acp/builds/install/binary.js +28 -0
- package/dist/workstation/acp/builds/install/npm.js +235 -0
- package/dist/workstation/acp/builds/install/uv.js +254 -0
- package/dist/workstation/acp/builds/migrate.js +195 -0
- package/dist/workstation/acp/builds/model.js +128 -0
- package/dist/workstation/acp/builds/nodeGate.js +100 -0
- package/dist/workstation/acp/builds/store.js +352 -0
- package/dist/workstation/acp/commandTableProbe.js +68 -0
- package/dist/workstation/acp/configOptions.js +103 -1
- package/dist/workstation/acp/delegationMcp.js +24 -0
- package/dist/workstation/acp/envSecrets.js +27 -0
- package/dist/workstation/acp/initializeResponse.js +285 -0
- package/dist/workstation/acp/installErrors.js +12 -2
- package/dist/workstation/acp/installProbe.js +1 -1
- package/dist/workstation/acp/managedPatches.js +144 -0
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +80 -0
- package/dist/workstation/acp/permission.js +335 -37
- package/dist/workstation/acp/processor/checkpoint.js +87 -0
- package/dist/workstation/acp/processor/commands.js +35 -0
- package/dist/workstation/acp/processor/control.js +355 -0
- package/dist/workstation/acp/processor/emit.js +26 -0
- package/dist/workstation/acp/processor/jsonrpc.js +172 -0
- package/dist/workstation/acp/processor/memoryLog.js +49 -0
- package/dist/workstation/acp/processor/origins.js +48 -0
- package/dist/workstation/acp/processor/output.js +67 -0
- package/dist/workstation/acp/processor/projection.js +42 -0
- package/dist/workstation/acp/processor/record.js +128 -0
- package/dist/workstation/acp/processor/runtime.js +181 -0
- package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
- package/dist/workstation/acp/processor/session.js +525 -0
- package/dist/workstation/acp/processor/sideChannel.js +80 -0
- package/dist/workstation/acp/processor/state.js +74 -0
- package/dist/workstation/acp/processor/step.js +459 -0
- package/dist/workstation/acp/processor/test/fixtureLog.js +65 -0
- package/dist/workstation/acp/processor/test/harness.js +120 -0
- package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
- package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
- package/dist/workstation/acp/processor/transport/generations.js +76 -0
- package/dist/workstation/acp/processor/transport/keeper.js +130 -0
- package/dist/workstation/acp/processor/transport/local.js +269 -0
- package/dist/workstation/acp/processor/transport/types.js +78 -0
- package/dist/workstation/acp/processor/updates.js +364 -0
- package/dist/workstation/acp/processorSession.js +1512 -0
- package/dist/workstation/acp/qoderStartup.js +45 -0
- package/dist/workstation/acp/registry/catalog.js +385 -4
- package/dist/workstation/acp/registry/detect.js +55 -0
- package/dist/workstation/acp/registry/launch.js +61 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +56 -27
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +114 -0
- package/dist/workstation/acp/terminalLogin.js +171 -0
- package/dist/workstation/acp/terminalLoginSpec.js +41 -0
- package/dist/workstation/acp/test/adapterFixtures.js +256 -0
- package/dist/workstation/acp/test/daemonLife.js +106 -0
- package/dist/workstation/acp/test/fakeAgent.js +185 -9
- package/dist/workstation/acp/test/keeperWorld.js +267 -0
- package/dist/workstation/acp/test/memoryOutbox.js +55 -0
- package/dist/workstation/acp/test/raw/fixtures.js +196 -0
- package/dist/workstation/acp/test/raw/qoderCaptures.js +125 -0
- package/dist/workstation/acp/test/raw/sanitize.js +344 -0
- package/dist/workstation/acp/test/raw/sources.js +249 -0
- package/dist/workstation/acp/test/realAdapter.js +377 -0
- package/dist/workstation/acp/test/scriptAgent.js +206 -0
- package/dist/workstation/acp/test/sessionFrames.js +42 -0
- package/dist/workstation/acp/timedCommand.js +4 -2
- package/dist/workstation/acp/uvxInstall.js +72 -122
- package/dist/workstation/acp/validate.js +33 -5
- package/dist/workstation/agentFlow.js +103 -0
- package/dist/workstation/agentSessions.js +1686 -348
- package/dist/workstation/channels/agent.js +14 -6
- package/dist/workstation/channels/computer.js +203 -7
- package/dist/workstation/channels/computerExecs.js +200 -0
- package/dist/workstation/channels/management.js +86 -6
- package/dist/workstation/config.js +3 -1
- package/dist/workstation/connection/encryptedChannel.js +88 -21
- package/dist/workstation/daemon.js +309 -6
- package/dist/workstation/daemonctl.js +28 -2
- package/dist/workstation/desktop/binding.js +36 -0
- package/dist/workstation/desktop/bridge.js +212 -0
- package/dist/workstation/desktop/flow-control.js +52 -0
- package/dist/workstation/desktop/focused-text.js +130 -0
- package/dist/workstation/desktop/protocol.js +35 -0
- package/dist/workstation/exec/attach.js +126 -0
- package/dist/workstation/exec/command.js +40 -0
- package/dist/workstation/exec/durable.js +517 -0
- package/dist/workstation/exec/fallback.js +106 -0
- package/dist/workstation/exec/policy.js +837 -0
- package/dist/workstation/exec/shaping.js +211 -0
- package/dist/workstation/exec.js +3 -112
- package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
- package/dist/workstation/files/adapters/toolRequests.js +121 -0
- package/dist/workstation/files/client.js +455 -0
- package/dist/workstation/files/errors.js +28 -0
- package/dist/workstation/files/fence.js +350 -0
- package/dist/workstation/files/find/fileIndex.js +153 -0
- package/dist/workstation/files/find/fuzzy.js +155 -0
- package/dist/workstation/files/host.js +122 -0
- package/dist/workstation/files/hostProtocol.js +2 -0
- package/dist/workstation/files/media/cache.js +620 -0
- package/dist/workstation/files/media/encoding.js +308 -0
- package/dist/workstation/files/media/estimator.js +100 -0
- package/dist/workstation/files/media/media.js +325 -0
- package/dist/workstation/files/media/packedAudio.js +23 -0
- package/dist/workstation/files/media/scheduler.js +551 -0
- package/dist/workstation/files/media/types.js +5 -0
- package/dist/workstation/files/scope.js +45 -0
- package/dist/workstation/files/structured.js +284 -0
- package/dist/workstation/files/text.js +356 -0
- package/dist/workstation/files/uploads.js +351 -0
- package/dist/workstation/journal.js +65 -236
- package/dist/workstation/keepers/acp/client.js +101 -0
- package/dist/workstation/keepers/acp/envelope.js +62 -0
- package/dist/workstation/keepers/acp/host.js +419 -0
- package/dist/workstation/keepers/acp/main.js +18 -0
- package/dist/workstation/keepers/acp/memoryStream.js +121 -0
- package/dist/workstation/keepers/acp/params.js +137 -0
- package/dist/workstation/keepers/acp/protocol.js +66 -0
- package/dist/workstation/keepers/acp/recover.js +120 -0
- package/dist/workstation/keepers/acp/session.js +584 -0
- package/dist/workstation/keepers/acp/stderr.js +154 -0
- package/dist/workstation/keepers/kit/activity.js +103 -0
- package/dist/workstation/keepers/kit/bootstrap.js +164 -0
- package/dist/workstation/keepers/kit/buildVersion.js +51 -0
- package/dist/workstation/keepers/kit/bundle.js +120 -0
- package/dist/workstation/keepers/kit/client.js +432 -0
- package/dist/workstation/keepers/kit/identity.js +21 -0
- package/dist/workstation/keepers/kit/ipc.js +112 -0
- package/dist/workstation/keepers/kit/launchConfig.js +8 -0
- package/dist/workstation/keepers/kit/launcher.js +249 -0
- package/dist/workstation/keepers/kit/lock.js +123 -0
- package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
- package/dist/workstation/keepers/kit/paths.js +75 -0
- package/dist/workstation/keepers/kit/protocol.js +43 -0
- package/dist/workstation/keepers/kit/server.js +518 -0
- package/dist/workstation/keepers/kit/streamLog.js +697 -0
- package/dist/workstation/keepers/legacy.js +296 -0
- package/dist/workstation/keepers/policy.js +576 -0
- package/dist/workstation/keepers/stop.js +34 -0
- package/dist/workstation/keepers/terminal/client.js +213 -0
- package/dist/workstation/keepers/terminal/exec.js +981 -0
- package/dist/workstation/keepers/terminal/generations.js +129 -0
- package/dist/workstation/keepers/terminal/keeper.js +62 -0
- package/dist/workstation/keepers/terminal/main.js +43 -0
- package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
- package/dist/workstation/keepers/terminal/params.js +27 -0
- package/dist/workstation/keepers/terminal/protocol.js +56 -0
- package/dist/workstation/keepers/terminal/pty.js +330 -0
- package/dist/workstation/oauth/deviceAuthorization.js +21 -12
- package/dist/workstation/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +201 -8
- package/dist/workstation/protocol/common.js +36 -0
- package/dist/workstation/protocol/computer.js +107 -7
- package/dist/workstation/protocol/management.js +50 -2
- package/dist/workstation/qoderAdapter/backgroundProcess.js +118 -0
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +193 -0
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +372 -0
- package/dist/workstation/qoderAdapter/callbackContext.js +76 -0
- package/dist/workstation/qoderAdapter/completedContinuation.js +139 -0
- package/dist/workstation/qoderAdapter/contract.js +78 -0
- package/dist/workstation/qoderAdapter/foregroundOutput.js +36 -0
- package/dist/workstation/qoderAdapter/history.js +85 -0
- package/dist/workstation/qoderAdapter/identityIndex.js +191 -0
- package/dist/workstation/qoderAdapter/inputQueue.js +42 -0
- package/dist/workstation/qoderAdapter/main.js +95 -0
- package/dist/workstation/qoderAdapter/mapper.js +117 -0
- package/dist/workstation/qoderAdapter/observerPlugin.js +245 -0
- package/dist/workstation/qoderAdapter/observerReceiver.js +187 -0
- package/dist/workstation/qoderAdapter/permissions.js +61 -0
- package/dist/workstation/qoderAdapter/profile.js +93 -0
- package/dist/workstation/qoderAdapter/sdkDriver.js +48 -0
- package/dist/workstation/qoderAdapter/sdkRuntime.js +93 -0
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +145 -0
- package/dist/workstation/qoderAdapter/session.js +319 -0
- package/dist/workstation/qoderAdapter/subagentMeta.js +28 -0
- package/dist/workstation/qoderAdapter/subwork.js +85 -0
- package/dist/workstation/qoderAdapter/taskControl.js +38 -0
- package/dist/workstation/qoderAdapter/taskLedger.js +163 -0
- package/dist/workstation/qoderAdapter/types.js +1 -0
- package/dist/workstation/qoderAdapter/usage.js +37 -0
- package/dist/workstation/register.js +4 -0
- package/dist/workstation/systemMetrics.js +110 -1
- package/dist/workstation/tools/browser.js +394 -0
- package/dist/workstation/tools/client.js +277 -0
- package/dist/workstation/tools/info.js +50 -0
- package/dist/workstation/tools/keeperProcess.js +124 -0
- package/dist/workstation/tools/router.js +55 -0
- package/dist/workstation/tools/terminalRouter.js +222 -0
- package/dist/workstation/tools/validation.js +86 -0
- package/dist/workstation/update/coordinator.js +32 -0
- package/dist/workstation/update/npmInstaller.js +2 -0
- package/dist/workstation/update/ptyPreflight.js +45 -0
- package/dist/workstation/update/taskRegistry.js +141 -23
- package/dist/workstation/update/watchdog.js +66 -0
- package/package.json +20 -7
- package/skills/axom/SKILL.md +55 -17
- package/skills/axom-definition/SKILL.md +32 -32
- package/skills/axom-platform/SKILL.md +39 -39
- package/dist/workstation/acp/acpSession.js +0 -1047
- package/dist/workstation/acp/contract.js +0 -494
- package/dist/workstation/acp/draftInterceptor.js +0 -154
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, realpath, lstat } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
import { clientDir } from '../config.js';
|
|
7
|
+
export const QODER_SDK_VERSION = '1.0.50';
|
|
8
|
+
export const QODER_SDK_PACKAGE = '@qoder-ai/qoder-agent-sdk';
|
|
9
|
+
export const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
export async function sdkRecipe(options = {}) {
|
|
11
|
+
const directory = options.recipeDir ?? fileURLToPath(new URL(import.meta.url.endsWith('.ts') ? '../../../runtime-deps/qoder-sdk/' : '../../runtime-deps/qoder-sdk/', import.meta.url));
|
|
12
|
+
const [manifest, lock] = await Promise.all(['package.json', 'package-lock.json'].map(file => readFile(join(directory, file))));
|
|
13
|
+
const pkg = JSON.parse(manifest.toString());
|
|
14
|
+
const parsed = JSON.parse(lock.toString());
|
|
15
|
+
if (pkg.dependencies?.[QODER_SDK_PACKAGE] !== QODER_SDK_VERSION ||
|
|
16
|
+
parsed.packages?.['']?.dependencies?.[QODER_SDK_PACKAGE] !== QODER_SDK_VERSION ||
|
|
17
|
+
parsed.packages?.[`node_modules/${QODER_SDK_PACKAGE}`]?.version !== QODER_SDK_VERSION)
|
|
18
|
+
throw new Error('Invalid bundled Qoder SDK recipe');
|
|
19
|
+
const digest = sha256(Buffer.concat([Buffer.from('axom-qoder-sdk-v1\0'), manifest, Buffer.from('\0'), lock]));
|
|
20
|
+
return { manifest, lock, digest, packages: parsed.packages,
|
|
21
|
+
root: resolve(options.configDir ?? clientDir(), 'acp-adapters', 'qoder-sdk', digest, `${process.platform}-${process.arch}-node${process.versions.node.split('.')[0]}`) };
|
|
22
|
+
}
|
|
23
|
+
function inside(root, file) {
|
|
24
|
+
const rel = relative(root, file);
|
|
25
|
+
return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
26
|
+
}
|
|
27
|
+
/** Validate the selected instance, never a package found in an ancestor or global prefix. */
|
|
28
|
+
export async function validateSdkInstance(instance, recipe) {
|
|
29
|
+
const canonical = await realpath(instance);
|
|
30
|
+
if (!(await lstat(instance)).isDirectory())
|
|
31
|
+
throw new Error('SDK instance must not be a symlink');
|
|
32
|
+
for (const [file, bytes] of [['package.json', recipe.manifest], ['package-lock.json', recipe.lock]]) {
|
|
33
|
+
if (!(await readFile(join(instance, file))).equals(bytes))
|
|
34
|
+
throw new Error(`SDK ${file} does not match recipe`);
|
|
35
|
+
}
|
|
36
|
+
for (const [key, metadata] of Object.entries(recipe.packages)) {
|
|
37
|
+
if (key === '')
|
|
38
|
+
continue;
|
|
39
|
+
if (!key.startsWith('node_modules/') || key.split('/').includes('..') || metadata.link)
|
|
40
|
+
throw new Error('Invalid SDK lockfile package path');
|
|
41
|
+
const directory = join(instance, key);
|
|
42
|
+
let actual;
|
|
43
|
+
try {
|
|
44
|
+
if (!inside(canonical, await realpath(directory)))
|
|
45
|
+
throw new Error('SDK dependency escapes its instance');
|
|
46
|
+
actual = JSON.parse(await readFile(join(directory, 'package.json'), 'utf8'));
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (metadata.optional && error.code === 'ENOENT')
|
|
50
|
+
continue;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
const name = key.slice(key.lastIndexOf('node_modules/') + 'node_modules/'.length);
|
|
54
|
+
if (actual.name !== name || actual.version !== metadata.version)
|
|
55
|
+
throw new Error(`SDK dependency mismatch: ${name}`);
|
|
56
|
+
}
|
|
57
|
+
const require = createRequire(join(instance, 'package.json'));
|
|
58
|
+
const entry = await realpath(require.resolve(QODER_SDK_PACKAGE));
|
|
59
|
+
if (!inside(join(canonical, 'node_modules', QODER_SDK_PACKAGE), entry))
|
|
60
|
+
throw new Error('SDK entry escapes its package');
|
|
61
|
+
return { entry, entrySha256: sha256(await readFile(entry)) };
|
|
62
|
+
}
|
|
63
|
+
export function assertSdkApi(sdk) {
|
|
64
|
+
const api = sdk;
|
|
65
|
+
if (!api || ['query', 'qodercliAuth', 'listSubagents', 'getSubagentMessages'].some(key => typeof api[key] !== 'function') || !api.ProcessTransport?.default)
|
|
66
|
+
throw new Error('Prepared Qoder SDK is missing required APIs');
|
|
67
|
+
}
|
|
68
|
+
export async function readPreparedSdkRuntime(options = {}) {
|
|
69
|
+
const recipe = await sdkRecipe(options);
|
|
70
|
+
const pointer = JSON.parse(await readFile(join(recipe.root, 'current.json'), 'utf8'));
|
|
71
|
+
if (pointer.digest !== recipe.digest || !/^[a-f0-9-]{36}$/.test(pointer.installationId ?? ''))
|
|
72
|
+
throw new Error('Invalid SDK installation pointer');
|
|
73
|
+
const instance = join(recipe.root, 'instances', pointer.installationId);
|
|
74
|
+
const receipt = JSON.parse(await readFile(join(instance, 'receipt.json'), 'utf8'));
|
|
75
|
+
const validated = await validateSdkInstance(instance, recipe);
|
|
76
|
+
if (receipt.schema !== 1 || receipt.digest !== recipe.digest || receipt.version !== QODER_SDK_VERSION ||
|
|
77
|
+
receipt.installationId !== pointer.installationId || receipt.entrySha256 !== validated.entrySha256 ||
|
|
78
|
+
receipt.lockSha256 !== sha256(recipe.lock))
|
|
79
|
+
throw new Error('Prepared SDK receipt does not match installation');
|
|
80
|
+
return { instance, ...validated };
|
|
81
|
+
}
|
|
82
|
+
/** Read-only: ACP serving must never install dependencies or fall back to the developer SDK. */
|
|
83
|
+
export async function loadPreparedSdkRuntime(options = {}) {
|
|
84
|
+
try {
|
|
85
|
+
const prepared = await readPreparedSdkRuntime(options);
|
|
86
|
+
const sdk = await import(pathToFileURL(prepared.entry).href);
|
|
87
|
+
assertSdkApi(sdk);
|
|
88
|
+
return { ...prepared, sdk };
|
|
89
|
+
}
|
|
90
|
+
catch (cause) {
|
|
91
|
+
throw new Error(`Qoder SDK is not prepared or is damaged. Run axom qoder-acp --prepare-sdk as the same user with AXOM_WORKSTATION_CONFIG_DIR=${options.configDir ?? clientDir()}`, { cause });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { mkdir, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { assertSdkApi, QODER_SDK_VERSION, readPreparedSdkRuntime, sdkRecipe, sha256, validateSdkInstance } from './sdkRuntime.js';
|
|
7
|
+
/** No shell, bounded output, and no surviving npm subprocesses after timeout or cancellation. */
|
|
8
|
+
export function runSdkCommand(command, args, options) {
|
|
9
|
+
return new Promise((resolvePromise, reject) => {
|
|
10
|
+
if (options.signal?.aborted) {
|
|
11
|
+
reject(new Error('Qoder SDK preparation cancelled'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (options.timeoutMs <= 0) {
|
|
15
|
+
reject(new Error('Qoder SDK preparation timed out'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let output = '', failure;
|
|
19
|
+
const child = spawn(command, args, { cwd: options.cwd, stdio: ['ignore', 'pipe', 'pipe'],
|
|
20
|
+
detached: process.platform !== 'win32', windowsHide: true });
|
|
21
|
+
const capture = (chunk) => { output = (output + chunk.toString()).slice(-8192); };
|
|
22
|
+
child.stdout.on('data', capture);
|
|
23
|
+
child.stderr.on('data', capture);
|
|
24
|
+
const stop = (reason) => {
|
|
25
|
+
failure ??= new Error(reason);
|
|
26
|
+
if (!child.pid)
|
|
27
|
+
return;
|
|
28
|
+
if (process.platform === 'win32') {
|
|
29
|
+
const killer = spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
|
|
30
|
+
killer.on('error', () => { child.kill('SIGKILL'); });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
try {
|
|
34
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
child.kill('SIGKILL');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const abort = () => stop('Qoder SDK preparation cancelled');
|
|
42
|
+
const timer = setTimeout(() => stop('Qoder SDK preparation timed out'), options.timeoutMs);
|
|
43
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
44
|
+
const cleanup = () => { clearTimeout(timer); options.signal?.removeEventListener('abort', abort); };
|
|
45
|
+
child.once('error', error => { cleanup(); reject(error); });
|
|
46
|
+
child.once('close', code => {
|
|
47
|
+
cleanup();
|
|
48
|
+
if (failure)
|
|
49
|
+
reject(failure);
|
|
50
|
+
else if (code !== 0)
|
|
51
|
+
reject(new Error(`Qoder SDK preparation failed (${code}): ${output}`));
|
|
52
|
+
else
|
|
53
|
+
resolvePromise();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Resolve the npm JavaScript entry so standard Windows npm.cmd never needs shell execution. */
|
|
58
|
+
export async function sdkNpmCli() {
|
|
59
|
+
const configured = process.env.AXOM_WORKSTATION_NPM_PATH;
|
|
60
|
+
const candidates = configured ? [configured] : [join(dirname(process.execPath), process.platform === 'win32' ? 'npm.cmd' : 'npm'),
|
|
61
|
+
...(process.env.PATH ?? '').split(delimiter).filter(Boolean).map(dir => join(dir, process.platform === 'win32' ? 'npm.cmd' : 'npm'))];
|
|
62
|
+
for (const candidate of candidates) {
|
|
63
|
+
try {
|
|
64
|
+
const actual = await realpath(candidate);
|
|
65
|
+
if (actual.endsWith(`${join('bin', 'npm-cli.js')}`))
|
|
66
|
+
return actual;
|
|
67
|
+
for (const script of [join(dirname(actual), 'node_modules/npm/bin/npm-cli.js'), join(dirname(actual), '../lib/node_modules/npm/bin/npm-cli.js')]) {
|
|
68
|
+
if ((await stat(script).catch(() => undefined))?.isFile())
|
|
69
|
+
return resolve(script);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch { /* try the next PATH entry */ }
|
|
73
|
+
}
|
|
74
|
+
throw new Error('Cannot resolve npm CLI. Set AXOM_WORKSTATION_NPM_PATH to the standard npm executable.');
|
|
75
|
+
}
|
|
76
|
+
const preparing = new Map();
|
|
77
|
+
export async function prepareSdkRuntime(options = {}) {
|
|
78
|
+
const recipe = await sdkRecipe(options);
|
|
79
|
+
// Calls with independent cancellation signals keep their own lifecycle; immutable publication
|
|
80
|
+
// also handles separate processes without a cross-process lock.
|
|
81
|
+
if (!options.signal && preparing.has(recipe.root))
|
|
82
|
+
return preparing.get(recipe.root);
|
|
83
|
+
const pending = prepare(options, recipe);
|
|
84
|
+
if (!options.signal)
|
|
85
|
+
preparing.set(recipe.root, pending);
|
|
86
|
+
try {
|
|
87
|
+
return await pending;
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
if (preparing.get(recipe.root) === pending)
|
|
91
|
+
preparing.delete(recipe.root);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function prepare(options, recipe) {
|
|
95
|
+
const budget = options.timeoutMs ?? 300_000;
|
|
96
|
+
if (!Number.isFinite(budget) || budget <= 0)
|
|
97
|
+
throw new Error('SDK preparation timeout must be positive');
|
|
98
|
+
const deadline = Date.now() + budget;
|
|
99
|
+
const commandOptions = (cwd) => ({ cwd, timeoutMs: deadline - Date.now(), signal: options.signal });
|
|
100
|
+
const probe = async (entry, cwd) => {
|
|
101
|
+
const code = `const sdk=await import(${JSON.stringify(pathToFileURL(entry).href)}); (${assertSdkApi.toString()})(sdk);`;
|
|
102
|
+
await runSdkCommand(process.execPath, ['--input-type=module', '-e', code], commandOptions(cwd));
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
const existing = await readPreparedSdkRuntime(options);
|
|
106
|
+
await probe(existing.entry, existing.instance);
|
|
107
|
+
return { instance: existing.instance, alreadyInstalled: true };
|
|
108
|
+
}
|
|
109
|
+
catch { /* Missing/damaged runtime: publish a new instance; leave old instances untouched. */ }
|
|
110
|
+
const installationId = randomUUID();
|
|
111
|
+
const staging = join(recipe.root, 'staging', installationId);
|
|
112
|
+
const instance = join(recipe.root, 'instances', installationId);
|
|
113
|
+
const pointerTemp = join(recipe.root, `current-${installationId}.json`);
|
|
114
|
+
await mkdir(staging, { recursive: true, mode: 0o700 });
|
|
115
|
+
try {
|
|
116
|
+
await writeFile(join(staging, 'package.json'), recipe.manifest);
|
|
117
|
+
await writeFile(join(staging, 'package-lock.json'), recipe.lock);
|
|
118
|
+
if (options.install)
|
|
119
|
+
await options.install(staging, commandOptions(staging));
|
|
120
|
+
else
|
|
121
|
+
await runSdkCommand(process.execPath, [await sdkNpmCli(), 'ci', '--ignore-scripts', '--omit=dev', '--include=optional',
|
|
122
|
+
'--no-audit', '--no-fund'], commandOptions(staging));
|
|
123
|
+
const validated = await validateSdkInstance(staging, recipe);
|
|
124
|
+
await probe(validated.entry, staging);
|
|
125
|
+
if (options.signal?.aborted)
|
|
126
|
+
throw new Error('Qoder SDK preparation cancelled');
|
|
127
|
+
if (Date.now() >= deadline)
|
|
128
|
+
throw new Error('Qoder SDK preparation timed out');
|
|
129
|
+
await writeFile(join(staging, 'receipt.json'), JSON.stringify({ schema: 1, digest: recipe.digest,
|
|
130
|
+
version: QODER_SDK_VERSION, installationId, lockSha256: sha256(recipe.lock), entrySha256: validated.entrySha256 }));
|
|
131
|
+
await mkdir(dirname(instance), { recursive: true, mode: 0o700 });
|
|
132
|
+
await rename(staging, instance);
|
|
133
|
+
await writeFile(pointerTemp, JSON.stringify({ digest: recipe.digest, installationId }), { flag: 'wx', mode: 0o600 });
|
|
134
|
+
if (options.signal?.aborted)
|
|
135
|
+
throw new Error('Qoder SDK preparation cancelled');
|
|
136
|
+
if (Date.now() >= deadline)
|
|
137
|
+
throw new Error('Qoder SDK preparation timed out');
|
|
138
|
+
await rename(pointerTemp, join(recipe.root, 'current.json'));
|
|
139
|
+
return { instance, alreadyInstalled: false };
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
await rm(staging, { recursive: true, force: true });
|
|
143
|
+
await rm(pointerTemp, { force: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { isAbsolute } from 'node:path';
|
|
3
|
+
import { InputQueue } from './inputQueue.js';
|
|
4
|
+
import { createObserverPlugin } from './observerPlugin.js';
|
|
5
|
+
import { ObserverReceiver } from './observerReceiver.js';
|
|
6
|
+
import { PermissionBridge } from './permissions.js';
|
|
7
|
+
import { QoderMessageMapper } from './mapper.js';
|
|
8
|
+
import { TaskLedger } from './taskLedger.js';
|
|
9
|
+
import { TaskControl } from './taskControl.js';
|
|
10
|
+
import { readChildHistory } from './history.js';
|
|
11
|
+
import { supportsQoderSdkSteering } from './sdkDriver.js';
|
|
12
|
+
export class QoderAdapterSession {
|
|
13
|
+
dependencies;
|
|
14
|
+
query;
|
|
15
|
+
input = new InputQueue();
|
|
16
|
+
receiver;
|
|
17
|
+
permissions;
|
|
18
|
+
mapper;
|
|
19
|
+
taskLedger;
|
|
20
|
+
taskControl;
|
|
21
|
+
resultIds = new Set();
|
|
22
|
+
earlyObservations = [];
|
|
23
|
+
degraded = false;
|
|
24
|
+
updateTail = Promise.resolve();
|
|
25
|
+
taskTail = Promise.resolve();
|
|
26
|
+
queuedUpdates = 0;
|
|
27
|
+
pump;
|
|
28
|
+
shutdown;
|
|
29
|
+
closed = false;
|
|
30
|
+
root;
|
|
31
|
+
cwd;
|
|
32
|
+
profileState;
|
|
33
|
+
active;
|
|
34
|
+
constructor(dependencies) {
|
|
35
|
+
this.dependencies = dependencies;
|
|
36
|
+
}
|
|
37
|
+
get currentProfile() {
|
|
38
|
+
return this.profileState ? { ...this.profileState, capabilities: [...this.profileState.capabilities] } : undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Public-SDK child history is exposed through the live adapter, never through private files. */
|
|
41
|
+
async readChildHistory(agentId, page) {
|
|
42
|
+
const root = this.root;
|
|
43
|
+
const dir = page?.dir ?? this.cwd;
|
|
44
|
+
if (!root || !dir) {
|
|
45
|
+
return { status: 'unknown', entries: [], rootSessionId: root ?? 'unknown', agentId,
|
|
46
|
+
offset: page?.offset ?? 0, limit: page?.limit ?? 50, hasMore: false, snapshotStable: false,
|
|
47
|
+
reason: 'invalid-page' };
|
|
48
|
+
}
|
|
49
|
+
return readChildHistory(root, agentId, { ...page, dir }, this.dependencies.historyReader);
|
|
50
|
+
}
|
|
51
|
+
async publishProfile() {
|
|
52
|
+
const value = this.currentProfile;
|
|
53
|
+
if (value)
|
|
54
|
+
await this.dependencies.profile(value);
|
|
55
|
+
}
|
|
56
|
+
degrade() {
|
|
57
|
+
if (this.degraded)
|
|
58
|
+
return;
|
|
59
|
+
this.degraded = true;
|
|
60
|
+
for (const task of this.taskLedger?.markStale() ?? [])
|
|
61
|
+
void this.queueTask(task, `degraded-${this.profileState?.runtimeGeneration}`).catch(() => { });
|
|
62
|
+
if (this.profileState)
|
|
63
|
+
this.profileState.observerHealthy = false;
|
|
64
|
+
void this.publishProfile().catch(() => { });
|
|
65
|
+
}
|
|
66
|
+
publishTask(task, uuid, summary) {
|
|
67
|
+
return this.dependencies.taskObservation?.({ rootSessionId: task.origin.rootSessionId,
|
|
68
|
+
runtimeGeneration: task.origin.runtimeGeneration, taskId: task.origin.taskId, uuid,
|
|
69
|
+
state: task.state, live: task.live, stale: task.stale,
|
|
70
|
+
...(task.taskType ? { taskType: task.taskType } : {}),
|
|
71
|
+
...(task.background !== undefined ? { background: task.background } : {}),
|
|
72
|
+
...(task.origin.agentId ? { agentId: task.origin.agentId } : {}),
|
|
73
|
+
...(task.origin.launchingToolUseId ? { launchingToolUseId: task.origin.launchingToolUseId } : {}),
|
|
74
|
+
...(summary !== undefined ? { summary, status: task.state } : {}) }) ?? Promise.resolve();
|
|
75
|
+
}
|
|
76
|
+
queueTask(task, uuid, summary) {
|
|
77
|
+
const pending = this.taskTail.then(() => this.publishTask(task, uuid, summary));
|
|
78
|
+
this.taskTail = pending.catch(() => this.degrade());
|
|
79
|
+
return pending;
|
|
80
|
+
}
|
|
81
|
+
sendUpdate(update) {
|
|
82
|
+
if (this.closed)
|
|
83
|
+
return Promise.resolve();
|
|
84
|
+
if (this.queuedUpdates >= 256) {
|
|
85
|
+
this.degrade();
|
|
86
|
+
return Promise.resolve();
|
|
87
|
+
}
|
|
88
|
+
this.queuedUpdates++;
|
|
89
|
+
const pending = this.updateTail.then(async () => {
|
|
90
|
+
if (!this.closed)
|
|
91
|
+
await this.dependencies.update({ sessionId: this.root, update });
|
|
92
|
+
}).finally(() => { this.queuedUpdates--; });
|
|
93
|
+
this.updateTail = pending.catch(() => { this.degrade(); });
|
|
94
|
+
return pending;
|
|
95
|
+
}
|
|
96
|
+
observe(observation) {
|
|
97
|
+
if (this.closed)
|
|
98
|
+
return;
|
|
99
|
+
if (!this.profileState?.rootConfirmed) {
|
|
100
|
+
if (this.earlyObservations.length >= 64)
|
|
101
|
+
this.degrade();
|
|
102
|
+
else
|
|
103
|
+
this.earlyObservations.push(observation);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (observation.origin)
|
|
107
|
+
this.mapper?.observe(observation.origin);
|
|
108
|
+
if (observation.launch) {
|
|
109
|
+
this.mapper?.observe(observation.launch);
|
|
110
|
+
if (observation.launch.launchingToolUseId && observation.launch.agentId)
|
|
111
|
+
for (const task of this.taskLedger?.observeLaunch(observation.launch.launchingToolUseId, observation.launch.agentId) ?? [])
|
|
112
|
+
void this.queueTask(task, `launch-${observation.launch.launchingToolUseId}`).catch(() => this.degrade());
|
|
113
|
+
}
|
|
114
|
+
if (observation.conflict)
|
|
115
|
+
this.mapper?.conflict(observation.conflict);
|
|
116
|
+
if (this.profileState)
|
|
117
|
+
this.profileState.observerHealthy = !this.degraded && this.receiver?.healthy === true;
|
|
118
|
+
void this.publishProfile().catch(() => { });
|
|
119
|
+
}
|
|
120
|
+
async newSession(request) {
|
|
121
|
+
if (this.root || this.closed)
|
|
122
|
+
throw new Error('Session already opened or closed');
|
|
123
|
+
if (!isAbsolute(request.cwd))
|
|
124
|
+
throw new Error('Session cwd must be absolute');
|
|
125
|
+
if (request.mcpServers.length)
|
|
126
|
+
throw new Error('MCP server mapping is not supported');
|
|
127
|
+
this.root = randomUUID();
|
|
128
|
+
this.cwd = request.cwd;
|
|
129
|
+
const generation = randomUUID();
|
|
130
|
+
this.taskLedger = new TaskLedger(this.root, generation);
|
|
131
|
+
this.profileState = { cliVersion: this.dependencies.driver.cliVersion, sdkVersion: this.dependencies.driver.sdkVersion,
|
|
132
|
+
executable: this.dependencies.driver.executable, persistent: true, mode: 'sdk-adapter', agentMessaging: false,
|
|
133
|
+
transport: 'process', capabilities: [], observerHealthy: false, completedContinuationEnabled: false,
|
|
134
|
+
sendMessageExposed: false, initialized: false, rootConfirmed: false, platform: process.platform,
|
|
135
|
+
runtimeSource: 'explicit-local', rootSessionId: this.root, runtimeGeneration: generation };
|
|
136
|
+
try {
|
|
137
|
+
const plugin = await createObserverPlugin(this.dependencies.runDir, { rootSessionId: this.root,
|
|
138
|
+
runtimeGeneration: generation, token: randomUUID() });
|
|
139
|
+
this.mapper = new QoderMessageMapper({ rootSessionId: this.root, runtimeGeneration: generation }, {
|
|
140
|
+
correction: (update) => { void this.sendUpdate(update).catch(() => { }); }, degraded: () => this.degrade()
|
|
141
|
+
});
|
|
142
|
+
this.receiver = new ObserverReceiver(plugin, { onObservation: (observation) => this.observe(observation), onDegraded: () => this.degrade() });
|
|
143
|
+
this.permissions = new PermissionBridge({ rootSessionId: this.root, runtimeGeneration: generation,
|
|
144
|
+
request: this.dependencies.permission ?? (async () => ({ outcome: { outcome: 'cancelled' } })) });
|
|
145
|
+
this.query = this.dependencies.driver.create({ prompt: this.input, options: { cwd: request.cwd,
|
|
146
|
+
sessionId: this.root, plugins: [{ type: 'local', path: plugin.pluginDir }],
|
|
147
|
+
canUseTool: this.permissions.canUseTool, onElicitation: this.permissions.onElicitation } });
|
|
148
|
+
this.taskControl = new TaskControl(this.taskLedger, () => this.currentProfile, this.query, this.dependencies.certifications ?? [], this.dependencies.backendBinding);
|
|
149
|
+
this.pump = this.readMessages();
|
|
150
|
+
const initialized = await this.query.initializationResult();
|
|
151
|
+
if (this.closed)
|
|
152
|
+
throw new Error('Session closed during initialization');
|
|
153
|
+
this.profileState.initialized = true;
|
|
154
|
+
this.profileState.capabilities = initialized.capabilities ?? [];
|
|
155
|
+
await this.publishProfile();
|
|
156
|
+
this.receiver.start();
|
|
157
|
+
return { sessionId: this.root, _meta: { qoderProfile: this.currentProfile } };
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
await this.close();
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async readMessages() {
|
|
165
|
+
try {
|
|
166
|
+
for await (const message of this.query) {
|
|
167
|
+
if (this.closed)
|
|
168
|
+
break;
|
|
169
|
+
if (message.type === 'system' && message.subtype === 'init') {
|
|
170
|
+
if (message.session_id !== this.root || message.cwd !== this.cwd ||
|
|
171
|
+
message.qodercli_version !== this.dependencies.driver.cliVersion ||
|
|
172
|
+
message.protocol_version !== '1.5.0')
|
|
173
|
+
throw new Error('SDK root identity/profile mismatch');
|
|
174
|
+
this.profileState.rootConfirmed = true;
|
|
175
|
+
this.profileState.protocolVersion = 1;
|
|
176
|
+
for (const observation of this.earlyObservations.splice(0))
|
|
177
|
+
this.observe(observation);
|
|
178
|
+
await this.publishProfile();
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!this.profileState?.rootConfirmed || message.session_id !== this.root)
|
|
182
|
+
continue;
|
|
183
|
+
await this.receive(message);
|
|
184
|
+
}
|
|
185
|
+
if (!this.closed)
|
|
186
|
+
throw new Error('SDK stream ended');
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
this.active?.reject(error instanceof Error ? error : new Error('SDK transport failed'));
|
|
190
|
+
this.active = undefined;
|
|
191
|
+
if (this.profileState)
|
|
192
|
+
this.profileState.rootConfirmed = false;
|
|
193
|
+
void this.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async receive(message) {
|
|
197
|
+
if (message.type === 'result') {
|
|
198
|
+
if (this.resultIds.has(message.uuid))
|
|
199
|
+
return;
|
|
200
|
+
if (this.resultIds.size >= 4096)
|
|
201
|
+
throw new Error('SDK result identity capacity exceeded');
|
|
202
|
+
this.resultIds.add(message.uuid);
|
|
203
|
+
if (message.origin !== undefined && message.origin.kind !== 'human')
|
|
204
|
+
return;
|
|
205
|
+
const active = this.active;
|
|
206
|
+
if (message.subtype === 'error_during_execution' &&
|
|
207
|
+
(message.terminal_reason === 'aborted_streaming' || message.terminal_reason === 'aborted_tools') &&
|
|
208
|
+
active?.steeringPending) {
|
|
209
|
+
// Qoder reports the interrupted prefix as a terminal error before it
|
|
210
|
+
// starts the injected `now` message. The 1.1.61 SDK uses
|
|
211
|
+
// `aborted_tools` when the current foreground tool is cancelled,
|
|
212
|
+
// while newer builds may use `aborted_streaming`. Both are an
|
|
213
|
+
// internal hand-off, not the host prompt's final result.
|
|
214
|
+
active.steeringPending--;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.active = undefined;
|
|
218
|
+
if (message.is_error || message.subtype !== 'success')
|
|
219
|
+
active?.reject(new Error(`SDK result error: ${message.error_code ?? message.subtype}`));
|
|
220
|
+
else
|
|
221
|
+
active?.resolve({ stopReason: message.terminal_reason === 'interrupted' ? 'cancelled' : 'end_turn' });
|
|
222
|
+
}
|
|
223
|
+
else if (message.type === 'system' && ['task_started', 'task_progress', 'task_updated',
|
|
224
|
+
'background_tasks_changed', 'task_notification'].includes(message.subtype)) {
|
|
225
|
+
const changed = this.taskLedger?.apply(message) ?? [];
|
|
226
|
+
if (this.taskLedger?.degraded)
|
|
227
|
+
this.degrade();
|
|
228
|
+
for (const task of changed)
|
|
229
|
+
await this.queueTask(task, message.uuid, message.subtype === 'task_notification' && task.origin.taskId === message.task_id
|
|
230
|
+
? message.summary.slice(0, 8192) : undefined);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
for (const update of this.mapper?.map(message, this.active?.runId) ?? [])
|
|
234
|
+
await this.sendUpdate(update);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async prompt(request) {
|
|
238
|
+
if (this.closed || !this.query || request.sessionId !== this.root || !this.profileState?.initialized)
|
|
239
|
+
throw new Error('Session unavailable');
|
|
240
|
+
if (this.active)
|
|
241
|
+
throw new Error('Session prompt already active');
|
|
242
|
+
if (request.prompt.some((block) => block.type !== 'text'))
|
|
243
|
+
throw new Error('Prompt media/resource mapping is unsupported');
|
|
244
|
+
const text = request.prompt.map((block) => block.type === 'text' ? block.text : '').join('\n');
|
|
245
|
+
if (Buffer.byteLength(text, 'utf8') > 131072)
|
|
246
|
+
throw new Error('Prompt capacity exceeded');
|
|
247
|
+
return new Promise((resolve, reject) => {
|
|
248
|
+
const runId = randomUUID();
|
|
249
|
+
this.active = { runId, resolve, reject, steeringPending: 0 };
|
|
250
|
+
try {
|
|
251
|
+
this.input.push({ type: 'user', uuid: runId, session_id: this.root,
|
|
252
|
+
parent_tool_use_id: null, message: { role: 'user', content: text } });
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
this.active = undefined;
|
|
256
|
+
reject(error);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
async steer(blocks, priority) {
|
|
261
|
+
if (this.closed || !this.query || !this.profileState?.initialized || !this.profileState.rootConfirmed ||
|
|
262
|
+
!supportsQoderSdkSteering(this.dependencies.driver.cliVersion) || !this.active)
|
|
263
|
+
return 'promptRequired';
|
|
264
|
+
if (blocks.some((block) => block.type !== 'text'))
|
|
265
|
+
throw new Error('Steering media/resource mapping is unsupported');
|
|
266
|
+
const text = blocks.map((block) => block.type === 'text' ? block.text : '').join('\n');
|
|
267
|
+
if (!text.trim())
|
|
268
|
+
throw new Error('Steering prompt must contain text');
|
|
269
|
+
if (Buffer.byteLength(text, 'utf8') > 131072)
|
|
270
|
+
throw new Error('Steering capacity exceeded');
|
|
271
|
+
const active = this.active;
|
|
272
|
+
active.steeringPending++;
|
|
273
|
+
try {
|
|
274
|
+
this.input.push({ type: 'user', uuid: randomUUID(), session_id: this.root,
|
|
275
|
+
parent_tool_use_id: null, origin: { kind: 'human' }, shouldQuery: true,
|
|
276
|
+
...(priority === undefined ? {} : { priority }), message: { role: 'user', content: text } });
|
|
277
|
+
return 'injected';
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
active.steeringPending--;
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async cancel() { if (!this.closed && this.query)
|
|
285
|
+
await this.query.interrupt(); }
|
|
286
|
+
async stopTask(request) {
|
|
287
|
+
if (this.closed || !this.taskControl)
|
|
288
|
+
throw new Error('Task stop unavailable');
|
|
289
|
+
return this.taskControl.stop(request);
|
|
290
|
+
}
|
|
291
|
+
close() {
|
|
292
|
+
if (this.shutdown)
|
|
293
|
+
return this.shutdown;
|
|
294
|
+
this.closed = true;
|
|
295
|
+
this.input.close();
|
|
296
|
+
for (const task of this.taskLedger?.markStale() ?? [])
|
|
297
|
+
void this.queueTask(task, `closed-${this.profileState?.runtimeGeneration}`).catch(() => { });
|
|
298
|
+
this.permissions?.close();
|
|
299
|
+
this.active?.reject(new Error('Session closed'));
|
|
300
|
+
this.active = undefined;
|
|
301
|
+
this.shutdown = (async () => {
|
|
302
|
+
try {
|
|
303
|
+
await this.query?.close();
|
|
304
|
+
}
|
|
305
|
+
finally {
|
|
306
|
+
await this.pump;
|
|
307
|
+
await this.updateTail;
|
|
308
|
+
await this.taskTail;
|
|
309
|
+
await this.receiver?.dispose();
|
|
310
|
+
this.mapper?.dispose();
|
|
311
|
+
if (this.profileState) {
|
|
312
|
+
this.profileState.observerHealthy = false;
|
|
313
|
+
await this.publishProfile();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
})();
|
|
317
|
+
return this.shutdown;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const QODER_SUBAGENT_META_VERSION = 1;
|
|
2
|
+
export function qoderSubagentMeta(value) {
|
|
3
|
+
return { qoder: { version: QODER_SUBAGENT_META_VERSION, ...value } };
|
|
4
|
+
}
|
|
5
|
+
export function qoderChildMeta(parentToolUseId, origin, extra = { source: 'sdk' }) {
|
|
6
|
+
return qoderSubagentMeta({
|
|
7
|
+
...extra,
|
|
8
|
+
parentToolUseId,
|
|
9
|
+
route: 'child',
|
|
10
|
+
...(origin?.agentId ? { agentId: origin.agentId } : {}),
|
|
11
|
+
...(origin?.launchingToolUseId ? { launchingToolUseId: origin.launchingToolUseId } : {}),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function qoderSpawnMeta(toolUseId) {
|
|
15
|
+
return qoderSubagentMeta({ source: 'sdk', toolName: 'Agent', subagent: true, toolUseId });
|
|
16
|
+
}
|
|
17
|
+
export function qoderCorrectionMeta(source, origin) {
|
|
18
|
+
const base = {
|
|
19
|
+
source: 'correction', toolUseId: source.toolUseId,
|
|
20
|
+
...(origin?.agentId ? { agentId: origin.agentId } : {}),
|
|
21
|
+
...(origin?.launchingToolUseId ? { launchingToolUseId: origin.launchingToolUseId } : {}),
|
|
22
|
+
};
|
|
23
|
+
if (source.parentToolUseId && origin)
|
|
24
|
+
return qoderSubagentMeta({
|
|
25
|
+
...base, parentToolUseId: source.parentToolUseId, route: 'child',
|
|
26
|
+
});
|
|
27
|
+
return qoderSubagentMeta({ ...base, ...(origin ? {} : { route: 'unknown' }) });
|
|
28
|
+
}
|