@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,459 @@
|
|
|
1
|
+
import { accountLimitsSchema, ACCOUNT_LIMITS_READ, ACCOUNT_LIMITS_UPDATED } from '../../protocol/accountLimits.js';
|
|
2
|
+
import { initializeRequestParams, parseInitializeResult } from '../initializeResponse.js';
|
|
3
|
+
import { STEER_METHOD } from '../steering.js';
|
|
4
|
+
import { checkpointOf } from './checkpoint.js';
|
|
5
|
+
import { failUnsentSteer, onAsyncTaskStopNote, onDelegationRefreshPendingNote, onDelegationRenewedNote, onHistoryEndNote, onInstallOutcomeNote, onOutputGapNote, onLaunch, onOpenNote, onSetConfigOptionNote, onSteerDown, onSteerNote, settleConfig, settleOpen, settleSteer, } from './control.js';
|
|
6
|
+
import { CANCEL_REQUEST_METHOD, classifyLine, inboundKey, invalidParams, invalidRequest, isJsonRpcId, methodNotFound, outboundKey, parseError, settlementErrorCode, settlementOf, } from './jsonrpc.js';
|
|
7
|
+
import { RecordOutput } from './output.js';
|
|
8
|
+
import { INBOUND_REQUEST_SCHEMAS } from './sdkSchemas.js';
|
|
9
|
+
import { BATCH_CLOSE_REASON, connected, dropCards, finishAllTurns, forgetCard, onAnswerNote, onDown, onElicitationAnswerNote, onElicitationRequest, onInboundCancelled, onInterruptNote, onPermissionRequest, onPromptFailedNote, onPromptNote, onUndeliverableNote, resetForSpawn, settlePrompt, } from './session.js';
|
|
10
|
+
import { onClaudeSdkMessage, onInitialized, onProcessExit, onProfileNotification } from './runtime.js';
|
|
11
|
+
import { closeRepair, onRepairNote, onSideNote } from './sideChannel.js';
|
|
12
|
+
import { REPAIR_NOTE, SIDE_NOTE } from '../../qoderAdapter/backgroundTerminalFold.js';
|
|
13
|
+
import { endHistory, onSessionUpdate } from './updates.js';
|
|
14
|
+
import { initialState, SETTLED_HISTORY_LIMIT, } from './state.js';
|
|
15
|
+
/** Serialized size above which a note is dropped (`droppedNotes`). */
|
|
16
|
+
export const NOTE_MAX_BYTES = 64 * 1024;
|
|
17
|
+
/** Methods `rpc.request` refuses: their params carry prompt blocks or secrets (§5.4, §13). */
|
|
18
|
+
export const SEMANTIC_ONLY_METHODS = new Set([
|
|
19
|
+
'session/new', 'session/load', 'session/resume', 'session/prompt', 'authenticate', STEER_METHOD,
|
|
20
|
+
]);
|
|
21
|
+
export class OutOfOrderRecordError extends Error {
|
|
22
|
+
}
|
|
23
|
+
/** Inbound requests the session layer answers (cards); everything else is `-32601`. */
|
|
24
|
+
const HELD_INBOUND_METHODS = new Set(Object.keys(INBOUND_REQUEST_SCHEMAS));
|
|
25
|
+
/** A processor that threw mid-record: its state may be half-updated, so it refuses to go on. */
|
|
26
|
+
export class ProcessorPoisonedError extends Error {
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Fold one record into `state` and return what it produced. Throws {@link OutOfOrderRecordError}
|
|
30
|
+
* (state untouched) if `record` is not the next record, and `CommandBudgetExceededError` if it would
|
|
31
|
+
* send a 17th request or produce a 257th command — a bug, after which `state` must be discarded (see
|
|
32
|
+
* the file header).
|
|
33
|
+
*/
|
|
34
|
+
export function step(state, record) {
|
|
35
|
+
if (state.offset >= 0 && record.o !== state.offset + 1) {
|
|
36
|
+
throw new OutOfOrderRecordError(`expected record ${state.offset + 1}, got ${record.o}`);
|
|
37
|
+
}
|
|
38
|
+
const out = new RecordOutput(record.o, () => (state.process.status === 'running' ? state.process.spawn.spawnId : undefined));
|
|
39
|
+
switch (record.kind) {
|
|
40
|
+
case 'proc':
|
|
41
|
+
onProc(state, record.proc, out);
|
|
42
|
+
break;
|
|
43
|
+
case 'up':
|
|
44
|
+
onUp(state, record.line, record.o, record.t, out);
|
|
45
|
+
break;
|
|
46
|
+
case 'note':
|
|
47
|
+
onNote(state, record.note, out);
|
|
48
|
+
break;
|
|
49
|
+
case 'down':
|
|
50
|
+
onDown(state, record);
|
|
51
|
+
onSteerDown(state, record);
|
|
52
|
+
break;
|
|
53
|
+
case 'err':
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
state.offset = record.o;
|
|
57
|
+
state.t = record.t;
|
|
58
|
+
return { frames: out.frames, commands: out.commands };
|
|
59
|
+
}
|
|
60
|
+
function sendRequest(state, out, method, params, kind, ctx) {
|
|
61
|
+
const { id, cause } = out.request(method, params);
|
|
62
|
+
const pending = { id, method, kind, cause, ...(ctx !== undefined ? { ctx } : {}) };
|
|
63
|
+
state.outbound[String(id)] = pending;
|
|
64
|
+
return id;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Close the JSON-RPC connection: unanswered cards and inbound requests are void (nothing can carry an
|
|
68
|
+
* answer any more), everything pending settles as `closed` — ending its turn — and any turn left ends
|
|
69
|
+
* `failed`.
|
|
70
|
+
*/
|
|
71
|
+
function closeConnection(state, reason, out) {
|
|
72
|
+
const pending = Object.values(state.outbound);
|
|
73
|
+
state.outbound = {};
|
|
74
|
+
state.inbound = {};
|
|
75
|
+
dropCards(state, out);
|
|
76
|
+
closeRepair(state);
|
|
77
|
+
state.closed = reason;
|
|
78
|
+
for (const entry of pending)
|
|
79
|
+
settle(state, entry, { outcome: 'closed', reason }, out);
|
|
80
|
+
finishAllTurns(state, out);
|
|
81
|
+
endHistory(state, out, false);
|
|
82
|
+
}
|
|
83
|
+
function onProc(state, event, out) {
|
|
84
|
+
if (event.type === 'spawn') {
|
|
85
|
+
if (state.process.status === 'running' && state.closed === undefined) {
|
|
86
|
+
closeConnection(state, 'replaced by a new adapter process', out);
|
|
87
|
+
}
|
|
88
|
+
state.process = { status: 'running', spawn: event };
|
|
89
|
+
delete state.closed;
|
|
90
|
+
state.outbound = {};
|
|
91
|
+
state.inbound = {};
|
|
92
|
+
resetForSpawn(state);
|
|
93
|
+
const id = sendRequest(state, out, 'initialize', initializeRequestParams(), 'initialize');
|
|
94
|
+
state.initialize = { status: 'pending', requestId: id };
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const current = state.process;
|
|
98
|
+
// A second `exit` (e.g. `keeper_lost` after a real one) keeps the spawn it belongs to.
|
|
99
|
+
const spawnId = current.status === 'running' ? current.spawn.spawnId : current.status === 'exited' ? current.spawnId : undefined;
|
|
100
|
+
if (state.closed === undefined && current.status === 'running')
|
|
101
|
+
closeConnection(state, 'process exited', out);
|
|
102
|
+
state.process = { status: 'exited', ...(spawnId !== undefined ? { spawnId } : {}), exit: event };
|
|
103
|
+
if (current.status === 'running')
|
|
104
|
+
onProcessExit(state, out);
|
|
105
|
+
}
|
|
106
|
+
function onUp(state, line, o, t, out) {
|
|
107
|
+
if (!connected(state)) {
|
|
108
|
+
state.diagnostics.ignoredLines += 1;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const message = classifyLine(line);
|
|
112
|
+
switch (message.type) {
|
|
113
|
+
case 'empty':
|
|
114
|
+
return;
|
|
115
|
+
case 'parse_error':
|
|
116
|
+
state.diagnostics.protocolErrors += 1;
|
|
117
|
+
out.error(null, parseError());
|
|
118
|
+
return;
|
|
119
|
+
case 'invalid':
|
|
120
|
+
state.diagnostics.protocolErrors += 1;
|
|
121
|
+
out.error(null, invalidRequest(message.raw));
|
|
122
|
+
return;
|
|
123
|
+
case 'batch':
|
|
124
|
+
// `ClientApp` connects with `allowBatches: false`: a batch closes the connection.
|
|
125
|
+
state.diagnostics.protocolErrors += 1;
|
|
126
|
+
closeConnection(state, BATCH_CLOSE_REASON, out);
|
|
127
|
+
return;
|
|
128
|
+
case 'unaddressed':
|
|
129
|
+
state.diagnostics.protocolErrors += 1;
|
|
130
|
+
return;
|
|
131
|
+
case 'notification':
|
|
132
|
+
if (message.method === CANCEL_REQUEST_METHOD)
|
|
133
|
+
onInboundCancel(state, message.params, out);
|
|
134
|
+
else if (message.method === '_auth/status_update' && state.accountLimits) {
|
|
135
|
+
const auth = message.params?.authStatus;
|
|
136
|
+
const mode = auth?.kind ? state.accountAuthBillingModes?.[auth.kind] : undefined;
|
|
137
|
+
if (auth && mode) {
|
|
138
|
+
state.accountLimits = { ...state.accountLimits, updateMode: 'snapshot', billingMode: mode, availability: mode === 'non_subscription' ? 'not_applicable' : 'unknown', plan: auth.account?.plan ? { id: auth.account.plan, label: auth.account.plan } : null, limits: [], balances: [], _meta: { _axom: { identityOnly: true, authKind: auth.kind } }, observedAt: new Date(t).toISOString() };
|
|
139
|
+
out.unjournaled({ t: 'agent_account_update', snapshot: state.accountLimits });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (message.method === ACCOUNT_LIMITS_UPDATED) {
|
|
143
|
+
const result = accountLimitsSchema.safeParse(message.params);
|
|
144
|
+
if (result.success) {
|
|
145
|
+
const snapshot = { ...result.data, updateMode: result.data.updateMode ?? 'delta' };
|
|
146
|
+
state.accountLimits = snapshot;
|
|
147
|
+
out.unjournaled({ t: 'agent_account_update', snapshot });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (message.method === 'session/update')
|
|
151
|
+
onSessionUpdate(state, message.params, out, t);
|
|
152
|
+
else if (message.method === '_claude/sdkMessage')
|
|
153
|
+
onClaudeSdkMessage(state, out, message.params, t);
|
|
154
|
+
else if (message.method === '_axom/qoder/profile')
|
|
155
|
+
onProfileNotification(state, out, message.params);
|
|
156
|
+
return;
|
|
157
|
+
case 'request':
|
|
158
|
+
onInboundRequest(state, message.id, message.method, message.params, o, out);
|
|
159
|
+
return;
|
|
160
|
+
case 'response': {
|
|
161
|
+
const key = outboundKey(message.id);
|
|
162
|
+
const entry = key === undefined ? undefined : state.outbound[key];
|
|
163
|
+
if (!entry) {
|
|
164
|
+
state.diagnostics.orphanResponses += 1;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
delete state.outbound[key];
|
|
168
|
+
settle(state, entry, settlementOf(message.raw), out);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function onInboundCancel(state, params, out) {
|
|
174
|
+
if (typeof params !== 'object' || params === null)
|
|
175
|
+
return;
|
|
176
|
+
const requestId = params.requestId;
|
|
177
|
+
if (!isJsonRpcId(requestId))
|
|
178
|
+
return;
|
|
179
|
+
const key = inboundKey(requestId);
|
|
180
|
+
const entry = state.inbound[key];
|
|
181
|
+
if (!entry || entry.cancelRequested)
|
|
182
|
+
return;
|
|
183
|
+
entry.cancelRequested = true;
|
|
184
|
+
onInboundCancelled(state, key, out);
|
|
185
|
+
}
|
|
186
|
+
function onInboundRequest(state, id, method, params, o, out) {
|
|
187
|
+
if (!HELD_INBOUND_METHODS.has(method)) {
|
|
188
|
+
out.error(id, methodNotFound(method));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const parsed = INBOUND_REQUEST_SCHEMAS[method].safeParse(params);
|
|
192
|
+
if (!parsed.success) {
|
|
193
|
+
out.error(id, invalidParams(parsed.error.format()));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// The handler sees the SDK-stripped params, exactly as `ClientApp` hands them over.
|
|
197
|
+
const data = parsed.data;
|
|
198
|
+
const key = inboundKey(id);
|
|
199
|
+
// Same id twice replaces the earlier entry, as the SDK's `incomingRequests.set` does.
|
|
200
|
+
state.inbound[key] = { id, method, o };
|
|
201
|
+
const card = method === 'session/request_permission'
|
|
202
|
+
? onPermissionRequest(state, id, data, out)
|
|
203
|
+
: onElicitationRequest(state, id, data, out);
|
|
204
|
+
if (card === undefined)
|
|
205
|
+
delete state.inbound[key];
|
|
206
|
+
else
|
|
207
|
+
state.inbound[key] = { id, method, o, card };
|
|
208
|
+
}
|
|
209
|
+
function settle(state, entry, settlement, out) {
|
|
210
|
+
switch (entry.kind) {
|
|
211
|
+
case 'initialize':
|
|
212
|
+
settleInitialize(state, entry, settlement, out);
|
|
213
|
+
return;
|
|
214
|
+
case 'prompt':
|
|
215
|
+
settlePrompt(state, entry, settlement, out);
|
|
216
|
+
return;
|
|
217
|
+
case 'open':
|
|
218
|
+
settleOpen(state, entry, settlement, out);
|
|
219
|
+
return;
|
|
220
|
+
case 'config':
|
|
221
|
+
settleConfig(state, entry, settlement, out);
|
|
222
|
+
return;
|
|
223
|
+
case 'steer':
|
|
224
|
+
settleSteer(state, entry, settlement, out);
|
|
225
|
+
return;
|
|
226
|
+
case 'account': {
|
|
227
|
+
const parsed = settlement.outcome === 'result' ? accountLimitsSchema.safeParse(settlement.result) : null;
|
|
228
|
+
out.unjournaled({ t: 'agent_account_read_result', id: entry.ctx, ok: parsed?.success === true, ...(parsed?.success ? { snapshot: parsed.data } : { error: 'Failed to fetch usage' }) });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
case 'rpc': {
|
|
232
|
+
const code = settlementErrorCode(settlement);
|
|
233
|
+
const settled = state.diagnostics.settled;
|
|
234
|
+
settled.push({ id: entry.id, method: entry.method, outcome: settlement.outcome, ...(code !== undefined ? { code } : {}) });
|
|
235
|
+
if (settled.length > SETTLED_HISTORY_LIMIT)
|
|
236
|
+
settled.splice(0, settled.length - SETTLED_HISTORY_LIMIT);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function settleInitialize(state, entry, settlement, out) {
|
|
242
|
+
if (state.initialize.status !== 'pending' || state.initialize.requestId !== entry.id)
|
|
243
|
+
return;
|
|
244
|
+
if (settlement.outcome === 'closed') {
|
|
245
|
+
state.initialize = { status: 'failed', reason: 'closed', detail: settlement.reason };
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (settlement.outcome === 'error') {
|
|
249
|
+
const code = settlementErrorCode(settlement);
|
|
250
|
+
state.initialize = { status: 'failed', reason: 'rpc_error', detail: settlement.error.message, ...(code !== undefined ? { code } : {}) };
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
let reading;
|
|
254
|
+
try {
|
|
255
|
+
reading = parseInitializeResult(settlement.result);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
// `connectAgent` would throw here too; the processor records it instead of dying on a bad line.
|
|
259
|
+
state.initialize = { status: 'failed', reason: 'malformed', detail: error instanceof Error ? error.message : String(error) };
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (!reading.protocolSupported) {
|
|
263
|
+
state.initialize = { status: 'failed', reason: 'unsupported_protocol', protocolVersion: reading.protocolVersion };
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const { protocolSupported: _supported, ...facts } = reading;
|
|
267
|
+
state.initialize = { status: 'ready', facts: JSON.parse(JSON.stringify(facts)) };
|
|
268
|
+
onInitialized(state, settlement.result);
|
|
269
|
+
const capability = settlement.result.agentCapabilities?._meta?.['axom/accountRateLimits'];
|
|
270
|
+
if (capability && typeof capability === 'object' && capability.read === true) {
|
|
271
|
+
const quotaCapability = capability;
|
|
272
|
+
state.accountAuthBillingModes = quotaCapability.authBillingModes;
|
|
273
|
+
state.accountLimits = { schemaVersion: 1, provider: quotaCapability.provider ?? 'external', billingMode: 'unknown', availability: 'unknown', observedAt: new Date(state.t).toISOString(), plan: null, limits: [], balances: [], _meta: { _axom: { identityOnly: true } } };
|
|
274
|
+
// Binding can precede initialize. Publish capability identity without reading any quota.
|
|
275
|
+
out.unjournaled({ t: 'agent_account_update', snapshot: state.accountLimits });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** UTF-8 length of a string, without `Buffer` (the processor keeps to plain JS). */
|
|
279
|
+
function utf8Length(text) {
|
|
280
|
+
let bytes = 0;
|
|
281
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
282
|
+
const code = text.charCodeAt(index);
|
|
283
|
+
if (code < 0x80)
|
|
284
|
+
bytes += 1;
|
|
285
|
+
else if (code < 0x800)
|
|
286
|
+
bytes += 2;
|
|
287
|
+
else if (code >= 0xd800 && code <= 0xdbff && index + 1 < text.length) {
|
|
288
|
+
bytes += 4;
|
|
289
|
+
index += 1;
|
|
290
|
+
}
|
|
291
|
+
else
|
|
292
|
+
bytes += 3;
|
|
293
|
+
}
|
|
294
|
+
return bytes;
|
|
295
|
+
}
|
|
296
|
+
function onNote(state, note, out) {
|
|
297
|
+
if (utf8Length(JSON.stringify(note)) > NOTE_MAX_BYTES) {
|
|
298
|
+
state.diagnostics.droppedNotes += 1;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
switch (note.type) {
|
|
302
|
+
case 'agent_prompt':
|
|
303
|
+
onPromptNote(state, note, out);
|
|
304
|
+
return;
|
|
305
|
+
case 'agent_prompt_failed': {
|
|
306
|
+
const entry = typeof note.requestId === 'number' ? state.outbound[String(note.requestId)] : undefined;
|
|
307
|
+
if (entry?.kind === 'steer') {
|
|
308
|
+
if (typeof note.error !== 'string' || !failUnsentSteer(state, entry, note.error, out))
|
|
309
|
+
state.diagnostics.droppedNotes += 1;
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
onPromptFailedNote(state, note, out);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
case 'agent_interrupt':
|
|
316
|
+
onInterruptNote(state, out);
|
|
317
|
+
return;
|
|
318
|
+
case 'agent_answer':
|
|
319
|
+
onAnswerNote(state, note, out);
|
|
320
|
+
return;
|
|
321
|
+
case 'agent_elicitation_answer':
|
|
322
|
+
onElicitationAnswerNote(state, note, out);
|
|
323
|
+
return;
|
|
324
|
+
case 'agent_answer_undeliverable':
|
|
325
|
+
onUndeliverableNote(state, note, out);
|
|
326
|
+
return;
|
|
327
|
+
case 'agent_session_open':
|
|
328
|
+
onOpenNote(state, note, out);
|
|
329
|
+
return;
|
|
330
|
+
case 'historyEnd':
|
|
331
|
+
onHistoryEndNote(state, out);
|
|
332
|
+
return;
|
|
333
|
+
case 'agent_set_config_option':
|
|
334
|
+
onSetConfigOptionNote(state, note, out);
|
|
335
|
+
return;
|
|
336
|
+
case 'agent_steer':
|
|
337
|
+
onSteerNote(state, note, out);
|
|
338
|
+
return;
|
|
339
|
+
case 'agent_async_task_stop':
|
|
340
|
+
onAsyncTaskStopNote(state, note, out);
|
|
341
|
+
return;
|
|
342
|
+
case 'agent_delegation_renewed':
|
|
343
|
+
onDelegationRenewedNote(state, note);
|
|
344
|
+
return;
|
|
345
|
+
case 'agent_delegation_refresh_pending':
|
|
346
|
+
onDelegationRefreshPendingNote(state);
|
|
347
|
+
return;
|
|
348
|
+
case 'agent_launch':
|
|
349
|
+
onLaunch(state, note);
|
|
350
|
+
return;
|
|
351
|
+
case 'agent_install_outcome':
|
|
352
|
+
onInstallOutcomeNote(state, note, out);
|
|
353
|
+
return;
|
|
354
|
+
case 'agent_output_gap':
|
|
355
|
+
onOutputGapNote(state, note, out);
|
|
356
|
+
return;
|
|
357
|
+
case REPAIR_NOTE:
|
|
358
|
+
onRepairNote(state, note);
|
|
359
|
+
return;
|
|
360
|
+
case SIDE_NOTE:
|
|
361
|
+
onSideNote(state, note, out);
|
|
362
|
+
return;
|
|
363
|
+
case 'agent_account_read': {
|
|
364
|
+
if (!connected(state)) {
|
|
365
|
+
out.unjournaled({ t: 'agent_account_read_result', id: note.id, ok: false, error: 'Agent is offline' });
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (state.initialize.status !== 'ready' || !state.initialize.facts.supportsAccountLimits ||
|
|
369
|
+
state.accountLimits?.billingMode === 'non_subscription' ||
|
|
370
|
+
(state.accountAuthBillingModes && state.accountLimits?.billingMode !== 'subscription')) {
|
|
371
|
+
out.unjournaled({ t: 'agent_account_read_result', id: note.id, ok: false, error: 'Account usage is unavailable' });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
sendRequest(state, out, ACCOUNT_LIMITS_READ, { sessionId: state.rootSessionId }, 'account', note.id);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
case 'rpc.request': {
|
|
378
|
+
const { method, params, ctx } = note;
|
|
379
|
+
if (typeof method !== 'string' || method === '' || SEMANTIC_ONLY_METHODS.has(method) || !connected(state)) {
|
|
380
|
+
state.diagnostics.droppedNotes += 1;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
sendRequest(state, out, method, params, 'rpc', ctx);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
case 'rpc.cancel': {
|
|
387
|
+
const key = outboundKey(note.requestId);
|
|
388
|
+
const entry = key === undefined ? undefined : state.outbound[key];
|
|
389
|
+
if (!entry || entry.cancelRequested || !connected(state)) {
|
|
390
|
+
state.diagnostics.droppedNotes += 1;
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
out.notify(CANCEL_REQUEST_METHOD, { requestId: entry.id });
|
|
394
|
+
entry.cancelRequested = true;
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
case 'rpc.respond': {
|
|
398
|
+
const requestId = note.requestId;
|
|
399
|
+
const entry = isJsonRpcId(requestId) ? state.inbound[inboundKey(requestId)] : undefined;
|
|
400
|
+
const hasResult = Object.hasOwn(note, 'result');
|
|
401
|
+
const error = note.error;
|
|
402
|
+
const validError = typeof error === 'object' && error !== null
|
|
403
|
+
&& Number.isInteger(error.code) && typeof error.message === 'string';
|
|
404
|
+
if (!entry || !connected(state) || hasResult === (error !== undefined) || (error !== undefined && !validError)) {
|
|
405
|
+
state.diagnostics.droppedNotes += 1;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const key = inboundKey(entry.id);
|
|
409
|
+
delete state.inbound[key];
|
|
410
|
+
forgetCard(state, key, entry.card, out);
|
|
411
|
+
if (hasResult)
|
|
412
|
+
out.result(entry.id, note.result);
|
|
413
|
+
else
|
|
414
|
+
out.error(entry.id, error);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
default:
|
|
418
|
+
state.diagnostics.unknownNotes += 1;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
export function createProcessor(init = {}) {
|
|
422
|
+
const state = init.state ?? initialState();
|
|
423
|
+
let poisoned;
|
|
424
|
+
const refuse = () => {
|
|
425
|
+
if (poisoned !== undefined)
|
|
426
|
+
throw new ProcessorPoisonedError(`processor state is unusable after a failed step: ${poisoned}`);
|
|
427
|
+
};
|
|
428
|
+
return {
|
|
429
|
+
step(record) {
|
|
430
|
+
refuse();
|
|
431
|
+
try {
|
|
432
|
+
return step(state, record);
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
if (!(error instanceof OutOfOrderRecordError))
|
|
436
|
+
poisoned = error instanceof Error ? error.message : String(error);
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
snapshot() {
|
|
441
|
+
refuse();
|
|
442
|
+
return checkpointOf(state);
|
|
443
|
+
},
|
|
444
|
+
get state() {
|
|
445
|
+
return state;
|
|
446
|
+
},
|
|
447
|
+
get poisoned() {
|
|
448
|
+
return poisoned;
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Continue from a checkpoint (already decoded by `decodeCheckpoint`). The caller compares
|
|
454
|
+
* `checkpoint.processorVersion` with `PROCESSOR_VERSION` to decide whether replay output may be
|
|
455
|
+
* compared with the outbox (spec §5.5.6).
|
|
456
|
+
*/
|
|
457
|
+
export function restoreProcessor(checkpoint) {
|
|
458
|
+
return createProcessor({ state: JSON.parse(JSON.stringify(checkpoint.state)) });
|
|
459
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A representative raw log that touches every piece of processor state Tasks 16–17b keep: initialize
|
|
3
|
+
* facts and the Qoder profile, the open session bound from its response (new, then — after a respawn —
|
|
4
|
+
* load with history and `historyEnd`), delegation and launch notes, config and steering round trips,
|
|
5
|
+
* every kind of `session/update` route (root, draft, child), message synthesis including an
|
|
6
|
+
* out-of-turn trailing chunk, async tasks, activity, both card kinds and the origin graph, turns
|
|
7
|
+
* (completed, interrupted, cut by an exit), the 17a low-level notes, and the diagnostics.
|
|
8
|
+
*/
|
|
9
|
+
import { createProcessor } from '../step.js';
|
|
10
|
+
import { append, chunk, elicitationRequest, exitEntry, initializeResult, lastPromptId, lastRequestId, newSession, note, permissionRequest, prompt, respond, run, spawnEntry, subagentSpawned, up, update, } from './harness.js';
|
|
11
|
+
export const QODER_PROFILE = {
|
|
12
|
+
cliVersion: '1.1.61', sdkVersion: '0.1.0', executable: '/usr/local/bin/qodercli', persistent: true, agentMessaging: false,
|
|
13
|
+
observerHealthy: true, completedContinuationEnabled: false, sendMessageExposed: false, mode: 'sdk-adapter', transport: 'process',
|
|
14
|
+
capabilities: ['background_tasks_v1'],
|
|
15
|
+
};
|
|
16
|
+
export const LAUNCH = { source: 'registry', executable: '/usr/local/bin/agent', platform: 'darwin', architecture: 'arm64', transport: 'stdio' };
|
|
17
|
+
export function representativeLog() {
|
|
18
|
+
const session = newSession();
|
|
19
|
+
const processor = createProcessor();
|
|
20
|
+
let next = 0;
|
|
21
|
+
const step = (...entries) => {
|
|
22
|
+
append(session, ...entries);
|
|
23
|
+
next = run(processor, session, next);
|
|
24
|
+
};
|
|
25
|
+
step(spawnEntry('spawn-a'), note('agent_launch', { launch: LAUNCH }));
|
|
26
|
+
step(respond(lastRequestId(session, 'initialize'), initializeResult({
|
|
27
|
+
agentCapabilities: { loadSession: true, sessionCapabilities: { list: {} } },
|
|
28
|
+
authMethods: [{ id: 'login', name: 'Login', type: 'terminal', args: ['login'] }, { type: 'terminal' }],
|
|
29
|
+
agentInfo: { name: '@agentclientprotocol/claude-agent-acp', version: '1.0.0' },
|
|
30
|
+
_meta: { steering: { supported: true, timings: ['next'] }, qoderProfile: QODER_PROFILE },
|
|
31
|
+
})));
|
|
32
|
+
step(
|
|
33
|
+
// Before the open response: a profile candidate (kept), an update (no root yet: dropped).
|
|
34
|
+
up({ jsonrpc: '2.0', method: '_axom/qoder/profile', params: { sessionId: 'S1', revision: 1,
|
|
35
|
+
profile: { ...QODER_PROFILE, rootSessionId: 'S1', runtimeGeneration: 'g1', rootConfirmed: true, initialized: true } } }), note('agent_session_open', {
|
|
36
|
+
mode: 'new', cwd: '/work', redact: ['secret:delegation'],
|
|
37
|
+
mcpServers: [{ type: 'http', name: 'axom-delegation', url: 'https://x.test/mcp/delegation', headers: [{ name: 'Authorization', value: 'secret:delegation' }] }],
|
|
38
|
+
delegation: { expiresAtEpochMs: 1_900_000_000_000, leaseId: 'lease-hash', bindingId: 'b-1', instanceId: 'i-1' },
|
|
39
|
+
}), update('S1', { sessionUpdate: 'available_commands_update', availableCommands: [] }));
|
|
40
|
+
step(respond(lastRequestId(session, 'session/new'), {
|
|
41
|
+
sessionId: 'S1',
|
|
42
|
+
configOptions: [{ id: 'model', name: 'Model', type: 'select', currentValue: 'a', options: [{ value: 'a', name: 'A' }, { value: 'b', name: 'B' }] }],
|
|
43
|
+
modes: { currentModeId: 'ask', availableModes: [{ id: 'ask', name: 'Ask' }, { id: 'code', name: 'Code' }] },
|
|
44
|
+
}));
|
|
45
|
+
step(update('S1', { sessionUpdate: 'available_commands_update', availableCommands: [{ name: 'init', description: 'Init' }] }), note('rpc.request', { method: '_test/probe', params: { x: 1 }, ctx: { purpose: 'probe' } }), permissionRequest(5), subagentSpawned('S1', 'C1'), subagentSpawned('ghost', 'C2'), permissionRequest(6, 'C1'), elicitationRequest(9), prompt(session, 'one', { platformTurnId: 'turn-1' }), chunk('S1', 'Hello ', 'm1'), update('S1', { sessionUpdate: 'tool_call', toolCallId: 't1', title: 'Run', kind: 'execute', status: 'pending', rawInput: { command: 'sleep 1' } }), update('S1', { sessionUpdate: 'tool_call_update', toolCallId: 't1', status: 'completed' }), chunk('C1', 'child says hi', 'cm1'), update('S1', { sessionUpdate: 'async_task_spawned', asyncTaskId: 'task-1', name: 'shell', taskType: 'shell', description: 'd', showInTranscript: false, canStop: true }), update('S1', { sessionUpdate: 'async_task_spawned', asyncTaskId: 'task-2', name: 'shell', taskType: 'shell', description: 'd', showInTranscript: false, canStop: true }), chunk('S1', 'world', 'm2'), up({ jsonrpc: '2.0', method: '_claude/sdkMessage', params: { message: { type: 'system', subtype: 'session_state_changed', state: 'running' } } }), update('S1', { sessionUpdate: 'session_info_update', title: 'T', _meta: { codex: { threadStatus: { type: 'active' } }, piAcp: { queueDepth: 2, running: true } } }), { kind: 'err', line: 'adapter: warming up' }, up({ jsonrpc: '2.0', id: 12345, result: {} }), up({ jsonrpc: '2.0', method: '$/cancel_request', params: { requestId: 5 } }), note('mystery.future.note'));
|
|
46
|
+
const probe = Object.values(processor.state.outbound).find((p) => p.method === '_test/probe').id;
|
|
47
|
+
step(respond(probe, { ok: true }), note('agent_answer', { requestId: 'perm:spawn-a:6', outcome: 'allow' }), note('agent_elicitation_answer', { requestId: '9', action: 'accept', content: { branch: 'main' } }), note('agent_set_config_option', { id: 'cfg-1', configId: 'model', value: 'b' }), note('agent_set_config_option', { id: 'cfg-2', configId: 'acp:modes', value: 'code' }));
|
|
48
|
+
step(respond(lastRequestId(session, 'session/set_config_option'), {
|
|
49
|
+
configOptions: [{ id: 'model', name: 'Model', type: 'select', currentValue: 'b', options: [{ value: 'a', name: 'A' }, { value: 'b', name: 'B' }] }],
|
|
50
|
+
}), respond(lastRequestId(session, 'session/set_mode'), null));
|
|
51
|
+
session.blocks.set('steer-1', [{ type: 'text', text: 'also this' }]);
|
|
52
|
+
step(note('agent_steer', { id: 'steer-1', blocksRef: 'steer-1', priority: 'next' }));
|
|
53
|
+
step(respond(lastRequestId(session, '_session/steering'), { outcome: 'injected' }), respond(lastPromptId(session), { stopReason: 'end_turn' }),
|
|
54
|
+
// Trailing, after the response: out of turn, closed before the next turn starts.
|
|
55
|
+
chunk('S1', 'late words'), update('S1', { sessionUpdate: 'async_task_state_update', asyncTaskId: 'task-1', state: 'stopped' }), update('S1', { sessionUpdate: 'async_task_state_update', asyncTaskId: 'task-1', state: 'completed' }), note('agent_delegation_renewed', { expiresAtEpochMs: 1_900_000_100_000, renewalRevision: 1 }), prompt(session, 'two'), chunk('S1', 'thinking'), update('S1', { sessionUpdate: 'tool_call', toolCallId: 't2', title: 'Serve', kind: 'execute', rawInput: { command: 'serve', run_in_background: true } }), permissionRequest(10), note('agent_interrupt'));
|
|
56
|
+
step(respond(lastPromptId(session), { stopReason: 'cancelled' }), prompt(session, 'three', { platformTurnId: 'turn-3' }), note('rpc.respond', { requestId: 5, result: { outcome: { outcome: 'cancelled' } } }));
|
|
57
|
+
step(exitEntry(0), spawnEntry('spawn-b'));
|
|
58
|
+
step(respond(lastRequestId(session, 'initialize'), initializeResult({ agentInfo: { name: 'other-agent', version: '2' } })));
|
|
59
|
+
step(note('agent_launch', { launch: LAUNCH }), note('agent_session_open', { mode: 'load', cwd: '/work', sessionId: 'S1' }));
|
|
60
|
+
step(update('S1', { sessionUpdate: 'user_message_chunk', content: { type: 'text', text: 'old question' } }), chunk('S1', 'old answer', 'h1'), subagentSpawned('S1', 'C9'), chunk('C9', 'old child'));
|
|
61
|
+
step(respond(lastRequestId(session, 'session/load'), { configOptions: [] }));
|
|
62
|
+
step(chunk('S1', 'qoder replays one more old chunk after its response'), note('historyEnd'));
|
|
63
|
+
step(prompt(session, 'four'), chunk('S1', 'new'), note('historyEnd'));
|
|
64
|
+
return { session, records: session.log.read(0) };
|
|
65
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test harness: an in-memory session — raw log, outbox, and the record loop the daemon will run
|
|
3
|
+
* (spec 2026-10-11 §5.4/§5.5): `step` → execute the record's commands (idempotent by cause) → append
|
|
4
|
+
* its frames to the outbox.
|
|
5
|
+
*/
|
|
6
|
+
import { executeDownCommands } from '../commands.js';
|
|
7
|
+
import { MemoryRawLog } from '../memoryLog.js';
|
|
8
|
+
import { createProcessor } from '../step.js';
|
|
9
|
+
export class SimulatedCrash extends Error {
|
|
10
|
+
}
|
|
11
|
+
export function newSession() {
|
|
12
|
+
let now = 1_000;
|
|
13
|
+
const stdin = [];
|
|
14
|
+
const log = new MemoryRawLog(() => now++, (line) => stdin.push(line));
|
|
15
|
+
return { log, stdin, outbox: [], blocks: new Map() };
|
|
16
|
+
}
|
|
17
|
+
/** What the executor appends when a prompt's blocks are gone (T19 does the same). */
|
|
18
|
+
export const PROMPT_LOST = 'The prompt was lost before it reached the agent';
|
|
19
|
+
/**
|
|
20
|
+
* Process every record from `from` to the head — including the `down` records the loop itself
|
|
21
|
+
* appends — and return the offset to continue from.
|
|
22
|
+
*/
|
|
23
|
+
export function run(processor, session, from, options = {}) {
|
|
24
|
+
let next = from;
|
|
25
|
+
while (next < session.log.head) {
|
|
26
|
+
const record = session.log.read(next, 1)[0];
|
|
27
|
+
const output = processor.step(record);
|
|
28
|
+
const report = executeDownCommands(session.log, output.commands, (ref) => session.blocks.get(ref));
|
|
29
|
+
for (const command of report.unresolved) {
|
|
30
|
+
const requestId = JSON.parse(command.line).id;
|
|
31
|
+
session.log.append({ kind: 'note', note: { type: 'agent_prompt_failed', requestId, error: PROMPT_LOST } });
|
|
32
|
+
}
|
|
33
|
+
options.afterCommands?.(record, output.commands);
|
|
34
|
+
session.outbox.push(...output.frames);
|
|
35
|
+
next += 1;
|
|
36
|
+
}
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
export function append(session, ...entries) {
|
|
40
|
+
for (const entry of entries)
|
|
41
|
+
session.log.append(entry);
|
|
42
|
+
}
|
|
43
|
+
export const spawnEntry = (spawnId = 'spawn-1') => ({ kind: 'proc', proc: { type: 'spawn', spawnId, pid: 4242, pgid: 4242, startTime: 'Sat Oct 11 10:00:00 2026' } });
|
|
44
|
+
export const exitEntry = (code = 0) => ({ kind: 'proc', proc: { type: 'exit', code, signal: null, stderrTail: '' } });
|
|
45
|
+
export const up = (message) => ({ kind: 'up', line: JSON.stringify(message) });
|
|
46
|
+
export const note = (type, fields = {}) => ({ kind: 'note', note: { type, ...fields } });
|
|
47
|
+
/** The `initialize` result a v1 agent answers with. */
|
|
48
|
+
export const initializeResult = (extra = {}) => ({ protocolVersion: 1, agentCapabilities: {}, authMethods: [], ...extra });
|
|
49
|
+
/** JSON-RPC requests (with ids) the processor wrote to stdin, parsed. */
|
|
50
|
+
export function sentRequests(session) {
|
|
51
|
+
return session.stdin
|
|
52
|
+
.map((line) => JSON.parse(line))
|
|
53
|
+
.filter((m) => typeof m.method === 'string' && 'id' in m);
|
|
54
|
+
}
|
|
55
|
+
/** An `agent_prompt` note whose blocks the session holds under a fresh ref. */
|
|
56
|
+
export function prompt(session, text, fields = {}) {
|
|
57
|
+
const blocksRef = `blocks-${session.blocks.size + 1}`;
|
|
58
|
+
session.blocks.set(blocksRef, [{ type: 'text', text }]);
|
|
59
|
+
return note('agent_prompt', { blocksRef, ...fields });
|
|
60
|
+
}
|
|
61
|
+
/** Frames in the outbox, as JSON (for byte-for-byte comparison). */
|
|
62
|
+
export const framesJson = (session) => session.outbox.map((frame) => JSON.stringify(frame));
|
|
63
|
+
/** The JSON-RPC response the agent writes for request `id`. */
|
|
64
|
+
export const respond = (id, result = {}) => up({ jsonrpc: '2.0', id, result });
|
|
65
|
+
/** The id of the last request with `method` written to stdin. */
|
|
66
|
+
export const lastRequestId = (session, method) => sentRequests(session).filter((request) => request.method === method).at(-1).id;
|
|
67
|
+
/**
|
|
68
|
+
* Spawn, answer `initialize`, open the session with `session/new` and answer it with `sessionId`: the
|
|
69
|
+
* response binds the root (Task 17b — no helper binds it any more). Returns the offset to continue from.
|
|
70
|
+
*/
|
|
71
|
+
export function openSession(session, processor = createProcessor(), sessionId = 'S1', spawnId = 'spawn-1', newResult = {}) {
|
|
72
|
+
append(session, spawnEntry(spawnId));
|
|
73
|
+
let next = run(processor, session, processor.state.offset + 1);
|
|
74
|
+
append(session, respond(lastRequestId(session, 'initialize'), initializeResult()));
|
|
75
|
+
next = run(processor, session, next);
|
|
76
|
+
append(session, note('agent_session_open', { mode: 'new', cwd: '/work' }));
|
|
77
|
+
next = run(processor, session, next);
|
|
78
|
+
append(session, respond(lastRequestId(session, 'session/new'), { sessionId, ...newResult }));
|
|
79
|
+
next = run(processor, session, next);
|
|
80
|
+
return { processor, next, sessionId };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fold records `[0, until)` into a fresh processor the way recovery replays (commands go through the
|
|
84
|
+
* idempotent executor, so nothing already in raw is written again; frames are collected, not sent).
|
|
85
|
+
* The log alone is enough: the open response binds the root.
|
|
86
|
+
*/
|
|
87
|
+
export function replay(session, until = session.log.head) {
|
|
88
|
+
const processor = createProcessor();
|
|
89
|
+
const frames = [];
|
|
90
|
+
for (let next = 0; next < until; next += 1) {
|
|
91
|
+
const output = processor.step(session.log.read(next, 1)[0]);
|
|
92
|
+
const report = executeDownCommands(session.log, output.commands, (ref) => session.blocks.get(ref));
|
|
93
|
+
if (report.written.length > 0)
|
|
94
|
+
throw new Error(`replay wrote ${report.written.length} new command(s) at record ${next}`);
|
|
95
|
+
frames.push(...output.frames);
|
|
96
|
+
}
|
|
97
|
+
return { processor, frames };
|
|
98
|
+
}
|
|
99
|
+
/** The `id` of the last `session/prompt` written to stdin. */
|
|
100
|
+
export const lastPromptId = (session) => sentRequests(session).filter((r) => r.method === 'session/prompt').at(-1).id;
|
|
101
|
+
/** A `session/request_permission` the agent sends. */
|
|
102
|
+
export const permissionRequest = (id, sessionId = 'S1', options = ['allow', 'deny'], toolCall = { toolCallId: 'call-1', title: 'Edit file' }) => up({ jsonrpc: '2.0', id, method: 'session/request_permission', params: {
|
|
103
|
+
sessionId, toolCall, options: options.map((optionId) => ({ optionId, name: optionId, kind: optionId === 'allow' ? 'allow_once' : 'reject_once' })),
|
|
104
|
+
} });
|
|
105
|
+
/** A `'form'` `elicitation/create` the agent sends. */
|
|
106
|
+
export const elicitationRequest = (id, message = 'Which branch?', sessionId = 'S1') => up({ jsonrpc: '2.0', id, method: 'elicitation/create', params: {
|
|
107
|
+
sessionId, mode: 'form', message, requestedSchema: { type: 'object', properties: { branch: { type: 'string', title: 'Branch' } } },
|
|
108
|
+
} });
|
|
109
|
+
/** A `session/update` notification. */
|
|
110
|
+
export const update = (sessionId, fields) => up({ jsonrpc: '2.0', method: 'session/update', params: { sessionId, update: fields } });
|
|
111
|
+
/** An agent text chunk. */
|
|
112
|
+
export const chunk = (sessionId, text, messageId) => update(sessionId, { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text }, ...(messageId ? { messageId } : {}) });
|
|
113
|
+
/** Journaled frames of one kind (`agent_turn_done` / `agent_event`). */
|
|
114
|
+
export const framesOf = (session, t) => session.outbox.map((f) => f.frame).filter((f) => f.t === t);
|
|
115
|
+
/** The last stdin line, parsed. */
|
|
116
|
+
export const lastStdin = (session) => JSON.parse(session.stdin.at(-1));
|
|
117
|
+
/** A draft `subagent_spawned` update: `child` was spawned under `parent`. */
|
|
118
|
+
export const subagentSpawned = (parent, child) => up({ jsonrpc: '2.0', method: 'session/update', params: { sessionId: parent, update: {
|
|
119
|
+
sessionUpdate: 'subagent_spawned', subagentSessionId: child, name: child, task: 't', capabilities: {},
|
|
120
|
+
} } });
|