@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,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitise a real ACP capture into a committable raw-record fixture (Task 17d).
|
|
3
|
+
*
|
|
4
|
+
* Input: either the legacy capture spelling `{ms, dir: 'in'|'out'|'note'|'stderr'|…, msg}` (the
|
|
5
|
+
* `tmp/*.jsonl` captures) or the kit-stream spelling `{o, t, k, …}` that `scripts/acp-capture.mjs`
|
|
6
|
+
* now writes. Output: kit-stream records (`encodeStreamRecord`'s spelling), one per line —
|
|
7
|
+
* a synthetic `proc spawn` first (`spawnId: 'capture'`, pid 0), then `up` / `down` / `err`, and the
|
|
8
|
+
* capture's `proc exit` (stderr tail placeholdered) if it has one. `in` → `up`, `out` → `down`, `stderr` → one `err`
|
|
9
|
+
* per line, `ms` → `t`, `o` renumbered gap-free. The capture tool's own bookkeeping (`note`,
|
|
10
|
+
* `fatal`) is dropped: it is not wire traffic and it repeats prompt results.
|
|
11
|
+
*
|
|
12
|
+
* What is kept verbatim: every JSON-RPC structural field and the record order — `jsonrpc`, ids,
|
|
13
|
+
* methods, update kinds, tool kinds, statuses, stop reasons, option ids, mode / model / config
|
|
14
|
+
* values, versions and agent names (the processor and the live path branch on them), numbers and
|
|
15
|
+
* booleans, object keys, and line (chunk) boundaries.
|
|
16
|
+
*
|
|
17
|
+
* What is replaced:
|
|
18
|
+
* - every other string (prompt and message text, titles, commands, outputs, paths, descriptions,
|
|
19
|
+
* stderr) by a placeholder of the same length and shape: letters → `x`, digits → `0`, `@` → `_`;
|
|
20
|
+
* whitespace and punctuation stay, so line structure and length class survive;
|
|
21
|
+
* - id-like values (keys `id`, `uuid`, `…Id`, `…_id`, `…Ids`) token by token: a random-looking token
|
|
22
|
+
* (≥ 6 chars with a digit) is mapped injectively to a synthetic one (UUIDs to synthetic UUIDs), so
|
|
23
|
+
* every reference to the same id stays equal and distinct ids stay distinct;
|
|
24
|
+
* - in the strings kept verbatim (and in keys), defensively: home paths, user and host names, emails,
|
|
25
|
+
* `sk-` / `ghp_` / `Bearer` tokens and long base64 / hex blobs.
|
|
26
|
+
*
|
|
27
|
+
* Deterministic: the same input and options always give the same output.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Keys whose string values are structural vocabulary, kept anywhere (after the defensive scrub of
|
|
31
|
+
* {@link Scrubber.verbatim}). `name` / `value` are deliberately **not** here: they are kept only in
|
|
32
|
+
* the contexts listed in {@link VOCABULARY_CONTEXTS} (ACP also spells env vars and headers as
|
|
33
|
+
* `[{name, value}]`).
|
|
34
|
+
*/
|
|
35
|
+
export const VERBATIM_KEYS = new Set([
|
|
36
|
+
'jsonrpc', 'method', 'sessionUpdate', 'kind', 'status', 'type', 'subtype', 'state', 'stopReason', 'outcome',
|
|
37
|
+
'toolName', 'tool_name', 'version', 'category', 'currency', 'phase', 'presentation', 'provider',
|
|
38
|
+
'controlMethod', 'actions', 'capabilities', 'task_type', 'taskType', 'subagent_type', 'priority', 'role', 'mimeType',
|
|
39
|
+
'protocolVersion', 'transport', 'platform', 'architecture', 'source', 'mode', 'action',
|
|
40
|
+
]);
|
|
41
|
+
/** Vocabulary ids (enumerations an agent offers, not identities): kept, never id-mapped. */
|
|
42
|
+
export const VOCABULARY_ID_KEYS = new Set(['optionId', 'modeId', 'currentModeId', 'configId']);
|
|
43
|
+
/**
|
|
44
|
+
* Where `id` / `name` / `value` / `currentValue` / `resetValue` are vocabulary and kept: the agent's
|
|
45
|
+
* identity, config options (except model lists, see {@link MODEL_KEYS}), modes, auth methods.
|
|
46
|
+
* Everywhere else — permission option labels, tool names, subagent names, env, headers — they are
|
|
47
|
+
* placeholdered (or id-mapped).
|
|
48
|
+
*/
|
|
49
|
+
export const VOCABULARY_CONTEXTS = new Set(['agentInfo', 'configOptions', 'availableModes', 'authMethods']);
|
|
50
|
+
const VOCABULARY_FIELDS = new Set(['id', 'name', 'value', 'currentValue', 'resetValue']);
|
|
51
|
+
/** Anything below these keys is secret-bearing (MCP server launch, env, headers): every string is placeholdered. */
|
|
52
|
+
export const SECRET_CONTEXTS = new Set(['mcpServers', 'env', 'headers']);
|
|
53
|
+
const SECRET_KEY = /authorization|api[_-]?key|token|secret|password|cookie/i;
|
|
54
|
+
/**
|
|
55
|
+
* Model identifiers fingerprint the user's provider setup: they are mapped (consistently, one table)
|
|
56
|
+
* to `model-NNNN`. Also mapped: the option values of a config option whose `category` or `id` is
|
|
57
|
+
* `model`, and a `session/set_config_option` value for such an option. Their labels are placeholdered.
|
|
58
|
+
*/
|
|
59
|
+
export const MODEL_KEYS = new Set(['modelId', 'currentModelId', 'model', 'resolvedModel']);
|
|
60
|
+
/** Subtrees whose strings are all placeholdered, names included (installed skills / plugins fingerprint the user). */
|
|
61
|
+
export const OPAQUE_CONTEXTS = new Set(['availableCommands']);
|
|
62
|
+
const ID_KEY = /^(id|uuid)$|Id$|_id$|Ids$/;
|
|
63
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
64
|
+
/** Agent id prefixes kept in front of a mapped id (structure, not identity). */
|
|
65
|
+
const ID_PREFIX = /^(toolu|srvtoolu|msg|call|exec|rs|req|run|task|agent|child)[_-]/;
|
|
66
|
+
/** Letters → `x`, digits → `0`, `@` → `_`: same length, same whitespace and punctuation. */
|
|
67
|
+
export function placeholder(text) {
|
|
68
|
+
return text.replace(/\p{L}|\p{M}/gu, 'x').replace(/\p{N}/gu, '0').replace(/@/g, '_');
|
|
69
|
+
}
|
|
70
|
+
function escapeRegExp(text) {
|
|
71
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
72
|
+
}
|
|
73
|
+
const ROOT = { secret: false, opaque: false, vocabulary: false, modelOption: false };
|
|
74
|
+
class Scrubber {
|
|
75
|
+
#ids = new Map();
|
|
76
|
+
#models = new Map();
|
|
77
|
+
/** Config option ids whose values are models (a later `set_config_option` maps its value the same way). */
|
|
78
|
+
#modelConfigs = new Set();
|
|
79
|
+
#words;
|
|
80
|
+
constructor(words) {
|
|
81
|
+
const unique = [...new Set(words.filter((w) => w.length >= 3))].sort((a, b) => b.length - a.length);
|
|
82
|
+
this.#words = unique.length > 0 ? new RegExp(unique.map(escapeRegExp).join('|'), 'gi') : null;
|
|
83
|
+
}
|
|
84
|
+
/** Kept strings: only the defensive patterns are replaced. */
|
|
85
|
+
verbatim(text) {
|
|
86
|
+
let out = text
|
|
87
|
+
.replace(/\/(Users|home)\/[^/\s"'`]+/g, '/u/user')
|
|
88
|
+
.replace(/-(Users|home)-[A-Za-z0-9._]+/g, '-u-user')
|
|
89
|
+
.replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)+/g, '<email>')
|
|
90
|
+
.replace(/\bBearer\s+\S+/gi, 'Bearer <redacted>').replace(/\bBearer\b/gi, '<auth>')
|
|
91
|
+
.replace(/\bsk-[A-Za-z0-9_-]+/g, '<redacted>')
|
|
92
|
+
.replace(/\bgh[pousr]_[A-Za-z0-9]+/g, '<redacted>')
|
|
93
|
+
.replace(/[A-Za-z0-9+/_=-]{40,}/g, (blob) => `<blob:${blob.length}>`);
|
|
94
|
+
if (this.#words)
|
|
95
|
+
out = out.replace(this.#words, 'user');
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* An identity: every `:`-separated segment that is not a plain number is mapped injectively (with or
|
|
100
|
+
* without digits — `natureserver-session` is an identity too). A UUID maps to a synthetic UUID; a
|
|
101
|
+
* known agent prefix (`toolu_`, `exec-`, …) is kept in front.
|
|
102
|
+
*/
|
|
103
|
+
id(text) {
|
|
104
|
+
if (UUID.test(text))
|
|
105
|
+
return this.#uuid(text);
|
|
106
|
+
return text.split(':').map((segment) => (segment === '' || /^\d+$/.test(segment) ? segment : this.#segment(segment))).join(':');
|
|
107
|
+
}
|
|
108
|
+
#segment(segment) {
|
|
109
|
+
if (UUID.test(segment))
|
|
110
|
+
return this.#uuid(segment);
|
|
111
|
+
const known = this.#ids.get(segment);
|
|
112
|
+
if (known)
|
|
113
|
+
return known;
|
|
114
|
+
const prefix = ID_PREFIX.exec(segment)?.[0] ?? '';
|
|
115
|
+
const mapped = `${prefix}id${String(this.#ids.size + 1).padStart(4, '0')}`;
|
|
116
|
+
this.#ids.set(segment, mapped);
|
|
117
|
+
return mapped;
|
|
118
|
+
}
|
|
119
|
+
#uuid(token) {
|
|
120
|
+
const key = token.toLowerCase();
|
|
121
|
+
const known = this.#ids.get(key);
|
|
122
|
+
if (known)
|
|
123
|
+
return known;
|
|
124
|
+
const mapped = `00000000-0000-4000-8000-${String(this.#ids.size + 1).padStart(12, '0')}`;
|
|
125
|
+
this.#ids.set(key, mapped);
|
|
126
|
+
return mapped;
|
|
127
|
+
}
|
|
128
|
+
model(text) {
|
|
129
|
+
const known = this.#models.get(text);
|
|
130
|
+
if (known)
|
|
131
|
+
return known;
|
|
132
|
+
const mapped = `model-${String(this.#models.size + 1).padStart(4, '0')}`;
|
|
133
|
+
this.#models.set(text, mapped);
|
|
134
|
+
return mapped;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A side-file row (a Qoder native transcript row the background terminal repair reads, Task 30a): identities are mapped with the
|
|
138
|
+
* capture's table (so the row still names the capture's session and tool calls), `cwd` is placeholdered exactly as the capture's
|
|
139
|
+
* `cwd` was (the repair compares them), and free text keeps its words — the repair parses it (`<task-notification>` and its
|
|
140
|
+
* tags) — with every capture identity inside it rewritten and only the defensive patterns of {@link verbatim} applied. Such text
|
|
141
|
+
* is model output: review it before committing.
|
|
142
|
+
*/
|
|
143
|
+
side(value, key) {
|
|
144
|
+
if (typeof value === 'string') {
|
|
145
|
+
if (key === 'cwd')
|
|
146
|
+
return placeholder(value);
|
|
147
|
+
if (ID_KEY.test(key))
|
|
148
|
+
return this.id(value);
|
|
149
|
+
return this.verbatim(this.#knownIdsIn(value));
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(value))
|
|
152
|
+
return value.map((item) => this.side(item, key));
|
|
153
|
+
if (value !== null && typeof value === 'object') {
|
|
154
|
+
const out = {};
|
|
155
|
+
for (const [k, v] of Object.entries(value))
|
|
156
|
+
out[this.verbatim(k)] = this.side(v, k);
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
/** Every identity of the table (≥ 6 chars, longest first) rewritten where it appears inside `text`. */
|
|
162
|
+
#knownIdsIn(text) {
|
|
163
|
+
const known = [...this.#ids.entries()].filter(([original]) => original.length >= 6).sort((a, b) => b[0].length - a[0].length);
|
|
164
|
+
let out = text;
|
|
165
|
+
for (const [original, mapped] of known)
|
|
166
|
+
out = out.split(original).join(mapped);
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
/** A string that is not vocabulary: an identity already seen elsewhere keeps its mapping, anything else is placeholdered. */
|
|
170
|
+
#opaque(text) {
|
|
171
|
+
if (text.length >= 6) {
|
|
172
|
+
const known = this.#ids.get(text) ?? this.#ids.get(text.toLowerCase());
|
|
173
|
+
if (known)
|
|
174
|
+
return known;
|
|
175
|
+
}
|
|
176
|
+
return placeholder(text);
|
|
177
|
+
}
|
|
178
|
+
#string(text, key, context) {
|
|
179
|
+
if (context.secret || SECRET_KEY.test(key) || context.opaque)
|
|
180
|
+
return placeholder(text);
|
|
181
|
+
if (MODEL_KEYS.has(key))
|
|
182
|
+
return this.model(text);
|
|
183
|
+
if (context.modelOption && (key === 'value' || key === 'currentValue' || key === 'resetValue'))
|
|
184
|
+
return this.model(text);
|
|
185
|
+
if (VOCABULARY_ID_KEYS.has(key))
|
|
186
|
+
return this.verbatim(text);
|
|
187
|
+
if (context.vocabulary && VOCABULARY_FIELDS.has(key) && !(context.modelOption && key === 'name'))
|
|
188
|
+
return this.verbatim(text);
|
|
189
|
+
if (ID_KEY.test(key))
|
|
190
|
+
return this.id(text);
|
|
191
|
+
if (VERBATIM_KEYS.has(key))
|
|
192
|
+
return this.verbatim(text);
|
|
193
|
+
return this.#opaque(text);
|
|
194
|
+
}
|
|
195
|
+
value(value, key, context = ROOT) {
|
|
196
|
+
if (typeof value === 'string')
|
|
197
|
+
return this.#string(value, key, context);
|
|
198
|
+
if (Array.isArray(value))
|
|
199
|
+
return value.map((item) => this.value(item, key, context));
|
|
200
|
+
if (value !== null && typeof value === 'object') {
|
|
201
|
+
const record = value;
|
|
202
|
+
let here = context;
|
|
203
|
+
// An entry of `configOptions` is reached as an array item, so its `key` is still `configOptions`.
|
|
204
|
+
if (context.vocabulary && key === 'configOptions' && (record.category === 'model' || record.id === 'model')) {
|
|
205
|
+
if (typeof record.id === 'string')
|
|
206
|
+
this.#modelConfigs.add(record.id);
|
|
207
|
+
here = { ...context, modelOption: true };
|
|
208
|
+
}
|
|
209
|
+
// A `session/set_config_option` request: its `value` is the option's vocabulary (a model id for a model option).
|
|
210
|
+
if (typeof record.configId === 'string') {
|
|
211
|
+
here = { ...here, vocabulary: true, modelOption: this.#modelConfigs.has(record.configId) };
|
|
212
|
+
}
|
|
213
|
+
const out = {};
|
|
214
|
+
for (const [k, v] of Object.entries(record)) {
|
|
215
|
+
const child = {
|
|
216
|
+
...here,
|
|
217
|
+
secret: here.secret || SECRET_CONTEXTS.has(k),
|
|
218
|
+
opaque: here.opaque || OPAQUE_CONTEXTS.has(k),
|
|
219
|
+
vocabulary: here.vocabulary || VOCABULARY_CONTEXTS.has(k),
|
|
220
|
+
};
|
|
221
|
+
out[this.verbatim(k)] = this.value(v, k, child);
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** Home-directory user names named anywhere in the capture (`/Users/x`, `/home/x`, `-Users-x-`). */
|
|
229
|
+
export function detectWords(text) {
|
|
230
|
+
const words = new Set();
|
|
231
|
+
for (const match of text.matchAll(/[/-](?:Users|home)[/-]([A-Za-z0-9._]+)/g)) {
|
|
232
|
+
const word = match[1];
|
|
233
|
+
if (word.length >= 3 && !['user', 'shared', 'runner'].includes(word.toLowerCase()))
|
|
234
|
+
words.add(word);
|
|
235
|
+
}
|
|
236
|
+
return [...words];
|
|
237
|
+
}
|
|
238
|
+
function readItems(text) {
|
|
239
|
+
const items = [];
|
|
240
|
+
for (const line of text.split('\n')) {
|
|
241
|
+
if (!line.trim())
|
|
242
|
+
continue;
|
|
243
|
+
const row = JSON.parse(line);
|
|
244
|
+
if ('dir' in row) {
|
|
245
|
+
const t = Number(row.ms ?? 0);
|
|
246
|
+
if (row.dir === 'in')
|
|
247
|
+
items.push({ t, k: 'up', msg: row.msg });
|
|
248
|
+
else if (row.dir === 'out')
|
|
249
|
+
items.push({ t, k: 'down', msg: row.msg });
|
|
250
|
+
else if (row.dir === 'in-unparsed')
|
|
251
|
+
items.push({ t, k: 'up', text: String(row.msg) });
|
|
252
|
+
else if (row.dir === 'stderr') {
|
|
253
|
+
for (const part of String(row.msg).split('\n'))
|
|
254
|
+
if (part.trim())
|
|
255
|
+
items.push({ t, k: 'err', text: part });
|
|
256
|
+
}
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const t = Number(row.t ?? 0);
|
|
260
|
+
if (row.k === 'up' || row.k === 'down') {
|
|
261
|
+
const raw = String(row.l);
|
|
262
|
+
try {
|
|
263
|
+
items.push({ t, k: row.k, msg: JSON.parse(raw) });
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
items.push({ t, k: row.k, text: raw });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else if (row.k === 'err')
|
|
270
|
+
items.push({ t, k: 'err', text: String(row.l) });
|
|
271
|
+
else if (row.k === 'proc' && row.kind === 'exit') {
|
|
272
|
+
items.push({ t, k: 'exit', msg: { code: row.code ?? null, signal: row.signal ?? null, stderrTail: String(row.stderrTail ?? '') } });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return items;
|
|
276
|
+
}
|
|
277
|
+
/** Remove whole turns: each dropped `session/prompt` request, its response, and everything between. */
|
|
278
|
+
function dropTurns(items, turns) {
|
|
279
|
+
if (turns.length === 0)
|
|
280
|
+
return items;
|
|
281
|
+
const drop = new Set();
|
|
282
|
+
let promptIndex = 0;
|
|
283
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
284
|
+
const msg = items[i].msg;
|
|
285
|
+
if (items[i].k !== 'down' || msg?.method !== 'session/prompt')
|
|
286
|
+
continue;
|
|
287
|
+
promptIndex += 1;
|
|
288
|
+
if (!turns.includes(promptIndex))
|
|
289
|
+
continue;
|
|
290
|
+
let end = i + 1;
|
|
291
|
+
for (; end < items.length; end += 1) {
|
|
292
|
+
const reply = items[end].msg;
|
|
293
|
+
if (items[end].k === 'up' && reply && reply.method === undefined && reply.id === msg.id)
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
for (let j = i; j <= Math.min(end, items.length - 1); j += 1)
|
|
297
|
+
drop.add(j);
|
|
298
|
+
}
|
|
299
|
+
return items.filter((_, index) => !drop.has(index));
|
|
300
|
+
}
|
|
301
|
+
/** The whole sanitiser: capture text in, kit-stream ndjson out. */
|
|
302
|
+
export function sanitizeCapture(text, options = {}) {
|
|
303
|
+
return sanitizeWith(new Scrubber([...detectWords(text), ...(options.words ?? [])]), text, options);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* A capture and its side file (ndjson rows, `scripts/acp-capture.mjs --side-home`), sanitised with one identity table so the rows
|
|
307
|
+
* keep naming the capture's session and tool calls ({@link Scrubber.side}). Returns both texts.
|
|
308
|
+
*/
|
|
309
|
+
export function sanitizeCaptureWithSide(text, sideText, options = {}) {
|
|
310
|
+
const scrub = new Scrubber([...detectWords(text), ...detectWords(sideText), ...(options.words ?? [])]);
|
|
311
|
+
const capture = sanitizeWith(scrub, text, options);
|
|
312
|
+
const parsed = sideText.split('\n').filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
313
|
+
// Pass 1 fills the identity table from the rows' own id fields, so text naming an id before its field still maps (as for the capture).
|
|
314
|
+
for (const row of parsed)
|
|
315
|
+
scrub.side(row, '');
|
|
316
|
+
const rows = parsed.map((row) => JSON.stringify(scrub.side(row, '')));
|
|
317
|
+
return { capture, side: rows.length > 0 ? `${rows.join('\n')}\n` : '' };
|
|
318
|
+
}
|
|
319
|
+
function sanitizeWith(scrub, text, options) {
|
|
320
|
+
const items = dropTurns(readItems(text), options.dropTurns ?? []);
|
|
321
|
+
const out = [];
|
|
322
|
+
const t0 = items[0]?.t ?? 0;
|
|
323
|
+
out.push(JSON.stringify({ o: 0, t: 0, k: 'proc', kind: 'spawn', spawnId: 'capture', pid: 0, pgid: 0, startTime: 0 }));
|
|
324
|
+
// Pass 1 only fills the id tables, so a string that equals an identity seen *later* (a subagent's
|
|
325
|
+
// `name` equal to its agent id) still maps to that identity in pass 2.
|
|
326
|
+
for (const item of items)
|
|
327
|
+
if (item.msg !== undefined && item.k !== 'exit')
|
|
328
|
+
scrub.value(item.msg, '');
|
|
329
|
+
for (const item of items) {
|
|
330
|
+
const o = out.length;
|
|
331
|
+
const t = Math.max(0, item.t - t0);
|
|
332
|
+
if (item.k === 'exit') {
|
|
333
|
+
const exit = item.msg;
|
|
334
|
+
out.push(JSON.stringify({ o, t, k: 'proc', kind: 'exit', code: exit.code, signal: exit.signal, stderrTail: placeholder(exit.stderrTail) }));
|
|
335
|
+
}
|
|
336
|
+
else if (item.k === 'err')
|
|
337
|
+
out.push(JSON.stringify({ o, t, k: 'err', l: placeholder(item.text ?? '') }));
|
|
338
|
+
else {
|
|
339
|
+
const l = item.msg !== undefined ? JSON.stringify(scrub.value(item.msg, '')) : placeholder(item.text ?? '');
|
|
340
|
+
out.push(JSON.stringify({ o, t, k: item.k, l }));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return `${out.join('\n')}\n`;
|
|
344
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { decodeStreamRecord } from '../../processor/record.js';
|
|
3
|
+
import { fixtureNames, RAW_DIR, readFixtureText, transduce } from './fixtures.js';
|
|
4
|
+
/** Lines → kit-stream ndjson (a synthetic `proc spawn` first, `t` = position). */
|
|
5
|
+
export function captureText(lines) {
|
|
6
|
+
const records = [JSON.stringify({ o: 0, t: 0, k: 'proc', kind: 'spawn', spawnId: 'capture', pid: 0, pgid: 0, startTime: 0 })];
|
|
7
|
+
lines.forEach((line, index) => records.push(JSON.stringify({ o: index + 1, t: index + 1, k: line.k, l: JSON.stringify(line.message) })));
|
|
8
|
+
return `${records.join('\n')}\n`;
|
|
9
|
+
}
|
|
10
|
+
export const up = (message) => ({ k: 'up', message });
|
|
11
|
+
export const down = (message) => ({ k: 'down', message });
|
|
12
|
+
export const update = (sessionId, value) => up({ jsonrpc: '2.0', method: 'session/update', params: { sessionId, update: value } });
|
|
13
|
+
const SYNTHETIC_PROMPT = [{ type: 'text', text: 'go' }];
|
|
14
|
+
const opening = (sessionId) => [
|
|
15
|
+
down({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: 1, clientCapabilities: {} } }),
|
|
16
|
+
up({ jsonrpc: '2.0', id: 1, result: { protocolVersion: 1, agentCapabilities: {}, authMethods: [] } }),
|
|
17
|
+
down({ jsonrpc: '2.0', id: 2, method: 'session/new', params: { cwd: '/work/synthetic', mcpServers: [] } }),
|
|
18
|
+
up({ jsonrpc: '2.0', id: 2, result: { sessionId } }),
|
|
19
|
+
];
|
|
20
|
+
const chunk = (sessionId, text, messageId) => update(sessionId, { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text }, ...(messageId ? { messageId } : {}) });
|
|
21
|
+
/**
|
|
22
|
+
* Experiment 0.1's wire (`docs/superpowers/reports/2026-10-11-keepers-analysis/experiments/ordering/agent.cjs`
|
|
23
|
+
* with `perm`): a draft `subagent_state_update` between two chunks, then a tool call, its permission
|
|
24
|
+
* request and a chunk after it — the capture client answers the card after the turn's response.
|
|
25
|
+
*/
|
|
26
|
+
function experiment01() {
|
|
27
|
+
const S = 'S';
|
|
28
|
+
return { name: 'synthetic-experiment-0.1', kind: 'synthetic', text: captureText([
|
|
29
|
+
...opening(S),
|
|
30
|
+
down({ jsonrpc: '2.0', id: 3, method: 'session/prompt', params: { sessionId: S, prompt: SYNTHETIC_PROMPT } }),
|
|
31
|
+
chunk(S, 'A', 'm1'),
|
|
32
|
+
update(S, { sessionUpdate: 'subagent_state_update', subagentSessionId: 'child-1', state: 'running' }),
|
|
33
|
+
chunk(S, 'B', 'm1'),
|
|
34
|
+
update(S, { sessionUpdate: 'tool_call', toolCallId: 't1', title: 'Bash', kind: 'execute', status: 'pending' }),
|
|
35
|
+
up({ jsonrpc: '2.0', id: 99, method: 'session/request_permission', params: { sessionId: S, toolCall: { toolCallId: 't1' },
|
|
36
|
+
options: [{ optionId: 'allow', name: 'Allow', kind: 'allow_once' }] } }),
|
|
37
|
+
chunk(S, 'C', 'm2'),
|
|
38
|
+
up({ jsonrpc: '2.0', id: 3, result: { stopReason: 'end_turn' } }),
|
|
39
|
+
down({ jsonrpc: '2.0', id: 99, result: { outcome: { outcome: 'selected', optionId: 'allow' } } }),
|
|
40
|
+
]) };
|
|
41
|
+
}
|
|
42
|
+
/** A chunk with no `messageId` after a turn's response, then the next prompt (17b's out-of-turn rule). */
|
|
43
|
+
function outOfTurnThenPrompt() {
|
|
44
|
+
const S = 'S';
|
|
45
|
+
return { name: 'synthetic-out-of-turn-then-prompt', kind: 'synthetic', text: captureText([
|
|
46
|
+
...opening(S),
|
|
47
|
+
down({ jsonrpc: '2.0', id: 3, method: 'session/prompt', params: { sessionId: S, prompt: SYNTHETIC_PROMPT } }),
|
|
48
|
+
chunk(S, 'first answer'),
|
|
49
|
+
up({ jsonrpc: '2.0', id: 3, result: { stopReason: 'end_turn' } }),
|
|
50
|
+
chunk(S, 'late words'),
|
|
51
|
+
down({ jsonrpc: '2.0', id: 4, method: 'session/prompt', params: { sessionId: S, prompt: SYNTHETIC_PROMPT } }),
|
|
52
|
+
chunk(S, 'second answer'),
|
|
53
|
+
up({ jsonrpc: '2.0', id: 4, result: { stopReason: 'end_turn' } }),
|
|
54
|
+
]) };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* An interrupt mid-turn: a chunk, a tool call and its permission card are out, then the client cancels
|
|
58
|
+
* (`session/cancel`, the card answered `cancelled` by the interrupt itself) and the agent ends the
|
|
59
|
+
* prompt with `stopReason: cancelled`.
|
|
60
|
+
*/
|
|
61
|
+
function interruptMidTurn() {
|
|
62
|
+
const S = 'S';
|
|
63
|
+
return { name: 'synthetic-interrupt', kind: 'synthetic', text: captureText([
|
|
64
|
+
...opening(S),
|
|
65
|
+
down({ jsonrpc: '2.0', id: 3, method: 'session/prompt', params: { sessionId: S, prompt: SYNTHETIC_PROMPT } }),
|
|
66
|
+
chunk(S, 'working on it', 'm1'),
|
|
67
|
+
update(S, { sessionUpdate: 'tool_call', toolCallId: 't1', title: 'Bash', kind: 'execute', status: 'pending' }),
|
|
68
|
+
up({ jsonrpc: '2.0', id: 7, method: 'session/request_permission', params: { sessionId: S, toolCall: { toolCallId: 't1' },
|
|
69
|
+
options: [{ optionId: 'allow', name: 'Allow', kind: 'allow_once' }, { optionId: 'reject', name: 'Reject', kind: 'reject_once' }] } }),
|
|
70
|
+
down({ jsonrpc: '2.0', method: 'session/cancel', params: { sessionId: S } }),
|
|
71
|
+
down({ jsonrpc: '2.0', id: 7, result: { outcome: { outcome: 'cancelled' } } }),
|
|
72
|
+
update(S, { sessionUpdate: 'tool_call_update', toolCallId: 't1', status: 'failed' }),
|
|
73
|
+
up({ jsonrpc: '2.0', id: 3, result: { stopReason: 'cancelled' } }),
|
|
74
|
+
]) };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* T19 prerequisite: a `session/load` — the agent replays an earlier conversation (a user message, an
|
|
78
|
+
* assistant message across two chunks, a thought, a finished tool call) and answers with modes; the
|
|
79
|
+
* client then sets a mode, an `available_commands_update` arrives after that (after the load response,
|
|
80
|
+
* so live on both paths), and one turn runs on the loaded session.
|
|
81
|
+
*/
|
|
82
|
+
function loadThenPrompt() {
|
|
83
|
+
const S = 'S-loaded';
|
|
84
|
+
return { name: 'synthetic-load', kind: 'synthetic', text: captureText([
|
|
85
|
+
down({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: 1, clientCapabilities: {} } }),
|
|
86
|
+
up({ jsonrpc: '2.0', id: 1, result: { protocolVersion: 1, agentCapabilities: { loadSession: true }, authMethods: [] } }),
|
|
87
|
+
down({ jsonrpc: '2.0', id: 2, method: 'session/load', params: { sessionId: S, cwd: '/work/synthetic', mcpServers: [] } }),
|
|
88
|
+
update(S, { sessionUpdate: 'user_message_chunk', content: { type: 'text', text: 'earlier question' } }),
|
|
89
|
+
chunk(S, 'earlier ', 'h1'),
|
|
90
|
+
chunk(S, 'answer', 'h1'),
|
|
91
|
+
update(S, { sessionUpdate: 'agent_thought_chunk', content: { type: 'text', text: 'thinking it over' } }),
|
|
92
|
+
update(S, { sessionUpdate: 'tool_call', toolCallId: 'h-t1', title: 'Read', kind: 'read', status: 'completed' }),
|
|
93
|
+
chunk(S, 'second earlier answer', 'h2'),
|
|
94
|
+
up({ jsonrpc: '2.0', id: 2, result: { modes: { currentModeId: 'default', availableModes: [
|
|
95
|
+
{ id: 'default', name: 'Default' }, { id: 'plan', name: 'Plan' }
|
|
96
|
+
] } } }),
|
|
97
|
+
down({ jsonrpc: '2.0', id: 3, method: 'session/set_mode', params: { sessionId: S, modeId: 'plan' } }),
|
|
98
|
+
up({ jsonrpc: '2.0', id: 3, result: {} }),
|
|
99
|
+
update(S, { sessionUpdate: 'available_commands_update', availableCommands: [{ name: 'xxxx', description: 'xxxxxxx' }] }),
|
|
100
|
+
down({ jsonrpc: '2.0', id: 4, method: 'session/prompt', params: { sessionId: S, prompt: SYNTHETIC_PROMPT } }),
|
|
101
|
+
chunk(S, 'live answer', 'm1'),
|
|
102
|
+
up({ jsonrpc: '2.0', id: 4, result: { stopReason: 'end_turn' } }),
|
|
103
|
+
]) };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* T19 prerequisite: a cached `session/resume` (an adapter advertising
|
|
107
|
+
* `sessionCapabilities.resume`) — no history, a config snapshot on the response, then one turn.
|
|
108
|
+
*/
|
|
109
|
+
function resumeThenPrompt() {
|
|
110
|
+
const S = 'S-resumed';
|
|
111
|
+
return { name: 'synthetic-resume', kind: 'synthetic', text: captureText([
|
|
112
|
+
down({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: 1, clientCapabilities: {} } }),
|
|
113
|
+
up({ jsonrpc: '2.0', id: 1, result: { protocolVersion: 1, agentCapabilities: { loadSession: true, sessionCapabilities: { resume: {} } },
|
|
114
|
+
authMethods: [], agentInfo: { name: '@agentclientprotocol/claude-agent-acp', version: '1.0.0' } } }),
|
|
115
|
+
down({ jsonrpc: '2.0', id: 2, method: 'session/resume', params: { sessionId: S, cwd: '/work/synthetic', mcpServers: [] } }),
|
|
116
|
+
up({ jsonrpc: '2.0', id: 2, result: { configOptions: [{ id: 'model', name: 'Model', type: 'select', currentValue: 'model-0001',
|
|
117
|
+
options: [{ value: 'model-0001', name: 'xxxxx' }, { value: 'model-0002', name: 'xxxxx' }] }] } }),
|
|
118
|
+
down({ jsonrpc: '2.0', id: 3, method: 'session/prompt', params: { sessionId: S, prompt: SYNTHETIC_PROMPT } }),
|
|
119
|
+
chunk(S, 'resumed answer', 'm1'),
|
|
120
|
+
up({ jsonrpc: '2.0', id: 3, result: { stopReason: 'end_turn' } }),
|
|
121
|
+
]) };
|
|
122
|
+
}
|
|
123
|
+
export async function allSources() {
|
|
124
|
+
const real = fixtureNames().map((name) => ({
|
|
125
|
+
name: name.replace(/\.jsonl$/, ''), kind: 'real', text: readFixtureText(name), path: join(RAW_DIR, name),
|
|
126
|
+
}));
|
|
127
|
+
return [...real, experiment01(), outOfTurnThenPrompt(), interruptMidTurn(), loadThenPrompt(), resumeThenPrompt()];
|
|
128
|
+
}
|
|
129
|
+
export function captureRecords(source) {
|
|
130
|
+
return source.text.split('\n').filter((line) => line.trim()).map((line) => decodeStreamRecord(JSON.parse(line)));
|
|
131
|
+
}
|
|
132
|
+
export async function buildLog(source, options = {}) {
|
|
133
|
+
const transduced = transduce(captureRecords(source), options);
|
|
134
|
+
return { ...transduced, records: transduced.session.log.read(0), source, sideNotes: 0 };
|
|
135
|
+
}
|