@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,372 @@
|
|
|
1
|
+
export const MAX_TASKS = 512;
|
|
2
|
+
export const MAX_RESULT = 128 * 1024;
|
|
3
|
+
export const MAX_RESULTS = 4 * 1024 * 1024;
|
|
4
|
+
/** Child transcript rows remembered per background Agent (`BackgroundProcess`' `seen` bound). */
|
|
5
|
+
export const CHILD_ROW_LIMIT = 4096;
|
|
6
|
+
const TERMINALS = new Set(['completed', 'failed', 'cancelled']);
|
|
7
|
+
export function object(value) {
|
|
8
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
9
|
+
}
|
|
10
|
+
export function address(value) {
|
|
11
|
+
return typeof value === 'string' && /^[a-zA-Z0-9_-]{1,160}$/.test(value) ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
function own(record, key) {
|
|
14
|
+
return Object.hasOwn(record, key) ? record[key] : undefined;
|
|
15
|
+
}
|
|
16
|
+
function put(record, key, value) {
|
|
17
|
+
Object.defineProperty(record, key, { value, enumerable: true, writable: true, configurable: true });
|
|
18
|
+
}
|
|
19
|
+
/** UTF-8 length of a string, as `Buffer.byteLength` counts it (a lone surrogate is 3 bytes). */
|
|
20
|
+
export function utf8Length(text) {
|
|
21
|
+
let bytes = 0;
|
|
22
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
23
|
+
const code = text.charCodeAt(index);
|
|
24
|
+
if (code < 0x80)
|
|
25
|
+
bytes += 1;
|
|
26
|
+
else if (code < 0x800)
|
|
27
|
+
bytes += 2;
|
|
28
|
+
else if (code >= 0xd800 && code <= 0xdbff && index + 1 < text.length
|
|
29
|
+
&& text.charCodeAt(index + 1) >= 0xdc00 && text.charCodeAt(index + 1) <= 0xdfff) {
|
|
30
|
+
bytes += 4;
|
|
31
|
+
index += 1;
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
bytes += 3;
|
|
35
|
+
}
|
|
36
|
+
return bytes;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The facts in one root transcript row — `BackgroundTerminalRepair.consume` up to the point where it
|
|
40
|
+
* consulted its own memory (that half is {@link TerminalRepairFold.apply}).
|
|
41
|
+
*/
|
|
42
|
+
export function rootRowFacts(row, context) {
|
|
43
|
+
if (row.sessionId !== context.nativeSessionId || row.cwd !== context.cwd || row.isSidechain !== false)
|
|
44
|
+
return [];
|
|
45
|
+
const content = object(row.message).content;
|
|
46
|
+
if (!Array.isArray(content))
|
|
47
|
+
return [];
|
|
48
|
+
const facts = [];
|
|
49
|
+
// resume deliberately sends no historical ACP openings. Recover only certified root Agent
|
|
50
|
+
// background inputs; no opening is emitted and the existing exact-ID notification rules apply.
|
|
51
|
+
if (context.restoreCalls && row.type === 'assistant') {
|
|
52
|
+
for (const block of content.map(object)) {
|
|
53
|
+
const input = object(block.input), id = address(block.id);
|
|
54
|
+
if (block.type === 'tool_use' && block.name === 'Agent' && id && input.run_in_background === true &&
|
|
55
|
+
typeof input.prompt === 'string' && (input.subagent_type == null || typeof input.subagent_type === 'string')) {
|
|
56
|
+
facts.push({ kind: 'call', toolUseId: id, input });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return facts;
|
|
60
|
+
}
|
|
61
|
+
if (row.type !== 'user')
|
|
62
|
+
return facts;
|
|
63
|
+
const output = object(row.toolUseResult);
|
|
64
|
+
if (output.isAsync === true && output.status === 'async_launched') {
|
|
65
|
+
const results = content.map(object).filter(c => c.type === 'tool_result');
|
|
66
|
+
const id = results.length === 1 ? address(results[0].tool_use_id) : undefined;
|
|
67
|
+
const agent = address(output.agentId);
|
|
68
|
+
if (id)
|
|
69
|
+
facts.push({ kind: 'launch', toolUseId: id, ...(agent ? { agentId: agent } : {}) });
|
|
70
|
+
return facts;
|
|
71
|
+
}
|
|
72
|
+
if (row.isMeta !== true || object(row.origin).kind !== 'task-notification')
|
|
73
|
+
return facts;
|
|
74
|
+
for (const block of content.map(object)) {
|
|
75
|
+
if (block.type !== 'text' || typeof block.text !== 'string' ||
|
|
76
|
+
!block.text.startsWith('[SYSTEM NOTIFICATION - NOT USER INPUT]\n'))
|
|
77
|
+
continue;
|
|
78
|
+
// Match the authoritative envelope before result text; tags inside a result cannot change IDs.
|
|
79
|
+
const match = block.text.match(/<task-notification>\n<task-id>([a-zA-Z0-9_-]{1,160})<\/task-id>\n<tool-use-id>([a-zA-Z0-9_-]{1,160})<\/tool-use-id>\n(?:<output-file>[^\n]*<\/output-file>\n)?<status>(completed|failed|cancelled)<\/status>\n<summary>[^\n]*<\/summary>\n<result>([\s\S]*)<\/result>\n(?:<usage>[^\n]*<\/usage>\n)?<\/task-notification>\s*$/);
|
|
80
|
+
if (match && utf8Length(match[4]) <= MAX_RESULT) {
|
|
81
|
+
facts.push({ kind: 'terminal', toolUseId: match[2], taskId: match[1], state: match[3], result: match[4] });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return facts;
|
|
85
|
+
}
|
|
86
|
+
export function newTerminalRepairData(options) {
|
|
87
|
+
return {
|
|
88
|
+
nativeSessionId: options.nativeSessionId, restoreCalls: options.restoreCalls === true, includeProcess: options.includeProcess === true,
|
|
89
|
+
calls: {}, callCount: 0, launches: [], terminals: [], delivered: {}, pendingProcesses: {}, resultBytes: 0, childRows: {}, polledProcesses: {},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export class TerminalRepairFold {
|
|
93
|
+
data;
|
|
94
|
+
constructor(data) {
|
|
95
|
+
this.data = data;
|
|
96
|
+
}
|
|
97
|
+
call(id) { return own(this.data.calls, id); }
|
|
98
|
+
agentOf(id) { return this.data.launches.find(([toolUseId]) => toolUseId === id)?.[1]; }
|
|
99
|
+
/** Every ACP event the session routes or emits (live or load history): learns the certified Agent calls. */
|
|
100
|
+
observe(event) {
|
|
101
|
+
if ((event.acpSessionId && event.acpSessionId !== this.data.nativeSessionId) ||
|
|
102
|
+
!['tool_call', 'tool_call_update'].includes(event.eventName))
|
|
103
|
+
return;
|
|
104
|
+
const payload = event.payload;
|
|
105
|
+
const id = address(payload.toolCallId);
|
|
106
|
+
if (!id)
|
|
107
|
+
return;
|
|
108
|
+
let call = this.call(id);
|
|
109
|
+
const input = object(payload.rawInput);
|
|
110
|
+
if (!call && input.run_in_background === true && typeof input.prompt === 'string' &&
|
|
111
|
+
(input.subagent_type == null || typeof input.subagent_type === 'string') && payload.kind === 'think' &&
|
|
112
|
+
object(object(payload._meta).qoder).toolName === 'Agent' && this.data.callCount < MAX_TASKS) {
|
|
113
|
+
const meta = object(object(payload._meta).qoder);
|
|
114
|
+
call = { input, invocationComplete: false, nativeTerminal: false,
|
|
115
|
+
process: this.data.includeProcess && meta.version === 1 && meta.source === 'native-acp-patch' };
|
|
116
|
+
put(this.data.calls, id, call);
|
|
117
|
+
this.data.callCount += 1;
|
|
118
|
+
}
|
|
119
|
+
if (!call)
|
|
120
|
+
return;
|
|
121
|
+
if (payload.status === 'completed')
|
|
122
|
+
call.invocationComplete = true;
|
|
123
|
+
const output = object(payload.rawOutput);
|
|
124
|
+
if (output.kind === 'agent-result' && (TERMINALS.has(String(output.state)) || output.state === 'error')) {
|
|
125
|
+
call.nativeTerminal = true; // Upstream's structured evidence always wins.
|
|
126
|
+
// Never published now: its input, child rows and status are dead (checkpointed state).
|
|
127
|
+
this.forget(id, call);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** A known call has no result yet: the reader keeps polling the root transcript. */
|
|
131
|
+
active() {
|
|
132
|
+
return Object.entries(this.data.calls).some(([id, call]) => !Object.hasOwn(this.data.delivered, id) && !call.nativeTerminal);
|
|
133
|
+
}
|
|
134
|
+
/** This call's child transcript is still followed. */
|
|
135
|
+
wantsProcess(toolUseId) {
|
|
136
|
+
const call = this.call(toolUseId);
|
|
137
|
+
return call !== undefined && call.process && !Object.hasOwn(this.data.delivered, toolUseId) && !call.nativeTerminal;
|
|
138
|
+
}
|
|
139
|
+
/** Fold one fact; returns the ACP events to emit now (a child row's, or the results a poll's end publishes). */
|
|
140
|
+
apply(fact) {
|
|
141
|
+
const data = this.data;
|
|
142
|
+
switch (fact.kind) {
|
|
143
|
+
case 'call':
|
|
144
|
+
if (data.restoreCalls && !this.call(fact.toolUseId) && data.callCount < MAX_TASKS) {
|
|
145
|
+
put(data.calls, fact.toolUseId, { input: fact.input, invocationComplete: false, nativeTerminal: false, process: false });
|
|
146
|
+
data.callCount += 1;
|
|
147
|
+
}
|
|
148
|
+
return [];
|
|
149
|
+
case 'launch': {
|
|
150
|
+
const call = this.call(fact.toolUseId);
|
|
151
|
+
if (data.restoreCalls && call)
|
|
152
|
+
call.invocationComplete = true;
|
|
153
|
+
if (fact.agentId && data.launches.length < MAX_TASKS && this.agentOf(fact.toolUseId) === undefined) {
|
|
154
|
+
data.launches.push([fact.toolUseId, fact.agentId]);
|
|
155
|
+
}
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
case 'terminal': {
|
|
159
|
+
const bytes = utf8Length(fact.result);
|
|
160
|
+
if (bytes <= MAX_RESULT && data.resultBytes + bytes <= MAX_RESULTS && data.terminals.length < MAX_TASKS &&
|
|
161
|
+
!data.terminals.some((terminal) => terminal.toolUseId === fact.toolUseId)) {
|
|
162
|
+
data.resultBytes += bytes;
|
|
163
|
+
data.terminals.push({ toolUseId: fact.toolUseId, taskId: fact.taskId, state: fact.state, result: fact.result });
|
|
164
|
+
}
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
case 'child': {
|
|
168
|
+
// Only a transcript the repair still follows (the reader asked the same question before reading).
|
|
169
|
+
if (!this.wantsProcess(fact.toolUseId) || this.agentOf(fact.toolUseId) === undefined)
|
|
170
|
+
return [];
|
|
171
|
+
let seen = own(data.childRows, fact.toolUseId);
|
|
172
|
+
if (!seen)
|
|
173
|
+
put(data.childRows, fact.toolUseId, seen = []);
|
|
174
|
+
if (seen.includes(fact.uuid) || seen.length >= CHILD_ROW_LIMIT)
|
|
175
|
+
return [];
|
|
176
|
+
seen.push(fact.uuid);
|
|
177
|
+
return [...fact.events];
|
|
178
|
+
}
|
|
179
|
+
case 'child_status':
|
|
180
|
+
if (!this.wantsProcess(fact.toolUseId) || this.agentOf(fact.toolUseId) === undefined)
|
|
181
|
+
return [];
|
|
182
|
+
if (fact.caughtUp)
|
|
183
|
+
delete data.pendingProcesses[fact.toolUseId];
|
|
184
|
+
else
|
|
185
|
+
put(data.pendingProcesses, fact.toolUseId, true);
|
|
186
|
+
put(data.polledProcesses, fact.toolUseId, true);
|
|
187
|
+
return [];
|
|
188
|
+
case 'poll_end':
|
|
189
|
+
return this.publish();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
publish() {
|
|
193
|
+
const data = this.data;
|
|
194
|
+
const events = [];
|
|
195
|
+
for (const terminal of data.terminals) {
|
|
196
|
+
const id = terminal.toolUseId;
|
|
197
|
+
const call = this.call(id), agentId = this.agentOf(id);
|
|
198
|
+
if (call?.nativeTerminal)
|
|
199
|
+
terminal.result = '';
|
|
200
|
+
if (Object.hasOwn(data.delivered, id) || Object.hasOwn(data.pendingProcesses, id) || !call?.invocationComplete ||
|
|
201
|
+
call.nativeTerminal || !agentId || (call.process && !Object.hasOwn(data.polledProcesses, id)))
|
|
202
|
+
continue;
|
|
203
|
+
put(data.delivered, id, true);
|
|
204
|
+
events.push({ eventName: 'tool_call_update', payload: {
|
|
205
|
+
name: 'Agent', kind: 'think', toolCallId: id, rawInput: call.input, status: 'completed',
|
|
206
|
+
rawOutput: { kind: 'agent-result', state: terminal.state, agentId, result: terminal.result },
|
|
207
|
+
content: [{ type: 'content', content: { type: 'text', text: terminal.result } }],
|
|
208
|
+
_meta: { qoder: { toolName: 'Agent' }, axomNativeTerminalRepair: {
|
|
209
|
+
patch: 'qoder-background-terminal', nativeSessionId: data.nativeSessionId,
|
|
210
|
+
taskId: terminal.taskId, source: 'native-task-notification',
|
|
211
|
+
} },
|
|
212
|
+
} });
|
|
213
|
+
// Delivered results are never published again; their text need not stay in a checkpoint.
|
|
214
|
+
terminal.result = '';
|
|
215
|
+
this.forget(id, call);
|
|
216
|
+
}
|
|
217
|
+
return events;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* A delivered or natively finished call is never published and its child never followed again
|
|
221
|
+
* (`wantsProcess` is false for good), so what only those two read goes: the input (read by
|
|
222
|
+
* `publish` alone), the child rows' uuids and status. The call entry itself stays — `observe` must
|
|
223
|
+
* not register the same id afresh — and so do the launch and result entries, whose presence is what
|
|
224
|
+
* keeps a restarted reader's repeats from being taken as new.
|
|
225
|
+
*/
|
|
226
|
+
forget(id, call) {
|
|
227
|
+
call.input = {};
|
|
228
|
+
delete this.data.childRows[id];
|
|
229
|
+
delete this.data.pendingProcesses[id];
|
|
230
|
+
delete this.data.polledProcesses[id];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// ---- notes ------------------------------------------------------------------------------------
|
|
234
|
+
/**
|
|
235
|
+
* The note kinds (spec §5.4: "Qoder 侧文件推导出的事件" enter the log as notes):
|
|
236
|
+
* - `qoder_terminal_repair{nativeSessionId, restoreCalls?}` — the daemon enabled the certified repair
|
|
237
|
+
* for this session (where today's path calls `enableBackgroundTerminalRepair`).
|
|
238
|
+
* - `qoder_side{fact}`, or for a fact too large for one note, consecutive
|
|
239
|
+
* `qoder_side{chunk: {index, count, text}}` notes whose `text`s concatenate to the fact's JSON.
|
|
240
|
+
*/
|
|
241
|
+
export const REPAIR_NOTE = 'qoder_terminal_repair';
|
|
242
|
+
export const SIDE_NOTE = 'qoder_side';
|
|
243
|
+
/** Equal to the processor's `NOTE_MAX_BYTES` (asserted in a test; this module does not import the processor). */
|
|
244
|
+
export const SIDE_NOTE_MAX_BYTES = 64 * 1024;
|
|
245
|
+
/**
|
|
246
|
+
* A fact larger than this (serialized) is not recorded (the reader counts it). A child row's line is
|
|
247
|
+
* ≤ 2 MiB and re-serializes no larger, but its text can appear twice (`rawOutput` and `content`), so a
|
|
248
|
+
* fact stays ≤ ~4 MiB; 8 MiB keeps every fact the live driver would have emitted. Its chunk buffer can be
|
|
249
|
+
* that large while a chunked fact is half-read: T19 does not cut a checkpoint then (`sideChunk !== null`).
|
|
250
|
+
*/
|
|
251
|
+
export const MAX_SIDE_FACT_BYTES = 8 * 1024 * 1024;
|
|
252
|
+
/** Enough for a {@link MAX_SIDE_FACT_BYTES} fact even when chunking doubles it (escaped quotes). */
|
|
253
|
+
export const MAX_SIDE_CHUNKS = 512;
|
|
254
|
+
/** Bytes `JSON.stringify` spends on one code unit (or pair) inside a string literal. */
|
|
255
|
+
function escapedBytes(text, index) {
|
|
256
|
+
const code = text.charCodeAt(index);
|
|
257
|
+
if (code === 0x22 || code === 0x5c || code === 0x08 || code === 0x0c || code === 0x0a || code === 0x0d || code === 0x09)
|
|
258
|
+
return { bytes: 2, units: 1 };
|
|
259
|
+
if (code < 0x20)
|
|
260
|
+
return { bytes: 6, units: 1 };
|
|
261
|
+
if (code < 0x80)
|
|
262
|
+
return { bytes: 1, units: 1 };
|
|
263
|
+
if (code < 0x800)
|
|
264
|
+
return { bytes: 2, units: 1 };
|
|
265
|
+
if (code >= 0xd800 && code <= 0xdbff && index + 1 < text.length) {
|
|
266
|
+
const next = text.charCodeAt(index + 1);
|
|
267
|
+
if (next >= 0xdc00 && next <= 0xdfff)
|
|
268
|
+
return { bytes: 4, units: 2 };
|
|
269
|
+
}
|
|
270
|
+
if (code >= 0xd800 && code <= 0xdfff)
|
|
271
|
+
return { bytes: 6, units: 1 };
|
|
272
|
+
return { bytes: 3, units: 1 };
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The notes that record one fact: one `{fact}` note when it fits {@link SIDE_NOTE_MAX_BYTES}, otherwise
|
|
276
|
+
* consecutive `{chunk}` notes, each within the limit. `[]` for a fact over {@link MAX_SIDE_FACT_BYTES}.
|
|
277
|
+
*/
|
|
278
|
+
export function sideNotes(fact) {
|
|
279
|
+
const whole = { type: SIDE_NOTE, fact };
|
|
280
|
+
if (utf8Length(JSON.stringify(whole)) <= SIDE_NOTE_MAX_BYTES)
|
|
281
|
+
return [whole];
|
|
282
|
+
const json = JSON.stringify(fact);
|
|
283
|
+
if (utf8Length(json) > MAX_SIDE_FACT_BYTES)
|
|
284
|
+
return [];
|
|
285
|
+
const overhead = utf8Length(JSON.stringify({ type: SIDE_NOTE, chunk: { index: MAX_SIDE_CHUNKS, count: MAX_SIDE_CHUNKS, text: '' } }));
|
|
286
|
+
const budget = SIDE_NOTE_MAX_BYTES - overhead;
|
|
287
|
+
const pieces = [];
|
|
288
|
+
let start = 0, used = 0;
|
|
289
|
+
for (let index = 0; index < json.length;) {
|
|
290
|
+
const { bytes, units } = escapedBytes(json, index);
|
|
291
|
+
if (used + bytes > budget) {
|
|
292
|
+
pieces.push(json.slice(start, index));
|
|
293
|
+
start = index;
|
|
294
|
+
used = 0;
|
|
295
|
+
}
|
|
296
|
+
used += bytes;
|
|
297
|
+
index += units;
|
|
298
|
+
}
|
|
299
|
+
pieces.push(json.slice(start));
|
|
300
|
+
if (pieces.length > MAX_SIDE_CHUNKS)
|
|
301
|
+
return [];
|
|
302
|
+
return pieces.map((text, index) => ({ type: SIDE_NOTE, chunk: { index, count: pieces.length, text } }));
|
|
303
|
+
}
|
|
304
|
+
function isEvent(value) {
|
|
305
|
+
const event = object(value);
|
|
306
|
+
return value === event && typeof event.eventName === 'string' && object(event.payload) === event.payload
|
|
307
|
+
&& (event.acpSessionId === undefined || typeof event.acpSessionId === 'string');
|
|
308
|
+
}
|
|
309
|
+
/** A fact read back from a note, shape-checked (the log is the daemon's own, but a note is still data). */
|
|
310
|
+
export function parseSideFact(value) {
|
|
311
|
+
const fact = object(value);
|
|
312
|
+
if (value !== fact)
|
|
313
|
+
return undefined;
|
|
314
|
+
const id = address(fact.toolUseId);
|
|
315
|
+
switch (fact.kind) {
|
|
316
|
+
case 'call':
|
|
317
|
+
return id && object(fact.input) === fact.input ? { kind: 'call', toolUseId: id, input: fact.input } : undefined;
|
|
318
|
+
case 'launch':
|
|
319
|
+
if (!id || (fact.agentId !== undefined && address(fact.agentId) === undefined))
|
|
320
|
+
return undefined;
|
|
321
|
+
return { kind: 'launch', toolUseId: id, ...(fact.agentId !== undefined ? { agentId: fact.agentId } : {}) };
|
|
322
|
+
case 'terminal':
|
|
323
|
+
return id && address(fact.taskId) && TERMINALS.has(String(fact.state)) && typeof fact.result === 'string'
|
|
324
|
+
? { kind: 'terminal', toolUseId: id, taskId: fact.taskId, state: fact.state, result: fact.result }
|
|
325
|
+
: undefined;
|
|
326
|
+
case 'child':
|
|
327
|
+
return id && address(fact.uuid) && Array.isArray(fact.events) && fact.events.every(isEvent)
|
|
328
|
+
? { kind: 'child', toolUseId: id, uuid: fact.uuid, events: fact.events }
|
|
329
|
+
: undefined;
|
|
330
|
+
case 'child_status':
|
|
331
|
+
return id && typeof fact.caughtUp === 'boolean' ? { kind: 'child_status', toolUseId: id, caughtUp: fact.caughtUp } : undefined;
|
|
332
|
+
case 'poll_end':
|
|
333
|
+
return { kind: 'poll_end' };
|
|
334
|
+
default:
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Read one `qoder_side` note against the reassembly buffer. A `{fact}` note (or a chunk with index 0)
|
|
340
|
+
* abandons any half-read chunked fact — a reader that restarted re-reads from the start, so the
|
|
341
|
+
* fact comes again. `malformed` when the note could not be used (shape, order, or JSON).
|
|
342
|
+
*/
|
|
343
|
+
export function readSideNote(buffer, note) {
|
|
344
|
+
if (Object.hasOwn(note, 'fact')) {
|
|
345
|
+
const fact = parseSideFact(note.fact);
|
|
346
|
+
return fact ? { buffer: null, fact, malformed: false } : { buffer: null, malformed: true };
|
|
347
|
+
}
|
|
348
|
+
const chunk = object(note.chunk);
|
|
349
|
+
const { index, count, text } = chunk;
|
|
350
|
+
if (note.chunk !== chunk || !Number.isSafeInteger(index) || !Number.isSafeInteger(count) || typeof text !== 'string'
|
|
351
|
+
|| count < 2 || count > MAX_SIDE_CHUNKS || index < 0 || index >= count) {
|
|
352
|
+
return { buffer, malformed: true };
|
|
353
|
+
}
|
|
354
|
+
let next;
|
|
355
|
+
if (index === 0)
|
|
356
|
+
next = { count: count, next: 1, text };
|
|
357
|
+
else if (buffer && buffer.count === count && buffer.next === index)
|
|
358
|
+
next = { count: buffer.count, next: buffer.next + 1, text: buffer.text + text };
|
|
359
|
+
else
|
|
360
|
+
return { buffer: null, malformed: true };
|
|
361
|
+
if (next.next < next.count)
|
|
362
|
+
return { buffer: next, malformed: false };
|
|
363
|
+
let parsed;
|
|
364
|
+
try {
|
|
365
|
+
parsed = JSON.parse(next.text);
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return { buffer: null, malformed: true };
|
|
369
|
+
}
|
|
370
|
+
const fact = parseSideFact(parsed);
|
|
371
|
+
return fact ? { buffer: null, fact, malformed: false } : { buffer: null, malformed: true };
|
|
372
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** A permission card whose tool call carries the SDK adapter's callback metadata. */
|
|
2
|
+
export function isSdkCallbackCard(toolCall) {
|
|
3
|
+
const meta = toolCall && typeof toolCall === 'object' && !Array.isArray(toolCall)
|
|
4
|
+
? toolCall._meta : undefined;
|
|
5
|
+
return !!meta && typeof meta === 'object' && !Array.isArray(meta) &&
|
|
6
|
+
Object.prototype.hasOwnProperty.call(meta, 'qoderSdk');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The callback snapshot's `connectionGeneration` on the processor path: one ACP session per adapter
|
|
10
|
+
* process, so it derives from the keeper's `spawnId` (spec §5.4), distinct from `hostGeneration`
|
|
11
|
+
* (= the `spawnId` itself).
|
|
12
|
+
*/
|
|
13
|
+
export function connectionGenerationOf(spawnId) {
|
|
14
|
+
return `session:${spawnId}`;
|
|
15
|
+
}
|
|
16
|
+
/** `AcpSession.callbackContextFor`, as a pure function. `undefined` = the card cannot carry a callback. */
|
|
17
|
+
export function permissionCallbackContext(input) {
|
|
18
|
+
const { rootSessionId, diagnostics, requestId, toolCall, pendingRpcRequestId } = input;
|
|
19
|
+
if (input.origin !== rootSessionId)
|
|
20
|
+
return;
|
|
21
|
+
const profile = diagnostics.profile;
|
|
22
|
+
const callbacks = diagnostics.callbacks;
|
|
23
|
+
if (!diagnostics.available || profile?.mode !== 'sdk-adapter' || profile.initialized !== true ||
|
|
24
|
+
profile.rootConfirmed !== true || profile.rootSessionId !== rootSessionId ||
|
|
25
|
+
!profile.runtimeGeneration || callbacks?.complete !== true ||
|
|
26
|
+
callbacks.rootSessionId !== rootSessionId ||
|
|
27
|
+
typeof requestId !== 'string' || pendingRpcRequestId === undefined ||
|
|
28
|
+
!toolCall || typeof toolCall !== 'object' || Array.isArray(toolCall))
|
|
29
|
+
return;
|
|
30
|
+
const call = toolCall;
|
|
31
|
+
const sdkMeta = call._meta && typeof call._meta === 'object' && !Array.isArray(call._meta)
|
|
32
|
+
? call._meta.qoderSdk : undefined;
|
|
33
|
+
if (!sdkMeta || typeof sdkMeta !== 'object' || Array.isArray(sdkMeta))
|
|
34
|
+
return;
|
|
35
|
+
const sdk = sdkMeta;
|
|
36
|
+
const origin = sdk.origin;
|
|
37
|
+
if (sdk.version !== 1 || sdk.blockingScope !== 'root' || sdk.callbackOnly !== true ||
|
|
38
|
+
typeof sdk.requestId !== 'string' || !/^[0-9a-f-]{36}$/i.test(sdk.requestId) ||
|
|
39
|
+
!origin || typeof origin !== 'object' || Array.isArray(origin))
|
|
40
|
+
return;
|
|
41
|
+
const source = origin;
|
|
42
|
+
const toolUseId = call.toolCallId;
|
|
43
|
+
const agentId = source.agentId;
|
|
44
|
+
if (source.rootSessionId !== rootSessionId || source.runtimeGeneration !== profile.runtimeGeneration ||
|
|
45
|
+
typeof toolUseId !== 'string' || source.toolUseId !== toolUseId ||
|
|
46
|
+
!toolUseId || utf8Length(toolUseId) > 256 ||
|
|
47
|
+
(agentId !== undefined && (typeof agentId !== 'string' || !agentId || utf8Length(agentId) > 256)))
|
|
48
|
+
return;
|
|
49
|
+
const pending = callbacks.pending.find((entry) => entry.requestId === requestId &&
|
|
50
|
+
entry.originNativeSessionId === rootSessionId);
|
|
51
|
+
if (!pending || callbacks.pending.length > 64 || pendingRpcRequestId !== pending.rpcRequestId)
|
|
52
|
+
return;
|
|
53
|
+
return { rootSessionId, hostGeneration: diagnostics.generation,
|
|
54
|
+
connectionGeneration: callbacks.connectionGeneration, requestId,
|
|
55
|
+
runtimeGeneration: profile.runtimeGeneration, toolUseId,
|
|
56
|
+
...(agentId ? { agentId } : {}), independent: false };
|
|
57
|
+
}
|
|
58
|
+
/** `Buffer.byteLength(text, 'utf8')` in plain JS (a lone surrogate is 3 bytes). */
|
|
59
|
+
function utf8Length(text) {
|
|
60
|
+
let bytes = 0;
|
|
61
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
62
|
+
const code = text.charCodeAt(index);
|
|
63
|
+
if (code < 0x80)
|
|
64
|
+
bytes += 1;
|
|
65
|
+
else if (code < 0x800)
|
|
66
|
+
bytes += 2;
|
|
67
|
+
else if (code >= 0xd800 && code <= 0xdbff && index + 1 < text.length
|
|
68
|
+
&& text.charCodeAt(index + 1) >= 0xdc00 && text.charCodeAt(index + 1) <= 0xdfff) {
|
|
69
|
+
bytes += 4;
|
|
70
|
+
index += 1;
|
|
71
|
+
}
|
|
72
|
+
else
|
|
73
|
+
bytes += 3;
|
|
74
|
+
}
|
|
75
|
+
return bytes;
|
|
76
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { capabilitiesFor, QODER_CERTIFICATIONS } from './profile.js';
|
|
2
|
+
const MAX_ID_BYTES = 256;
|
|
3
|
+
const MAX_MESSAGE_BYTES = 131072;
|
|
4
|
+
const MAX_RECORDS = 4096;
|
|
5
|
+
const states = new Set(['pending', 'paused', 'running', 'completed', 'stopped', 'failed', 'unknown']);
|
|
6
|
+
const id = (value) => typeof value === 'string' && value.trim().length > 0 &&
|
|
7
|
+
Buffer.byteLength(value, 'utf8') <= MAX_ID_BYTES && !/[\x00-\x1f\x7f]/.test(value);
|
|
8
|
+
const message = (value) => typeof value === 'string' &&
|
|
9
|
+
Buffer.byteLength(value, 'utf8') <= MAX_MESSAGE_BYTES;
|
|
10
|
+
function exactIdentity(value) {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
12
|
+
return false;
|
|
13
|
+
const input = value;
|
|
14
|
+
return ['rootSessionId', 'runtimeGeneration', 'sourceTaskId', 'agentId', 'requestId', 'toolUseId']
|
|
15
|
+
.every((key) => id(input[key]));
|
|
16
|
+
}
|
|
17
|
+
function hasVendorResumeClaim(value) {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
19
|
+
return false;
|
|
20
|
+
if (Object.prototype.hasOwnProperty.call(value, 'resumedAgentId'))
|
|
21
|
+
return true;
|
|
22
|
+
return Object.values(value).some((entry) => hasVendorResumeClaim(entry));
|
|
23
|
+
}
|
|
24
|
+
export function normalizeContinuationEvidence(value) {
|
|
25
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || hasVendorResumeClaim(value))
|
|
26
|
+
return undefined;
|
|
27
|
+
const input = value;
|
|
28
|
+
if (!exactIdentity(input.request) || !exactIdentity(input.sendMessage))
|
|
29
|
+
return undefined;
|
|
30
|
+
if (typeof input.sendMessage.success !== 'boolean')
|
|
31
|
+
return undefined;
|
|
32
|
+
const joined = input.joinedTask;
|
|
33
|
+
if (joined === undefined)
|
|
34
|
+
return {
|
|
35
|
+
request: { ...input.request },
|
|
36
|
+
sendMessage: { ...input.sendMessage,
|
|
37
|
+
success: input.sendMessage.success },
|
|
38
|
+
};
|
|
39
|
+
if (!joined || typeof joined !== 'object' || Array.isArray(joined))
|
|
40
|
+
return undefined;
|
|
41
|
+
const task = joined;
|
|
42
|
+
const joinedTask = task;
|
|
43
|
+
if (!exactIdentity(task) || !id(joinedTask.taskId) || joinedTask.taskId === joinedTask.sourceTaskId ||
|
|
44
|
+
!states.has(joinedTask.state) || !['pending', 'running', 'completed'].includes(joinedTask.state) ||
|
|
45
|
+
typeof joinedTask.live !== 'boolean' || typeof joinedTask.stale !== 'boolean')
|
|
46
|
+
return undefined;
|
|
47
|
+
return {
|
|
48
|
+
request: { ...input.request },
|
|
49
|
+
sendMessage: { ...input.sendMessage,
|
|
50
|
+
success: input.sendMessage.success },
|
|
51
|
+
joinedTask: { ...joinedTask },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function sameIdentity(a, b) {
|
|
55
|
+
return a.rootSessionId === b.rootSessionId && a.runtimeGeneration === b.runtimeGeneration &&
|
|
56
|
+
a.sourceTaskId === b.sourceTaskId && a.agentId === b.agentId && a.requestId === b.requestId &&
|
|
57
|
+
a.toolUseId === b.toolUseId;
|
|
58
|
+
}
|
|
59
|
+
/** Confirm only an observed, exact new execution. Natural-language claims are intentionally ignored. */
|
|
60
|
+
export function confirmContinuation(expectedAgentId, value) {
|
|
61
|
+
if (!id(expectedAgentId))
|
|
62
|
+
return 'rejected';
|
|
63
|
+
const evidence = normalizeContinuationEvidence(value);
|
|
64
|
+
if (!evidence || !evidence.joinedTask || !evidence.sendMessage.success ||
|
|
65
|
+
evidence.request.agentId !== expectedAgentId || !sameIdentity(evidence.request, evidence.sendMessage) ||
|
|
66
|
+
!sameIdentity(evidence.request, evidence.joinedTask) || evidence.joinedTask.agentId !== expectedAgentId ||
|
|
67
|
+
evidence.joinedTask.stale || !evidence.joinedTask.taskId ||
|
|
68
|
+
evidence.joinedTask.taskId === evidence.request.sourceTaskId)
|
|
69
|
+
return 'rejected';
|
|
70
|
+
return 'confirmed';
|
|
71
|
+
}
|
|
72
|
+
function key(identity) {
|
|
73
|
+
return [identity.rootSessionId, identity.runtimeGeneration, identity.requestId].join('\u0000');
|
|
74
|
+
}
|
|
75
|
+
/** A bounded, request-scoped ledger prevents duplicate confirmations and keeps late proof unknown. */
|
|
76
|
+
export class ContinuationLedger {
|
|
77
|
+
requests = new Map();
|
|
78
|
+
statuses = new Map();
|
|
79
|
+
joinedTaskIds = new Set();
|
|
80
|
+
evidence = new Map();
|
|
81
|
+
request(identity) {
|
|
82
|
+
if (!exactIdentity(identity) || this.requests.size >= MAX_RECORDS)
|
|
83
|
+
return 'rejected';
|
|
84
|
+
const requestKey = key(identity);
|
|
85
|
+
if (this.requests.has(requestKey))
|
|
86
|
+
return 'rejected';
|
|
87
|
+
this.requests.set(requestKey, { ...identity });
|
|
88
|
+
this.statuses.set(requestKey, 'requested');
|
|
89
|
+
return 'requested';
|
|
90
|
+
}
|
|
91
|
+
confirm(expectedAgentId, value) {
|
|
92
|
+
const evidence = normalizeContinuationEvidence(value);
|
|
93
|
+
if (!evidence)
|
|
94
|
+
return 'rejected';
|
|
95
|
+
const requestKey = key(evidence.request);
|
|
96
|
+
const original = this.requests.get(requestKey);
|
|
97
|
+
if (!original || !sameIdentity(original, evidence.request))
|
|
98
|
+
return 'unknown';
|
|
99
|
+
if (!evidence.joinedTask)
|
|
100
|
+
return 'unknown';
|
|
101
|
+
if (this.statuses.get(requestKey) === 'confirmed' || this.joinedTaskIds.has(evidence.joinedTask.taskId)) {
|
|
102
|
+
return 'rejected';
|
|
103
|
+
}
|
|
104
|
+
const result = confirmContinuation(expectedAgentId, evidence);
|
|
105
|
+
if (result === 'confirmed') {
|
|
106
|
+
this.joinedTaskIds.add(evidence.joinedTask.taskId);
|
|
107
|
+
this.statuses.set(requestKey, 'confirmed');
|
|
108
|
+
this.evidence.set(requestKey, evidence);
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
this.statuses.set(requestKey, 'rejected');
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
snapshot() {
|
|
115
|
+
return [...this.evidence.values()].map((entry) => ({
|
|
116
|
+
request: { ...entry.request }, sendMessage: { ...entry.sendMessage },
|
|
117
|
+
...(entry.joinedTask ? { joinedTask: { ...entry.joinedTask } } : {}),
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export async function requestCompletedContinuation(rootSessionId, agentId, text, requestId, options) {
|
|
122
|
+
const rejected = () => ({ status: 'rejected', rootSessionId, agentId, requestId });
|
|
123
|
+
if (!options)
|
|
124
|
+
return rejected();
|
|
125
|
+
const profile = options.profile, task = options.task;
|
|
126
|
+
if (!id(rootSessionId) || !id(agentId) || !id(requestId) || !message(text) || options.enabled !== true ||
|
|
127
|
+
options.busy === true || !profile || !task ||
|
|
128
|
+
task.origin.rootSessionId !== rootSessionId || task.origin.agentId !== agentId ||
|
|
129
|
+
task.origin.runtimeGeneration !== profile.runtimeGeneration ||
|
|
130
|
+
!capabilitiesFor(profile, task, options.certifications ?? QODER_CERTIFICATIONS, options.binding).continueCompleted)
|
|
131
|
+
return rejected();
|
|
132
|
+
try {
|
|
133
|
+
await options.prompt(text);
|
|
134
|
+
return { status: 'requested', rootSessionId, agentId, requestId };
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return rejected();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { parseQoderProfile } from './profile.js';
|
|
3
|
+
/**
|
|
4
|
+
* Native SDK/Hooks capabilities are opt-in until one real process has produced
|
|
5
|
+
* a complete, attributable contract record. The fake fixture must never add a
|
|
6
|
+
* profile to QODER_CERTIFICATIONS by itself.
|
|
7
|
+
*/
|
|
8
|
+
export const QODER_NATIVE_FALLBACKS = Object.freeze({
|
|
9
|
+
model: 'Model changes are negotiated by the base ACP session only.',
|
|
10
|
+
mode: 'Mode changes are negotiated by the base ACP session only.',
|
|
11
|
+
media: 'Prompt media and resources are unsupported by the SDK adapter.',
|
|
12
|
+
forms: 'Form elicitation is unsupported until an equivalent public SDK callback is certified.',
|
|
13
|
+
load: 'Load requires an exact native session proof; it never creates a replacement session.',
|
|
14
|
+
fork: 'Fork is unsupported until an exact new native execution can be proved.',
|
|
15
|
+
});
|
|
16
|
+
export function nativeFallback(feature) {
|
|
17
|
+
return { supported: false, reason: QODER_NATIVE_FALLBACKS[feature] };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parses the deliberately small output contract of an opt-in native runner.
|
|
21
|
+
* This is validation only: callers still decide whether to persist evidence or
|
|
22
|
+
* open a capability, and the shipping certification allowlist remains closed.
|
|
23
|
+
*/
|
|
24
|
+
export function parseNativeContractEvidence(value) {
|
|
25
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
26
|
+
return undefined;
|
|
27
|
+
const input = value;
|
|
28
|
+
const profile = parseQoderProfile(input.profile);
|
|
29
|
+
if (input.certified !== true || !profile || input.tasks !== true || input.hooks !== true ||
|
|
30
|
+
input.approvals !== true || input.terminalControl !== true || input.replayFree !== true ||
|
|
31
|
+
profile.cliVersion !== '1.1.64' || profile.sdkVersion !== '1.0.50' || profile.mode !== 'sdk-adapter' ||
|
|
32
|
+
profile.transport !== 'process' || profile.initialized !== true || profile.rootConfirmed !== true ||
|
|
33
|
+
profile.observerHealthy !== true || !profile.capabilities.includes('background_tasks_v1'))
|
|
34
|
+
return undefined;
|
|
35
|
+
return { certified: true, profile, tasks: true, hooks: true, approvals: true,
|
|
36
|
+
terminalControl: true, replayFree: true };
|
|
37
|
+
}
|
|
38
|
+
const MAX_OUTPUT_BYTES = 128 * 1024;
|
|
39
|
+
const TIMEOUT_MS = 120_000;
|
|
40
|
+
/**
|
|
41
|
+
* Runs an external certification command only when explicitly configured. The
|
|
42
|
+
* command is an executable path (no shell), receives no credentials from this
|
|
43
|
+
* helper, and must print NativeContractEvidence JSON to stdout.
|
|
44
|
+
*/
|
|
45
|
+
export async function runOptInNativeContract(command = process.env.AXOM_QODER_SDK_CONTRACT_COMMAND) {
|
|
46
|
+
if (!command)
|
|
47
|
+
return { status: 'not-run', stdout: '', stderr: '' };
|
|
48
|
+
if (command.includes('\0') || command.trim() !== command || command.length > 4096)
|
|
49
|
+
throw new Error('Invalid native contract command');
|
|
50
|
+
return await new Promise((resolve) => {
|
|
51
|
+
const child = spawn(command, [], { shell: false, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } });
|
|
52
|
+
let stdout = '', stderr = '', oversized = false, settled = false;
|
|
53
|
+
const append = (target, chunk) => {
|
|
54
|
+
if (Buffer.byteLength(target === 'stdout' ? stdout : stderr, 'utf8') + chunk.byteLength > MAX_OUTPUT_BYTES) {
|
|
55
|
+
oversized = true;
|
|
56
|
+
child.kill('SIGTERM');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (target === 'stdout')
|
|
60
|
+
stdout += chunk.toString('utf8');
|
|
61
|
+
else
|
|
62
|
+
stderr += chunk.toString('utf8');
|
|
63
|
+
};
|
|
64
|
+
child.stdout.on('data', (chunk) => append('stdout', chunk));
|
|
65
|
+
child.stderr.on('data', (chunk) => append('stderr', chunk));
|
|
66
|
+
const timer = setTimeout(() => child.kill('SIGTERM'), TIMEOUT_MS);
|
|
67
|
+
const finish = (result) => {
|
|
68
|
+
if (settled)
|
|
69
|
+
return;
|
|
70
|
+
settled = true;
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
resolve(result);
|
|
73
|
+
};
|
|
74
|
+
child.once('error', (error) => finish({ status: 'failed', command, stdout, stderr: `${stderr}${error.message}` }));
|
|
75
|
+
child.once('close', (code) => finish({ status: oversized ? 'failed' : code === 0 ? 'completed' : 'failed',
|
|
76
|
+
command, exitCode: code, stdout, stderr }));
|
|
77
|
+
});
|
|
78
|
+
}
|