@platless/axom-cli 0.3.13 → 0.6.36
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 +18 -5
- package/dist/axom-patch/cli/README.md +119 -9
- 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 +123 -0
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +81 -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/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
- package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
- package/dist/axom-patch/cli/nightly-task.md +194 -0
- package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
- package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +5 -1
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
- package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
- package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -5
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -7
- package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
- package/dist/axom-patch/cli/qoder-steering.md +20 -25
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
- package/dist/axom-patch/cli/runtime.json +39 -15
- package/dist/axom-patch/cli/zcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/zcode-models.json +37 -0
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +72 -0
- package/dist/cli.js +4 -9
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +65 -3
- package/dist/commands/workstation.js +73 -7
- 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 +1 -1
- package/dist/native/darwin-arm64/axom-quic +0 -0
- package/dist/test/keeperKit.js +67 -0
- package/dist/workstation/acp/acpAgents.js +2192 -636
- package/dist/workstation/acp/acpClient.js +84 -495
- package/dist/workstation/acp/adoption.js +10 -396
- 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 +324 -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/envSecrets.js +27 -0
- package/dist/workstation/acp/initializeResponse.js +290 -0
- package/dist/workstation/acp/installErrors.js +12 -2
- package/dist/workstation/acp/installProbe.js +1 -1
- package/dist/workstation/acp/managedPatches.js +239 -159
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +60 -3
- package/dist/workstation/acp/permission.js +42 -10
- package/dist/workstation/acp/processor/checkpoint.js +78 -0
- package/dist/workstation/acp/processor/commands.js +35 -0
- package/dist/workstation/acp/processor/control.js +282 -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 +105 -0
- package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
- package/dist/workstation/acp/processor/session.js +496 -0
- package/dist/workstation/acp/processor/state.js +67 -0
- package/dist/workstation/acp/processor/step.js +442 -0
- package/dist/workstation/acp/processor/test/fixtureLog.js +58 -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 +390 -0
- package/dist/workstation/acp/processorSession.js +1348 -0
- package/dist/workstation/acp/registry/catalog.js +189 -41
- package/dist/workstation/acp/registry/launch.js +76 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +111 -0
- package/dist/workstation/acp/terminalLogin.js +16 -42
- 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 +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +295 -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/sanitize.js +344 -0
- package/dist/workstation/acp/test/raw/sources.js +135 -0
- package/dist/workstation/acp/test/realAdapter.js +331 -0
- package/dist/workstation/acp/test/scriptAgent.js +214 -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/agentFlow.js +103 -0
- package/dist/workstation/agentSessions.js +1289 -544
- package/dist/workstation/channels/agent.js +7 -5
- package/dist/workstation/channels/computer.js +69 -21
- package/dist/workstation/channels/computerExecs.js +200 -0
- package/dist/workstation/channels/management.js +172 -8
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +16 -4
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +200 -0
- package/dist/workstation/control/commands.js +124 -0
- package/dist/workstation/control/daemonIdentity.js +53 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +63 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/runtimeClient.js +97 -0
- package/dist/workstation/control/runtimeHost.js +148 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/daemon.js +393 -12
- package/dist/workstation/daemonctl.js +103 -14
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/capabilities.js +92 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +353 -0
- package/dist/workstation/desktopControl/lobby.js +129 -0
- package/dist/workstation/desktopControl/macProvider.js +155 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/protocol.js +302 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1681 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +78 -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/credentials.js +142 -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/{fileOps.js → files/text.js} +152 -39
- 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 +124 -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 +289 -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 +76 -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/managedInstall.js +57 -0
- package/dist/workstation/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +50 -19
- package/dist/workstation/protocol/common.js +30 -0
- package/dist/workstation/protocol/computer.js +15 -4
- package/dist/workstation/protocol/management.js +141 -4
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +119 -0
- package/dist/workstation/rd/control.js +119 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/provider.js +222 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +45 -0
- package/dist/workstation/rd/update.js +171 -0
- package/dist/workstation/rd/upkeep.js +38 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/service/installer.js +104 -13
- package/dist/workstation/service/launchd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +154 -93
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- package/dist/workstation/tools/client.js +9 -1
- package/dist/workstation/tools/info.js +50 -0
- package/dist/workstation/tools/keeperProcess.js +15 -5
- package/dist/workstation/tools/router.js +55 -0
- package/dist/workstation/tools/terminalRouter.js +222 -0
- package/dist/workstation/tools/validation.js +37 -1
- package/dist/workstation/update/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +3 -0
- package/dist/workstation/update/globalInstall.js +116 -0
- package/dist/workstation/update/npmInstaller.js +49 -23
- package/dist/workstation/update/ptyPreflight.js +21 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/runUpdate.js +3 -2
- package/dist/workstation/update/taskRegistry.js +105 -11
- package/dist/workstation/update/watchdog.js +16790 -334
- package/package.json +11 -8
- package/skills/axom/SKILL.md +11 -11
- package/skills/axom-definition/SKILL.md +3 -3
- package/skills/axom-platform/SKILL.md +3 -3
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -49
- package/dist/axom-patch/cli/qoder-steering.json +0 -52
- package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
- package/dist/commands/guide.js +0 -38
- package/dist/workstation/acp/acpSession.js +0 -1475
- package/dist/workstation/acp/contract.js +0 -499
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/draftInterceptor.js +0 -163
- package/dist/workstation/acp/qoderStartup.js +0 -45
- package/dist/workstation/acp/sessionScope.js +0 -19
- package/dist/workstation/filePaths.js +0 -174
- package/dist/workstation/fileWorker.js +0 -81
- package/dist/workstation/fileWorkerClient.js +0 -121
- package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -106
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -262
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -30
- package/dist/workstation/qoderAdapter/history.js +0 -87
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -93
- package/dist/workstation/qoderAdapter/mapper.js +0 -117
- package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
- package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
- package/dist/workstation/qoderAdapter/permissions.js +0 -61
- package/dist/workstation/qoderAdapter/profile.js +0 -93
- package/dist/workstation/qoderAdapter/sdkDriver.js +0 -49
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -77
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- package/dist/workstation/tools/core.js +0 -50
- package/dist/workstation/tools/files.js +0 -525
- package/dist/workstation/tools/terminal.js +0 -214
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous JSON-RPC for the processor: id allocation, message classification, response
|
|
3
|
+
* correlation, and the exact bytes of every line it writes.
|
|
4
|
+
*
|
|
5
|
+
* ## Why the processor does this itself
|
|
6
|
+
*
|
|
7
|
+
* Today the SDK's `Connection` owns JSON-RPC, and its correlation is a per-connection counter from 0
|
|
8
|
+
* kept in a `Map` that dies with the process. Re-attaching to a still-running adapter with a fresh
|
|
9
|
+
* connection therefore reuses ids, and a late response to a previous daemon's `session/prompt`
|
|
10
|
+
* resolves whatever new request happens to hold the same number — measured in
|
|
11
|
+
* `docs/superpowers/reports/2026-10-11-keepers-analysis/experiments/exp-reattach.mjs naive`, where the
|
|
12
|
+
* new connection's third `_test/slow` call resolved to `{"stopReason":"end_turn"}`.
|
|
13
|
+
*
|
|
14
|
+
* Here:
|
|
15
|
+
* - every outbound request id is `o * 16 + i` ({@link outboundRequestId}) — numeric, strictly
|
|
16
|
+
* increasing along the log, and therefore never reused by any daemon generation reading the same
|
|
17
|
+
* log;
|
|
18
|
+
* - a response is matched only against the processor's own pending table, keyed by that id. An id
|
|
19
|
+
* the table does not hold is an **orphan**: it is counted and never attributed to anything else.
|
|
20
|
+
*
|
|
21
|
+
* ## Same semantics as the SDK, on purpose
|
|
22
|
+
*
|
|
23
|
+
* Every shape below mirrors `@agentclientprotocol/sdk` 1.4.0 `dist/jsonrpc.js` / `dist/stream.js`, so
|
|
24
|
+
* moving a session onto the processor changes nothing an adapter or the server can observe:
|
|
25
|
+
* - classification follows `receiveWireMessage`/`receiveMessage` (`isRequestMessage`,
|
|
26
|
+
* `isNotificationMessage`, `isResponseShapedMessage`, `isJsonRpcId`);
|
|
27
|
+
* - an unparseable line answers `-32700 Parse error`, a non-message `-32600 Invalid request`, both
|
|
28
|
+
* with `id: null` (`protocolErrorResponse`);
|
|
29
|
+
* - a response-shaped message for a pending id that is not a valid response settles that request
|
|
30
|
+
* as `RequestError.invalidRequest(response)` (`handleResponse`);
|
|
31
|
+
* - an error response settles with `{code, message, data}` exactly as `new RequestError(code, message,
|
|
32
|
+
* data)` would, and {@link settlementErrorCode} applies `jsonRpcErrorCode`'s rule to it;
|
|
33
|
+
* - an unhandled inbound request answers `RequestError.methodNotFound(method)`, a zod failure
|
|
34
|
+
* `RequestError.invalidParams(error.format())`.
|
|
35
|
+
*/
|
|
36
|
+
import { jsonRpcErrorCodeOf } from '../initializeResponse.js';
|
|
37
|
+
export const CANCEL_REQUEST_METHOD = '$/cancel_request';
|
|
38
|
+
/** Requests one record may send: their `i` ranges over `[0, 16)` so `o * 16 + i` is collision-free. */
|
|
39
|
+
export const COMMANDS_PER_RECORD = 16;
|
|
40
|
+
/**
|
|
41
|
+
* Commands of any kind one record may produce. Responses and notifications take no id, so only the
|
|
42
|
+
* first 16 commands may be requests; the rest (e.g. an interrupt cancelling 64 pending permission
|
|
43
|
+
* cards) only need a unique `(o, i)` cause for idempotency.
|
|
44
|
+
*/
|
|
45
|
+
export const MAX_COMMANDS_PER_RECORD = 256;
|
|
46
|
+
export class CommandBudgetExceededError extends Error {
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The JSON-RPC id of the request a record's `i`-th command sends (spec §5.4).
|
|
50
|
+
*
|
|
51
|
+
* Throws rather than wrapping: a 17th command from one record would collide with the next record's
|
|
52
|
+
* first id, which is precisely the defect this scheme exists to make impossible.
|
|
53
|
+
*/
|
|
54
|
+
export function outboundRequestId(cause) {
|
|
55
|
+
const { o, i } = cause;
|
|
56
|
+
if (!Number.isSafeInteger(o) || o < 0)
|
|
57
|
+
throw new CommandBudgetExceededError(`record offset ${o} is not a non-negative integer`);
|
|
58
|
+
if (!Number.isInteger(i) || i < 0 || i >= COMMANDS_PER_RECORD) {
|
|
59
|
+
throw new CommandBudgetExceededError(`record ${o} produced command #${i}; at most ${COMMANDS_PER_RECORD} commands per record`);
|
|
60
|
+
}
|
|
61
|
+
const id = o * COMMANDS_PER_RECORD + i;
|
|
62
|
+
if (!Number.isSafeInteger(id))
|
|
63
|
+
throw new CommandBudgetExceededError(`record offset ${o} is too large for a safe JSON-RPC id`);
|
|
64
|
+
return id;
|
|
65
|
+
}
|
|
66
|
+
function isRecord(value) {
|
|
67
|
+
return typeof value === 'object' && value !== null;
|
|
68
|
+
}
|
|
69
|
+
export function isJsonRpcId(value) {
|
|
70
|
+
return value === null || typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value));
|
|
71
|
+
}
|
|
72
|
+
function isEnvelope(value) {
|
|
73
|
+
return isRecord(value) && value.jsonrpc === '2.0';
|
|
74
|
+
}
|
|
75
|
+
function isRequestMessage(value) {
|
|
76
|
+
return isEnvelope(value) && 'id' in value && typeof value.method === 'string' && isJsonRpcId(value.id);
|
|
77
|
+
}
|
|
78
|
+
function isNotificationMessage(value) {
|
|
79
|
+
return isEnvelope(value) && !('id' in value) && typeof value.method === 'string';
|
|
80
|
+
}
|
|
81
|
+
function isResponseShaped(value) {
|
|
82
|
+
return !('method' in value) && ('id' in value || 'result' in value || 'error' in value);
|
|
83
|
+
}
|
|
84
|
+
function isErrorObject(value) {
|
|
85
|
+
return isRecord(value) && typeof value.code === 'number' && Number.isInteger(value.code) && typeof value.message === 'string';
|
|
86
|
+
}
|
|
87
|
+
/** The SDK's `isResponseMessage`: exactly one of `result`/`error`, a valid id, a well-formed error. */
|
|
88
|
+
function isValidResponse(value) {
|
|
89
|
+
if (!isEnvelope(value) || 'method' in value)
|
|
90
|
+
return false;
|
|
91
|
+
if (!('id' in value) || !isJsonRpcId(value.id))
|
|
92
|
+
return false;
|
|
93
|
+
const hasResult = Object.hasOwn(value, 'result');
|
|
94
|
+
const hasError = Object.hasOwn(value, 'error');
|
|
95
|
+
if (hasResult === hasError)
|
|
96
|
+
return false;
|
|
97
|
+
return !hasError || isErrorObject(value.error);
|
|
98
|
+
}
|
|
99
|
+
/** Classify one adapter stdout line the way the SDK's stream + connection would. */
|
|
100
|
+
export function classifyLine(line) {
|
|
101
|
+
const trimmed = line.trim();
|
|
102
|
+
if (!trimmed)
|
|
103
|
+
return { type: 'empty' };
|
|
104
|
+
let message;
|
|
105
|
+
try {
|
|
106
|
+
message = JSON.parse(trimmed);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return { type: 'parse_error' };
|
|
110
|
+
}
|
|
111
|
+
if (Array.isArray(message))
|
|
112
|
+
return { type: 'batch', raw: message };
|
|
113
|
+
if (!isRecord(message))
|
|
114
|
+
return { type: 'invalid', raw: message };
|
|
115
|
+
if (isRequestMessage(message)) {
|
|
116
|
+
return { type: 'request', id: message.id, method: message.method, params: message.params, raw: message };
|
|
117
|
+
}
|
|
118
|
+
if (isNotificationMessage(message)) {
|
|
119
|
+
return { type: 'notification', method: message.method, params: message.params, raw: message };
|
|
120
|
+
}
|
|
121
|
+
if (isResponseShaped(message)) {
|
|
122
|
+
return 'id' in message ? { type: 'response', id: message.id, raw: message } : { type: 'unaddressed', raw: message };
|
|
123
|
+
}
|
|
124
|
+
return { type: 'invalid', raw: message };
|
|
125
|
+
}
|
|
126
|
+
/** Turn a response for a pending id into its settlement — `handleResponse`'s three branches. */
|
|
127
|
+
export function settlementOf(response) {
|
|
128
|
+
if (!isValidResponse(response))
|
|
129
|
+
return { outcome: 'error', error: invalidRequest(response) };
|
|
130
|
+
if (Object.hasOwn(response, 'result'))
|
|
131
|
+
return { outcome: 'result', result: response.result };
|
|
132
|
+
const { code, message, data } = response.error;
|
|
133
|
+
return { outcome: 'error', error: { code, message, data } };
|
|
134
|
+
}
|
|
135
|
+
/** `jsonRpcErrorCode(err)` for a settlement: the code an agent answered with, if a frame may carry it. */
|
|
136
|
+
export function settlementErrorCode(settlement) {
|
|
137
|
+
return settlement.outcome === 'error' ? jsonRpcErrorCodeOf(settlement.error.code) : undefined;
|
|
138
|
+
}
|
|
139
|
+
/** The key a numeric outbound id is stored under; only numbers can ever match our own ids. */
|
|
140
|
+
export function outboundKey(id) {
|
|
141
|
+
return typeof id === 'number' && Number.isSafeInteger(id) ? String(id) : undefined;
|
|
142
|
+
}
|
|
143
|
+
/** The key an inbound (agent-allocated) id is stored under — type-tagged, as the SDK's `Map` keys are. */
|
|
144
|
+
export function inboundKey(id) {
|
|
145
|
+
return JSON.stringify(id);
|
|
146
|
+
}
|
|
147
|
+
// ---- error objects, exactly as `RequestError.<factory>(…).toErrorResponse()` spells them ----------
|
|
148
|
+
export function parseError() {
|
|
149
|
+
return { code: -32700, message: 'Parse error', data: undefined };
|
|
150
|
+
}
|
|
151
|
+
export function invalidRequest(data) {
|
|
152
|
+
return { code: -32600, message: 'Invalid request', data };
|
|
153
|
+
}
|
|
154
|
+
export function methodNotFound(method) {
|
|
155
|
+
return { code: -32601, message: `"Method not found": ${method}`, data: { method } };
|
|
156
|
+
}
|
|
157
|
+
export function invalidParams(data) {
|
|
158
|
+
return { code: -32602, message: 'Invalid params', data };
|
|
159
|
+
}
|
|
160
|
+
// ---- lines, with the SDK's key order (`{jsonrpc, id, method, params}`, `{jsonrpc, id, ...result}`) ----
|
|
161
|
+
export function requestLine(id, method, params) {
|
|
162
|
+
return JSON.stringify({ jsonrpc: '2.0', id, method, params });
|
|
163
|
+
}
|
|
164
|
+
export function notificationLine(method, params) {
|
|
165
|
+
return JSON.stringify({ jsonrpc: '2.0', method, params });
|
|
166
|
+
}
|
|
167
|
+
export function resultLine(id, result) {
|
|
168
|
+
return JSON.stringify({ jsonrpc: '2.0', id, result: result ?? null });
|
|
169
|
+
}
|
|
170
|
+
export function errorLine(id, error) {
|
|
171
|
+
return JSON.stringify({ jsonrpc: '2.0', id, error });
|
|
172
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { causeKey } from './record.js';
|
|
2
|
+
export class MemoryRawLog {
|
|
3
|
+
clock;
|
|
4
|
+
stdin;
|
|
5
|
+
records = [];
|
|
6
|
+
downsByCause = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* @param clock the keeper-receive time stamped on each appended record.
|
|
9
|
+
* @param stdin where `writeDown` delivers the line, standing in for the adapter's stdin.
|
|
10
|
+
*/
|
|
11
|
+
constructor(clock, stdin = () => { }) {
|
|
12
|
+
this.clock = clock;
|
|
13
|
+
this.stdin = stdin;
|
|
14
|
+
}
|
|
15
|
+
/** The offset the next record will get (one past the last). */
|
|
16
|
+
get head() {
|
|
17
|
+
return this.records.length;
|
|
18
|
+
}
|
|
19
|
+
/** The spawn this "keeper" runs now (from its own `proc` records), or `null`. */
|
|
20
|
+
running = null;
|
|
21
|
+
append(entry) {
|
|
22
|
+
const record = { ...entry, o: this.records.length, t: this.clock() };
|
|
23
|
+
this.records.push(record);
|
|
24
|
+
if (record.kind === 'proc')
|
|
25
|
+
this.running = record.proc.type === 'spawn' ? record.proc.spawnId : null;
|
|
26
|
+
if (record.kind === 'down' && record.annotation)
|
|
27
|
+
this.downsByCause.set(causeKey(record.annotation.cause), record);
|
|
28
|
+
return record;
|
|
29
|
+
}
|
|
30
|
+
/** Records with `o >= from`, at most `limit` of them, in order. */
|
|
31
|
+
read(from, limit = Number.POSITIVE_INFINITY) {
|
|
32
|
+
return this.records.slice(Math.max(0, from), Math.min(this.records.length, Math.max(0, from) + limit));
|
|
33
|
+
}
|
|
34
|
+
hasDown(cause) {
|
|
35
|
+
return this.downsByCause.has(causeKey(cause));
|
|
36
|
+
}
|
|
37
|
+
writeDown(command) {
|
|
38
|
+
if (command.spawnId !== this.running)
|
|
39
|
+
return 'refused';
|
|
40
|
+
this.stdin(command.line);
|
|
41
|
+
this.append({
|
|
42
|
+
kind: 'down',
|
|
43
|
+
line: command.line,
|
|
44
|
+
annotation: { ...command.annotation, cause: command.cause, spawnId: command.spawnId },
|
|
45
|
+
...(command.redact !== undefined ? { redact: command.redact } : {}),
|
|
46
|
+
});
|
|
47
|
+
return 'written';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission-origin graph (Task 17a, moved here in 17b): which native sessions may ask for
|
|
3
|
+
* permission — the root, and children with a complete `subagent_spawned` path to it
|
|
4
|
+
* (`AcpSession.observePermissionOrigin`). Fed by every mapped `subagent_spawned` event, live or load
|
|
5
|
+
* history (`observeReplayEvent`), in log order.
|
|
6
|
+
*/
|
|
7
|
+
import { getOwn, putOwn, UNRESOLVED_ORIGIN_LIMIT } from './state.js';
|
|
8
|
+
/**
|
|
9
|
+
* `AcpSession.observePermissionOrigin`, over plain records. `root` is the bound root session, or —
|
|
10
|
+
* inside a load's history window, before the load response binds it — the session being loaded.
|
|
11
|
+
*/
|
|
12
|
+
export function observeSubagentSpawned(state, parent, child, root = state.rootSessionId) {
|
|
13
|
+
const reject = () => { state.diagnostics.rejectedRelations += 1; };
|
|
14
|
+
if (typeof parent !== 'string' || !parent.trim() || typeof child !== 'string' || !child.trim())
|
|
15
|
+
return reject();
|
|
16
|
+
if (child === parent || child === root)
|
|
17
|
+
return reject();
|
|
18
|
+
const existing = getOwn(state.permissionParents, child);
|
|
19
|
+
if (existing !== undefined) {
|
|
20
|
+
if (existing !== parent)
|
|
21
|
+
reject();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (let ancestor = parent; ancestor !== undefined; ancestor = getOwn(state.permissionParents, ancestor)) {
|
|
25
|
+
if (ancestor === child)
|
|
26
|
+
return reject();
|
|
27
|
+
}
|
|
28
|
+
const trusted = parent === root || getOwn(state.permissionOrigins, parent) === true;
|
|
29
|
+
if (!trusted && Object.keys(state.unresolvedPermissionOrigins).length >= UNRESOLVED_ORIGIN_LIMIT)
|
|
30
|
+
return reject();
|
|
31
|
+
putOwn(state.permissionParents, child, parent);
|
|
32
|
+
if (!trusted) {
|
|
33
|
+
putOwn(state.unresolvedPermissionOrigins, child, true);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
putOwn(state.permissionOrigins, child, true);
|
|
37
|
+
let promoted;
|
|
38
|
+
do {
|
|
39
|
+
promoted = false;
|
|
40
|
+
for (const candidate of Object.keys(state.unresolvedPermissionOrigins)) {
|
|
41
|
+
if (getOwn(state.permissionOrigins, getOwn(state.permissionParents, candidate))) {
|
|
42
|
+
delete state.unresolvedPermissionOrigins[candidate];
|
|
43
|
+
putOwn(state.permissionOrigins, candidate, true);
|
|
44
|
+
promoted = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} while (promoted);
|
|
48
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one record produced: its down commands (each with its `(o, i)` cause) and its frames (each with
|
|
3
|
+
* its `(o, i)` src). Shared by `step.ts` (connection layer) and `session.ts` (turns and cards).
|
|
4
|
+
*/
|
|
5
|
+
import { CommandBudgetExceededError, MAX_COMMANDS_PER_RECORD, errorLine, notificationLine, outboundRequestId, requestLine, resultLine, } from './jsonrpc.js';
|
|
6
|
+
/** A command was produced with no running adapter process to address it to — a processor bug. */
|
|
7
|
+
export class CommandWithoutSpawnError extends Error {
|
|
8
|
+
}
|
|
9
|
+
export class RecordOutput {
|
|
10
|
+
o;
|
|
11
|
+
spawnId;
|
|
12
|
+
commands = [];
|
|
13
|
+
frames = [];
|
|
14
|
+
/**
|
|
15
|
+
* @param spawnId the running spawn **when a command is pushed** (read then, not at construction: a
|
|
16
|
+
* `spawn` record closes the old process and addresses its `initialize` to the new one).
|
|
17
|
+
*/
|
|
18
|
+
constructor(o, spawnId = () => undefined) {
|
|
19
|
+
this.o = o;
|
|
20
|
+
this.spawnId = spawnId;
|
|
21
|
+
}
|
|
22
|
+
push(line, extras = {}) {
|
|
23
|
+
const i = this.commands.length;
|
|
24
|
+
if (i >= MAX_COMMANDS_PER_RECORD) {
|
|
25
|
+
throw new CommandBudgetExceededError(`record ${this.o} produced more than ${MAX_COMMANDS_PER_RECORD} commands`);
|
|
26
|
+
}
|
|
27
|
+
const spawnId = this.spawnId();
|
|
28
|
+
if (spawnId === undefined)
|
|
29
|
+
throw new CommandWithoutSpawnError(`record ${this.o} produced a command with no running adapter process`);
|
|
30
|
+
const cause = { o: this.o, i };
|
|
31
|
+
this.commands.push({
|
|
32
|
+
cause,
|
|
33
|
+
spawnId,
|
|
34
|
+
line: line(i),
|
|
35
|
+
...(extras.annotation !== undefined ? { annotation: extras.annotation } : {}),
|
|
36
|
+
...(extras.fill !== undefined ? { fill: extras.fill } : {}),
|
|
37
|
+
...(extras.redact !== undefined ? { redact: extras.redact } : {}),
|
|
38
|
+
});
|
|
39
|
+
return cause;
|
|
40
|
+
}
|
|
41
|
+
/** Throws `CommandBudgetExceededError` once this record already produced 16 commands (`outboundRequestId`). */
|
|
42
|
+
request(method, params, extras) {
|
|
43
|
+
let id = -1;
|
|
44
|
+
const cause = this.push((index) => {
|
|
45
|
+
id = outboundRequestId({ o: this.o, i: index });
|
|
46
|
+
return requestLine(id, method, params);
|
|
47
|
+
}, extras);
|
|
48
|
+
return { id, cause };
|
|
49
|
+
}
|
|
50
|
+
notify(method, params) {
|
|
51
|
+
this.push(() => notificationLine(method, params));
|
|
52
|
+
}
|
|
53
|
+
result(id, result) {
|
|
54
|
+
this.push(() => resultLine(id, result));
|
|
55
|
+
}
|
|
56
|
+
error(id, error) {
|
|
57
|
+
this.push(() => errorLine(id, error));
|
|
58
|
+
}
|
|
59
|
+
/** A journaled frame; the caller has already numbered it. */
|
|
60
|
+
frame(frame) {
|
|
61
|
+
this.frames.push({ journaled: true, frame, src: { o: this.o, i: this.frames.length } });
|
|
62
|
+
}
|
|
63
|
+
/** An unnumbered frame: a level, an RPC reply, or a `processor.*` frame for the daemon. */
|
|
64
|
+
unjournaled(frame) {
|
|
65
|
+
this.frames.push({ journaled: false, frame, src: { o: this.o, i: this.frames.length } });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AGENT_WIRE_RUNTIME_PROFILE_VERSION, AGENT_WIRE_SCOPED_ORIGIN_VERSION, AGENT_WIRE_SESSION_AUTH_METHODS_VERSION, AGENT_WIRE_SESSION_STATE_VERSION, } from '../../protocol/agent.js';
|
|
2
|
+
function withSessionRef(frame, sessionRef) {
|
|
3
|
+
const { t, ...rest } = frame;
|
|
4
|
+
return { t, sessionRef, ...rest };
|
|
5
|
+
}
|
|
6
|
+
export function projectFrame(output, context) {
|
|
7
|
+
const frame = output.frame;
|
|
8
|
+
const t = frame.t;
|
|
9
|
+
if (typeof t !== 'string' || t.startsWith('processor.'))
|
|
10
|
+
return { frame: null };
|
|
11
|
+
const wire = context.serverAgentWire;
|
|
12
|
+
switch (t) {
|
|
13
|
+
case 'agent_event': {
|
|
14
|
+
if (frame.acpSessionId !== undefined && wire < AGENT_WIRE_SCOPED_ORIGIN_VERSION) {
|
|
15
|
+
const payload = frame.payload;
|
|
16
|
+
if (frame.eventName === 'permission_request' && typeof payload?.requestId === 'string') {
|
|
17
|
+
return { frame: null, note: { type: 'agent_answer_undeliverable', requestId: payload.requestId } };
|
|
18
|
+
}
|
|
19
|
+
return { frame: null };
|
|
20
|
+
}
|
|
21
|
+
return { frame: withSessionRef(frame, context.sessionRef) };
|
|
22
|
+
}
|
|
23
|
+
case 'agent_turn_done': {
|
|
24
|
+
const { agentRefused, errorCode, ...rest } = frame;
|
|
25
|
+
const projected = withSessionRef(rest, context.sessionRef);
|
|
26
|
+
if (agentRefused === true && rest.status === 'failed' && wire >= AGENT_WIRE_SESSION_AUTH_METHODS_VERSION) {
|
|
27
|
+
if (context.authMethods)
|
|
28
|
+
projected.authMethods = context.authMethods;
|
|
29
|
+
if (typeof errorCode === 'number')
|
|
30
|
+
projected.errorCode = errorCode;
|
|
31
|
+
}
|
|
32
|
+
return { frame: projected };
|
|
33
|
+
}
|
|
34
|
+
case 'agent_session_state':
|
|
35
|
+
return { frame: wire >= AGENT_WIRE_SESSION_STATE_VERSION ? withSessionRef(frame, context.sessionRef) : null };
|
|
36
|
+
case 'agent_runtime_profile':
|
|
37
|
+
return { frame: wire >= AGENT_WIRE_RUNTIME_PROFILE_VERSION ? withSessionRef(frame, context.sessionRef) : null };
|
|
38
|
+
default:
|
|
39
|
+
// RPC replies carry the server's request `id`, not a `sessionRef`.
|
|
40
|
+
return { frame: { ...frame } };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The raw records the ACP processor folds over (spec 2026-10-11 §5.3), and their on-stream spelling.
|
|
3
|
+
*
|
|
4
|
+
* ## Two spellings, one record
|
|
5
|
+
*
|
|
6
|
+
* keeper-acp stores each record as one line of a kit stream (`{o, t, k, …}`, §3.4) with short keys
|
|
7
|
+
* (`up{l}`, `down{l, a, redact?}`, `proc{kind, …}`, `note{n}`, `err{l}` — the names in
|
|
8
|
+
* interface-contracts). The processor works on the long, typed spelling below. {@link encodeStreamRecord}
|
|
9
|
+
* and {@link decodeStreamRecord} are the only bridge, so neither side has to know the other's names.
|
|
10
|
+
*
|
|
11
|
+
* ## What each kind is
|
|
12
|
+
*
|
|
13
|
+
* - `up`: one line the adapter wrote to stdout — a JSON-RPC message, verbatim.
|
|
14
|
+
* - `down`: one line written to the adapter's stdin. `annotation.cause = {o, i}` names the record
|
|
15
|
+
* (and the command index within it) whose processing produced the line; that is what makes
|
|
16
|
+
* commands idempotent (`commands.ts`). `annotation` is otherwise opaque to the keeper. `redact`
|
|
17
|
+
* lists the `secretRef`s whose values were replaced in the *logged* copy of the line — stdin got
|
|
18
|
+
* the real value, the log never does (§13). It names refs, never secrets.
|
|
19
|
+
* - `proc`: keeper-synthesised process events — `spawn{spawnId, pid, pgid, startTime}` and
|
|
20
|
+
* `exit{code, signal, stderrTail, reason?}`.
|
|
21
|
+
* - `note`: an opaque record the daemon wrote (server commands, its own decisions). Notes carry no
|
|
22
|
+
* secrets and no large payloads (§5.4): secrets by `secretRef`, large blocks by reference to the
|
|
23
|
+
* one `down` record that carries them.
|
|
24
|
+
* - `err`: one already-redacted stderr line.
|
|
25
|
+
*
|
|
26
|
+
* `o` is the record's offset in the session's raw stream (monotonic, gap-free per stream) and `t` is
|
|
27
|
+
* the keeper's receive time in epoch ms — the **only** clock the processor may read.
|
|
28
|
+
*/
|
|
29
|
+
export function sameCause(a, b) {
|
|
30
|
+
return a !== undefined && a.o === b.o && a.i === b.i;
|
|
31
|
+
}
|
|
32
|
+
/** The map key for a cause; `o` and `i` are integers so this is injective. */
|
|
33
|
+
export function causeKey(cause) {
|
|
34
|
+
return `${cause.o}:${cause.i}`;
|
|
35
|
+
}
|
|
36
|
+
export class MalformedRecordError extends Error {
|
|
37
|
+
}
|
|
38
|
+
export function encodeStreamRecord(record) {
|
|
39
|
+
const { o, t } = record;
|
|
40
|
+
switch (record.kind) {
|
|
41
|
+
case 'up':
|
|
42
|
+
return { o, t, k: 'up', l: record.line };
|
|
43
|
+
case 'down':
|
|
44
|
+
return {
|
|
45
|
+
o, t, k: 'down', l: record.line,
|
|
46
|
+
...(record.annotation !== undefined ? { a: record.annotation } : {}),
|
|
47
|
+
...(record.redact !== undefined ? { redact: record.redact } : {}),
|
|
48
|
+
};
|
|
49
|
+
case 'proc': {
|
|
50
|
+
const { type, ...rest } = record.proc;
|
|
51
|
+
return { o, t, k: 'proc', kind: type, ...rest };
|
|
52
|
+
}
|
|
53
|
+
case 'note':
|
|
54
|
+
return { o, t, k: 'note', n: record.note };
|
|
55
|
+
case 'err':
|
|
56
|
+
return { o, t, k: 'err', l: record.line };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isRecord(value) {
|
|
60
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
61
|
+
}
|
|
62
|
+
function str(value, what) {
|
|
63
|
+
if (typeof value !== 'string')
|
|
64
|
+
throw new MalformedRecordError(`${what} is not a string`);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
/** Inverse of {@link encodeStreamRecord}. Throws {@link MalformedRecordError} on anything else. */
|
|
68
|
+
export function decodeStreamRecord(value) {
|
|
69
|
+
if (!isRecord(value))
|
|
70
|
+
throw new MalformedRecordError('record is not an object');
|
|
71
|
+
const { o, t, k } = value;
|
|
72
|
+
if (typeof o !== 'number' || !Number.isSafeInteger(o) || o < 0)
|
|
73
|
+
throw new MalformedRecordError('record offset is not a non-negative integer');
|
|
74
|
+
if (typeof t !== 'number' || !Number.isFinite(t))
|
|
75
|
+
throw new MalformedRecordError('record time is not a number');
|
|
76
|
+
switch (k) {
|
|
77
|
+
case 'up':
|
|
78
|
+
return { o, t, kind: 'up', line: str(value.l, 'up.l') };
|
|
79
|
+
case 'err':
|
|
80
|
+
return { o, t, kind: 'err', line: str(value.l, 'err.l') };
|
|
81
|
+
case 'down': {
|
|
82
|
+
const a = value.a;
|
|
83
|
+
let annotation;
|
|
84
|
+
if (a !== undefined) {
|
|
85
|
+
if (!isRecord(a) || !isRecord(a.cause) || !Number.isSafeInteger(a.cause.o) || !Number.isSafeInteger(a.cause.i)) {
|
|
86
|
+
throw new MalformedRecordError('down.a.cause is not {o, i}');
|
|
87
|
+
}
|
|
88
|
+
annotation = a;
|
|
89
|
+
}
|
|
90
|
+
const redact = value.redact;
|
|
91
|
+
if (redact !== undefined && !(Array.isArray(redact) && redact.every((r) => typeof r === 'string'))) {
|
|
92
|
+
throw new MalformedRecordError('down.redact is not a string array');
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
o, t, kind: 'down', line: str(value.l, 'down.l'),
|
|
96
|
+
...(annotation !== undefined ? { annotation } : {}),
|
|
97
|
+
...(redact !== undefined ? { redact: redact } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
case 'proc': {
|
|
101
|
+
if (value.kind === 'spawn') {
|
|
102
|
+
const { spawnId, pid, pgid, startTime } = value;
|
|
103
|
+
if (typeof spawnId !== 'string' || typeof pid !== 'number' || typeof pgid !== 'number'
|
|
104
|
+
|| (typeof startTime !== 'string' && typeof startTime !== 'number')) {
|
|
105
|
+
throw new MalformedRecordError('proc spawn is missing spawnId/pid/pgid/startTime');
|
|
106
|
+
}
|
|
107
|
+
return { o, t, kind: 'proc', proc: { type: 'spawn', spawnId, pid, pgid, startTime } };
|
|
108
|
+
}
|
|
109
|
+
if (value.kind === 'exit') {
|
|
110
|
+
const { code, signal, stderrTail, reason } = value;
|
|
111
|
+
if ((code !== null && typeof code !== 'number') || (signal !== null && typeof signal !== 'string')
|
|
112
|
+
|| typeof stderrTail !== 'string' || (reason !== undefined && typeof reason !== 'string')) {
|
|
113
|
+
throw new MalformedRecordError('proc exit is missing code/signal/stderrTail');
|
|
114
|
+
}
|
|
115
|
+
return { o, t, kind: 'proc', proc: { type: 'exit', code, signal, stderrTail, ...(reason !== undefined ? { reason } : {}) } };
|
|
116
|
+
}
|
|
117
|
+
throw new MalformedRecordError(`unknown proc kind ${JSON.stringify(value.kind)}`);
|
|
118
|
+
}
|
|
119
|
+
case 'note': {
|
|
120
|
+
const n = value.n;
|
|
121
|
+
if (!isRecord(n) || typeof n.type !== 'string')
|
|
122
|
+
throw new MalformedRecordError('note.n has no string type');
|
|
123
|
+
return { o, t, kind: 'note', note: n };
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
throw new MalformedRecordError(`unknown record kind ${JSON.stringify(k)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const connectionGeneration = (spawnId) => `session:${spawnId}`;
|
|
2
|
+
import { claudeActivityRaw } from '../activityReporting.js';
|
|
3
|
+
import { isRecord } from './emit.js';
|
|
4
|
+
import { initialRuntime } from './state.js';
|
|
5
|
+
function opened(state) {
|
|
6
|
+
return state.open.status === 'open';
|
|
7
|
+
}
|
|
8
|
+
/** One agent-reported activity push (vendor-verbatim): a level, never numbered. */
|
|
9
|
+
export function reportActivity(state, out, raw, t) {
|
|
10
|
+
if (!opened(state))
|
|
11
|
+
return;
|
|
12
|
+
state.activity = { raw, t };
|
|
13
|
+
out.unjournaled({ t: 'agent_session_state', vendor: vendorOf(state), raw });
|
|
14
|
+
}
|
|
15
|
+
/** `InitializeResponse.agentInfo.name` (`agentSessions.ts`'s `vendor`), `''` when the agent named none. */
|
|
16
|
+
export function vendorOf(state) {
|
|
17
|
+
return state.initialize.status === 'ready' ? state.initialize.facts.agentInfo?.name ?? '' : '';
|
|
18
|
+
}
|
|
19
|
+
/** `_claude/sdkMessage` — only the `session_state_changed` system message is activity. */
|
|
20
|
+
export function onClaudeSdkMessage(state, out, params, t) {
|
|
21
|
+
const raw = claudeActivityRaw(isRecord(params) ? params : {});
|
|
22
|
+
if (raw)
|
|
23
|
+
reportActivity(state, out, raw, t);
|
|
24
|
+
}
|
|
25
|
+
/** `agent_opened.activityRaw`: the last push, if any. */
|
|
26
|
+
export function activityRaw(state) {
|
|
27
|
+
return state.activity?.raw;
|
|
28
|
+
}
|
|
29
|
+
/** The current adapter process's `spawnId` (running, or the one that exited). */
|
|
30
|
+
export function currentSpawnId(state) {
|
|
31
|
+
return state.process.status === 'running' ? state.process.spawn.spawnId
|
|
32
|
+
: state.process.status === 'exited' ? state.process.spawnId : undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `AgentRuntimeDiagnostics.callbacks` — the snapshot `AcpSession.publishCallbacks` last published,
|
|
36
|
+
* recomputed from state (every change to the cards it lists publishes, so the two are the same), or
|
|
37
|
+
* `undefined` before the first publish. Pending and settled are listed only while `complete`.
|
|
38
|
+
*/
|
|
39
|
+
export function callbackSnapshot(state) {
|
|
40
|
+
const { complete, rootSessionId } = state.callbacks;
|
|
41
|
+
const spawnId = currentSpawnId(state);
|
|
42
|
+
if (rootSessionId === null || spawnId === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
return {
|
|
45
|
+
rootSessionId, connectionGeneration: connectionGeneration(spawnId), complete,
|
|
46
|
+
pending: complete ? Object.values(state.permissions).map((card) => ({
|
|
47
|
+
requestId: card.requestId, originNativeSessionId: card.origin, rpcRequestId: card.rpcRequestId,
|
|
48
|
+
})) : [],
|
|
49
|
+
settled: complete ? state.settledPermissions.map(({ requestId, reason }) => ({ requestId, reason })) : [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The diagnostics as `agent_runtime_profile`/`agent_opened.runtimeDiagnostics` carry them, or `undefined` before a spawn and its `agent_launch` note. */
|
|
53
|
+
export function runtimeDiagnostics(state) {
|
|
54
|
+
const spawnId = currentSpawnId(state);
|
|
55
|
+
const { revision, available, launch } = state.runtime;
|
|
56
|
+
if (spawnId === undefined || launch === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
const callbacks = callbackSnapshot(state);
|
|
59
|
+
return { generation: spawnId, revision, available, launch,
|
|
60
|
+
...(callbacks !== undefined ? { callbacks } : {}) };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* `AcpSession.publishCallbacks` → `connectAgent`'s `publishCallbacks`: the snapshot changed, the
|
|
64
|
+
* revision goes up and the level is re-sent. Ignored before a root is known (today: a snapshot for a
|
|
65
|
+
* session the connection has not bound). Callers change the cards first, then publish.
|
|
66
|
+
*/
|
|
67
|
+
export function publishCallbacks(state, out) {
|
|
68
|
+
const root = state.rootSessionId ?? state.history?.rootSessionId ?? null;
|
|
69
|
+
if (root === null)
|
|
70
|
+
return;
|
|
71
|
+
state.callbacks.rootSessionId = root;
|
|
72
|
+
state.runtime.revision += 1;
|
|
73
|
+
changed(state, out);
|
|
74
|
+
}
|
|
75
|
+
/** A diagnostics change: published (unnumbered) once the session is open, as `onRuntimeDiagnostics` is. */
|
|
76
|
+
function changed(state, out) {
|
|
77
|
+
if (!opened(state))
|
|
78
|
+
return;
|
|
79
|
+
const diagnostics = runtimeDiagnostics(state);
|
|
80
|
+
if (diagnostics)
|
|
81
|
+
out.unjournaled({ t: 'agent_runtime_profile', diagnostics: JSON.parse(JSON.stringify(diagnostics)) });
|
|
82
|
+
}
|
|
83
|
+
export function resetRuntime(state) {
|
|
84
|
+
state.runtime = initialRuntime();
|
|
85
|
+
state.activity = null;
|
|
86
|
+
}
|
|
87
|
+
/** `agent_launch{launch}` — what `connectAgent` computes at spawn (`launchSource`, `findOnPath`, platform). */
|
|
88
|
+
export function onLaunchNote(state, launch) {
|
|
89
|
+
if (!isRecord(launch) || state.process.status !== 'running')
|
|
90
|
+
return false;
|
|
91
|
+
const { source, executable, platform, architecture, transport } = launch;
|
|
92
|
+
if (!['command-override', 'registry', 'registry-fallback', 'direct'].includes(source) || typeof executable !== 'string'
|
|
93
|
+
|| typeof platform !== 'string' || typeof architecture !== 'string' || transport !== 'stdio')
|
|
94
|
+
return false;
|
|
95
|
+
state.runtime.launch = { source: source, executable: executable.slice(0, 4096), platform, architecture, transport };
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
/** The child exited: unavailable, profile no longer observed (`child.once('exit')` in `connectAgent`). */
|
|
99
|
+
export function onProcessExit(state, out) {
|
|
100
|
+
const runtime = state.runtime;
|
|
101
|
+
runtime.revision += 1;
|
|
102
|
+
runtime.available = false;
|
|
103
|
+
changed(state, out);
|
|
104
|
+
}
|
|
105
|
+
/** The root just bound (`bindDraftUpdates`' diagnostics half): drop a profile for another root, apply a pending one. */
|