@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,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ACP SDK's own zod schemas, used synchronously as a schema library (spec 2026-10-11 §5.4: "SDK
|
|
3
|
+
* 只作 schema 库(同步 zod 校验/剥离)").
|
|
4
|
+
*
|
|
5
|
+
* ## Why these schemas, and why loaded this way
|
|
6
|
+
*
|
|
7
|
+
* The live path validates and strips every inbound message with the SDK's generated schemas
|
|
8
|
+
* (`dist/schema/zod.gen.js` — e.g. `ClientApp` parses `session/update` through `zSessionNotification`,
|
|
9
|
+
* `session/request_permission` through `zRequestPermissionRequest`). The processor has to apply the
|
|
10
|
+
* **same** schemas or moving a session onto it changes payload bytes (analysis B F2). Copying them
|
|
11
|
+
* would drift on the next SDK bump; importing them is the only honest option.
|
|
12
|
+
*
|
|
13
|
+
* The package's `exports` map does not expose `zod.gen.js`, so a bare subpath import is refused. It is
|
|
14
|
+
* reached instead by resolving the package entry and taking its sibling — a plain file path, which
|
|
15
|
+
* `exports` does not police — and loaded with `require` (synchronous `require(esm)`, Node ≥ 22.12;
|
|
16
|
+
* `engines.node` is `>=24`). This happens once, at module load: nothing here runs inside `step`.
|
|
17
|
+
* `guard.test.ts` allows exactly this file to import `node:module`.
|
|
18
|
+
*
|
|
19
|
+
* ## The pin
|
|
20
|
+
*
|
|
21
|
+
* `dist/schema/zod.gen.js` is the SDK's **private** layout, so `package.json` pins the SDK to one
|
|
22
|
+
* exact version (no range) and `sdkSchemas.test.ts` checks the installed version is that pin and the
|
|
23
|
+
* file resolves — an SDK bump has to be a deliberate change that re-runs that test.
|
|
24
|
+
*/
|
|
25
|
+
import { createRequire } from 'node:module';
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
/** The absolute path of the SDK's generated zod module (a sibling of its `acp.js` entry). */
|
|
28
|
+
export function sdkZodGenPath(resolve = require.resolve) {
|
|
29
|
+
const entry = resolve('@agentclientprotocol/sdk');
|
|
30
|
+
const suffix = 'acp.js';
|
|
31
|
+
if (!entry.endsWith(suffix)) {
|
|
32
|
+
throw new Error(`Unexpected @agentclientprotocol/sdk entry point ${entry}: the processor reads the SDK's private dist/schema/zod.gen.js next to dist/acp.js; re-check the pinned SDK version`);
|
|
33
|
+
}
|
|
34
|
+
return `${entry.slice(0, -suffix.length)}schema/zod.gen.js`;
|
|
35
|
+
}
|
|
36
|
+
function loadZodGen() {
|
|
37
|
+
const path = sdkZodGenPath();
|
|
38
|
+
try {
|
|
39
|
+
return require(path);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(`Cannot load the ACP SDK's zod schemas from ${path} (private SDK layout; re-check the pinned SDK version): ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const zodGen = loadZodGen();
|
|
46
|
+
function schema(name) {
|
|
47
|
+
const found = zodGen[name];
|
|
48
|
+
if (!found || typeof found.safeParse !== 'function')
|
|
49
|
+
throw new Error(`@agentclientprotocol/sdk has no schema ${name}`);
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
/** Inbound (agent → client) request params, by method — the schemas `clientRequestSpecs` uses. */
|
|
53
|
+
export const INBOUND_REQUEST_SCHEMAS = {
|
|
54
|
+
'session/request_permission': schema('zRequestPermissionRequest'),
|
|
55
|
+
'elicitation/create': schema('zCreateElicitationRequest'),
|
|
56
|
+
};
|
|
57
|
+
/** Inbound notification params, by method — the schemas `clientNotificationSpecs` uses. */
|
|
58
|
+
export const INBOUND_NOTIFICATION_SCHEMAS = {
|
|
59
|
+
'session/update': schema('zSessionNotification'),
|
|
60
|
+
'elicitation/complete': schema('zCompleteElicitationNotification'),
|
|
61
|
+
};
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/** Session turns, permission and elicitation cards, and callback snapshots over checkpointed state. Root and child permission origins are verified before a card is emitted; callback generations derive from the process spawn ID. */
|
|
2
|
+
import { buildElicitationRequestedEvent, buildPermissionRequestedEvent, elicitationFieldKeys, isBlank, permissionRequestId, } from '../permission.js';
|
|
3
|
+
import { validateWireEvent } from '../validate.js';
|
|
4
|
+
import { isRecord, nextSeq } from './emit.js';
|
|
5
|
+
import { inboundKey, settlementErrorCode } from './jsonrpc.js';
|
|
6
|
+
import { sameCause } from './record.js';
|
|
7
|
+
import { publishCallbacks, resetRuntime } from './runtime.js';
|
|
8
|
+
import { getOwn, initialCallbacks, initialMessage, PENDING_PERMISSION_LIMIT, SETTLED_PERMISSION_LIMIT, } from './state.js';
|
|
9
|
+
import { emitSessionEvent, endHistory, flushMessage, flushOutOfTurnMessage } from './updates.js';
|
|
10
|
+
/** `agentSessions.ts`'s admission refusal text, verbatim. */
|
|
11
|
+
export const ADMISSION_REFUSED = 'Workstation is not accepting new work';
|
|
12
|
+
/** The agent answered `session/prompt` with `stopReason: 'refusal'` and no words of its own. */
|
|
13
|
+
export const PROMPT_REFUSED = 'The agent refused this prompt.';
|
|
14
|
+
/** `AcpSession.prompt`'s overlap refusal text, verbatim. */
|
|
15
|
+
export const OVERLAPPING_TURN = 'An exact platform turn is still running; overlapping prompts are unavailable';
|
|
16
|
+
/** What the SDK rejects pending requests with when the adapter's stdout ends (`jsonrpc.js` `close()`). */
|
|
17
|
+
export const CONNECTION_CLOSED = 'ACP connection closed';
|
|
18
|
+
/** A prompt for a connection with no bound session (Task 17b binds it from the open response). */
|
|
19
|
+
export const NO_SESSION = 'No ACP session is open on this connection';
|
|
20
|
+
/** `ClientApp` closes the connection on a batch; that reason is kept as the turn's error. */
|
|
21
|
+
export const BATCH_CLOSE_REASON = 'JSON-RPC batches are not supported on this connection';
|
|
22
|
+
/** Pending `'form'` elicitations kept at once (new bound, mirrors permissions' 64). */
|
|
23
|
+
export const PENDING_ELICITATION_LIMIT = 64;
|
|
24
|
+
const PERMISSION_CANCELLED = { outcome: { outcome: 'cancelled' } };
|
|
25
|
+
const ELICITATION_DECLINE = { action: 'decline' };
|
|
26
|
+
const ELICITATION_CANCEL = { action: 'cancel' };
|
|
27
|
+
/** Connected = a running process whose JSON-RPC connection has not closed. */
|
|
28
|
+
export function connected(state) {
|
|
29
|
+
return state.process.status === 'running' && state.closed === undefined;
|
|
30
|
+
}
|
|
31
|
+
/** The one setter of `rootSessionId` — `control.ts` calls it on the `session/new|load|resume` response. */
|
|
32
|
+
export function bindRootSession(state, sessionId) {
|
|
33
|
+
state.rootSessionId = sessionId;
|
|
34
|
+
}
|
|
35
|
+
/** Every unanswered card id — `agent_opened.pendingRequestIds` (spec §5.5.4). */
|
|
36
|
+
export function pendingRequestIds(state) {
|
|
37
|
+
return [...Object.keys(state.permissions), ...Object.keys(state.elicitations)];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A new adapter process is a new connection: today's per-connection, per-`AcpSession` and
|
|
41
|
+
* per-`LiveSession` fields start fresh (a respawn is a new session there too). `seq` is not reset.
|
|
42
|
+
*/
|
|
43
|
+
export function resetForSpawn(state) {
|
|
44
|
+
state.rootSessionId = null;
|
|
45
|
+
state.open = { status: 'idle' };
|
|
46
|
+
state.configOptions = null;
|
|
47
|
+
state.history = null;
|
|
48
|
+
state.mapperScopes = {};
|
|
49
|
+
state.message = initialMessage();
|
|
50
|
+
state.steers = {};
|
|
51
|
+
state.steered = [];
|
|
52
|
+
state.asyncTasks = {};
|
|
53
|
+
state.draftSubagents = { children: {}, overflow: false };
|
|
54
|
+
resetRuntime(state);
|
|
55
|
+
state.turns = [];
|
|
56
|
+
state.exactExecutionUnconfirmed = false;
|
|
57
|
+
state.backgroundWriterUnconfirmed = false;
|
|
58
|
+
state.permissionsInterrupted = false;
|
|
59
|
+
state.permissions = {};
|
|
60
|
+
state.elicitations = {};
|
|
61
|
+
state.settledPermissions = [];
|
|
62
|
+
state.callbacks = initialCallbacks();
|
|
63
|
+
state.permissionOrigins = {};
|
|
64
|
+
state.permissionParents = {};
|
|
65
|
+
state.unresolvedPermissionOrigins = {};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The connection closed: unanswered cards can no longer be answered (nothing to write them to) — the
|
|
69
|
+
* read loop's `drainPendingApprovals`, so the callback snapshot goes incomplete and is published.
|
|
70
|
+
*/
|
|
71
|
+
export function dropCards(state, out) {
|
|
72
|
+
state.permissions = {};
|
|
73
|
+
state.elicitations = {};
|
|
74
|
+
state.callbacks.complete = false;
|
|
75
|
+
publishCallbacks(state, out);
|
|
76
|
+
}
|
|
77
|
+
export function closeErrorText(state) {
|
|
78
|
+
return state.closed === BATCH_CLOSE_REASON ? BATCH_CLOSE_REASON : CONNECTION_CLOSED;
|
|
79
|
+
}
|
|
80
|
+
// ---- turns ------------------------------------------------------------------------------------
|
|
81
|
+
/**
|
|
82
|
+
* The short `agent_turn_done` `agentSessions.ts` builds itself for a prompt that never reached the
|
|
83
|
+
* session (admission refused, `prompt()` threw): no `nativeSessionId`, no `stopReason`.
|
|
84
|
+
*/
|
|
85
|
+
function refuseTurn(state, out, error, platformTurnId) {
|
|
86
|
+
out.frame({
|
|
87
|
+
t: 'agent_turn_done',
|
|
88
|
+
seq: nextSeq(state),
|
|
89
|
+
status: 'failed',
|
|
90
|
+
error,
|
|
91
|
+
...(platformTurnId !== undefined ? { platformTurnId } : {}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const STOP_REASONS = {
|
|
95
|
+
end_turn: 'end_turn',
|
|
96
|
+
max_tokens: 'max_tokens',
|
|
97
|
+
max_turn_requests: 'max_turns',
|
|
98
|
+
// No `refusal`: a refusal result settles 'failed' (see `settlePrompt`), and a completed turn
|
|
99
|
+
// never carries it.
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* End turn `index` — `AcpSession.finishTurn` + `agentSessions.ts`'s `onAgentTurnDone` frame, in one.
|
|
103
|
+
* The frame keeps that method's key order (`agent_turn_done` minus `sessionRef`); `agentRefused` and
|
|
104
|
+
* `errorCode` are carried for the projection, which turns them into `refusalFields` (Task 17b).
|
|
105
|
+
*/
|
|
106
|
+
function finishTurn(state, out, index, status, finish) {
|
|
107
|
+
const [turn] = state.turns.splice(index, 1);
|
|
108
|
+
if (!turn)
|
|
109
|
+
return;
|
|
110
|
+
// Before the turn's own frame, on every path that ends a turn (`flushMessageCompleted`).
|
|
111
|
+
flushMessage(state, out);
|
|
112
|
+
const resolved = turn.interrupted ? 'interrupted' : status;
|
|
113
|
+
if (resolved === 'interrupted') {
|
|
114
|
+
state.permissionsInterrupted = true;
|
|
115
|
+
drainCards(state, out);
|
|
116
|
+
}
|
|
117
|
+
const frame = { t: 'agent_turn_done', seq: nextSeq(state), status: resolved };
|
|
118
|
+
if (turn.platformTurnId !== undefined)
|
|
119
|
+
frame.platformTurnId = turn.platformTurnId;
|
|
120
|
+
if (finish.executionEnded && state.turns.length === 0 && !state.backgroundWriterUnconfirmed) {
|
|
121
|
+
frame.executionEnded = true;
|
|
122
|
+
state.exactExecutionUnconfirmed = false;
|
|
123
|
+
}
|
|
124
|
+
const stopReason = typeof finish.stopReason === 'string' ? STOP_REASONS[finish.stopReason] : undefined;
|
|
125
|
+
if (resolved === 'completed' && stopReason !== undefined)
|
|
126
|
+
frame.stopReason = stopReason;
|
|
127
|
+
frame.nativeSessionId = state.rootSessionId !== null && state.rootSessionId.length > 0 ? state.rootSessionId : null;
|
|
128
|
+
frame.error = resolved === 'failed' && finish.error ? finish.error : null;
|
|
129
|
+
if (resolved === 'failed' && finish.agentRefused) {
|
|
130
|
+
frame.agentRefused = true;
|
|
131
|
+
if (finish.errorCode !== undefined)
|
|
132
|
+
frame.errorCode = finish.errorCode;
|
|
133
|
+
}
|
|
134
|
+
out.frame(frame);
|
|
135
|
+
}
|
|
136
|
+
/** `agent_prompt{blocksRef, platformTurnId?, admitted?}`. */
|
|
137
|
+
export function onPromptNote(state, note, out) {
|
|
138
|
+
const { blocksRef, platformTurnId, admitted } = note;
|
|
139
|
+
if (typeof blocksRef !== 'string' || blocksRef === '' || (platformTurnId !== undefined && typeof platformTurnId !== 'string')
|
|
140
|
+
|| (admitted !== undefined && typeof admitted !== 'boolean')) {
|
|
141
|
+
state.diagnostics.droppedNotes += 1;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (admitted === false) {
|
|
145
|
+
refuseTurn(state, out, ADMISSION_REFUSED, platformTurnId);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!connected(state) || state.rootSessionId === null) {
|
|
149
|
+
// `AcpSession.prompt`'s `readLoopDead` branch: registered as a turn and finished on the spot, so
|
|
150
|
+
// "one prompt, one terminal result" holds here too.
|
|
151
|
+
state.turns.push({ requestId: -1, cause: { o: out.o, i: -1 }, interrupted: false, sent: false,
|
|
152
|
+
...(platformTurnId !== undefined ? { platformTurnId } : {}) });
|
|
153
|
+
finishTurn(state, out, state.turns.length - 1, 'failed', { error: connected(state) ? NO_SESSION : closeErrorText(state) });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (state.exactExecutionUnconfirmed || (state.turns.length > 0 && (platformTurnId !== undefined
|
|
157
|
+
|| state.turns.some((turn) => turn.platformTurnId !== undefined)))) {
|
|
158
|
+
refuseTurn(state, out, OVERLAPPING_TURN, platformTurnId);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
state.permissionsInterrupted = false;
|
|
162
|
+
// A turn ends any load history still open: its output is live, never history (T19 writes
|
|
163
|
+
// `historyEnd` before admitting a prompt; this is the backstop).
|
|
164
|
+
endHistory(state, out, true);
|
|
165
|
+
flushOutOfTurnMessage(state, out);
|
|
166
|
+
const { id, cause } = out.request('session/prompt', { sessionId: state.rootSessionId, prompt: [] }, {
|
|
167
|
+
fill: { blocksRef },
|
|
168
|
+
...(platformTurnId !== undefined ? { annotation: { platformTurnId } } : {}),
|
|
169
|
+
});
|
|
170
|
+
const pending = { id, method: 'session/prompt', kind: 'prompt', cause };
|
|
171
|
+
state.outbound[String(id)] = pending;
|
|
172
|
+
state.turns.push({ requestId: id, cause, interrupted: false, sent: false, ...(platformTurnId !== undefined ? { platformTurnId } : {}) });
|
|
173
|
+
if (platformTurnId !== undefined)
|
|
174
|
+
state.exactExecutionUnconfirmed = true;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* A `down` record: if it is a queued prompt's line, the turn is now on the agent, and its
|
|
178
|
+
* `platformTurnId` is the one the record's annotation carries.
|
|
179
|
+
*/
|
|
180
|
+
export function onDown(state, record) {
|
|
181
|
+
const annotation = record.annotation;
|
|
182
|
+
if (!annotation)
|
|
183
|
+
return;
|
|
184
|
+
const turn = state.turns.find((candidate) => sameCause(annotation.cause, candidate.cause));
|
|
185
|
+
if (!turn || turn.sent)
|
|
186
|
+
return;
|
|
187
|
+
turn.sent = true;
|
|
188
|
+
if (typeof annotation.platformTurnId === 'string')
|
|
189
|
+
turn.platformTurnId = annotation.platformTurnId;
|
|
190
|
+
else
|
|
191
|
+
delete turn.platformTurnId;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The measured `_meta` key a refusal *result* carries its would-be error response under: a JSON
|
|
195
|
+
* string of the JSON-RPC error the agent chose not to send (`codebuddy-code` 2.171.0; shape-read,
|
|
196
|
+
* same rule as `acpClient`'s `_codebuddy.ai/authUrl`).
|
|
197
|
+
*/
|
|
198
|
+
const REFUSAL_ERROR_META_KEY = 'codebuddy.ai/errorMessage';
|
|
199
|
+
function refusalErrorDetail(result) {
|
|
200
|
+
if (!isRecord(result) || !isRecord(result._meta))
|
|
201
|
+
return undefined;
|
|
202
|
+
const raw = result._meta[REFUSAL_ERROR_META_KEY];
|
|
203
|
+
if (typeof raw !== 'string')
|
|
204
|
+
return undefined;
|
|
205
|
+
try {
|
|
206
|
+
const parsed = JSON.parse(raw);
|
|
207
|
+
if (!isRecord(parsed) || typeof parsed.message !== 'string')
|
|
208
|
+
return undefined;
|
|
209
|
+
return { ...(typeof parsed.code === 'number' ? { code: parsed.code } : {}), message: parsed.message };
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** A `session/prompt` request settled: its turn ends on this record. */
|
|
216
|
+
export function settlePrompt(state, entry, settlement, out) {
|
|
217
|
+
const index = state.turns.findIndex((turn) => turn.requestId === entry.id);
|
|
218
|
+
if (index < 0)
|
|
219
|
+
return;
|
|
220
|
+
switch (settlement.outcome) {
|
|
221
|
+
case 'result': {
|
|
222
|
+
const stopReason = isRecord(settlement.result) ? settlement.result.stopReason : undefined;
|
|
223
|
+
// ACP's own `cancelled` forces `interrupted` even when no interrupt was sent (a backstop).
|
|
224
|
+
if (stopReason === 'cancelled') {
|
|
225
|
+
finishTurn(state, out, index, 'interrupted', { stopReason, executionEnded: true });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Measured (codebuddy-code 2.171.0): an agent that needs credentials answers `session/prompt`
|
|
229
|
+
// with a *result* `stopReason: 'refusal'` — zero content, no JSON-RPC error — which used to
|
|
230
|
+
// settle 'completed' and render as nothing at all. A refusal result settles exactly like the
|
|
231
|
+
// agent's own refusal below ('failed' + `agentRefused`), with the agent's detail read out of
|
|
232
|
+
// the measured `_meta` key; a code-less detail stays an ordinary failure card server-side,
|
|
233
|
+
// while `-32000` beside the observed auth methods is what turns it into the sign-in card.
|
|
234
|
+
if (stopReason === 'refusal') {
|
|
235
|
+
const detail = refusalErrorDetail(settlement.result);
|
|
236
|
+
finishTurn(state, out, index, 'failed', {
|
|
237
|
+
agentRefused: true, executionEnded: true,
|
|
238
|
+
error: detail?.message ?? PROMPT_REFUSED, ...(detail?.code !== undefined ? { errorCode: detail.code } : {}),
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
finishTurn(state, out, index, 'completed', { stopReason, executionEnded: true });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
case 'error': {
|
|
246
|
+
// The one exit where the agent itself answered (`TurnResult.agentRefused`).
|
|
247
|
+
const errorCode = settlementErrorCode(settlement);
|
|
248
|
+
finishTurn(state, out, index, 'failed', {
|
|
249
|
+
error: settlement.error.message, agentRefused: true, errorCode, executionEnded: errorCode !== undefined,
|
|
250
|
+
});
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
case 'closed':
|
|
254
|
+
finishTurn(state, out, index, 'failed', { error: closeErrorText(state) });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* `agent_prompt_failed{requestId, error}` — the executor could not write a prompt's line (its blocks
|
|
260
|
+
* were gone after a restart, or the write failed). Ignored once the line is in raw: then the agent
|
|
261
|
+
* has it and its response ends the turn. The prompt never reached the agent, which is proof its
|
|
262
|
+
* execution ended (`TurnResult.executionEnded`).
|
|
263
|
+
*/
|
|
264
|
+
export function onPromptFailedNote(state, note, out) {
|
|
265
|
+
const { requestId, error } = note;
|
|
266
|
+
const entry = typeof requestId === 'number' ? state.outbound[String(requestId)] : undefined;
|
|
267
|
+
const index = entry ? state.turns.findIndex((turn) => turn.requestId === entry.id) : -1;
|
|
268
|
+
if (!entry || entry.kind !== 'prompt' || index < 0 || state.turns[index].sent || typeof error !== 'string') {
|
|
269
|
+
state.diagnostics.droppedNotes += 1;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
delete state.outbound[String(entry.id)];
|
|
273
|
+
finishTurn(state, out, index, 'failed', { error, executionEnded: true });
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* `agent_interrupt{}` — `AcpSession.interrupt`: every turn is marked interrupted (its result will say
|
|
277
|
+
* so whatever the agent answers), later permission requests are refused until the next prompt, every
|
|
278
|
+
* pending card is answered "cancelled", then `session/cancel` goes out.
|
|
279
|
+
*/
|
|
280
|
+
export function onInterruptNote(state, out) {
|
|
281
|
+
for (const turn of state.turns)
|
|
282
|
+
turn.interrupted = true;
|
|
283
|
+
state.permissionsInterrupted = true;
|
|
284
|
+
drainCards(state, out);
|
|
285
|
+
if (connected(state) && state.rootSessionId !== null)
|
|
286
|
+
out.notify('session/cancel', { sessionId: state.rootSessionId });
|
|
287
|
+
}
|
|
288
|
+
/** The connection is gone: every turn left ends `failed` (`finishAllTurns`). */
|
|
289
|
+
export function finishAllTurns(state, out) {
|
|
290
|
+
while (state.turns.length > 0)
|
|
291
|
+
finishTurn(state, out, 0, 'failed', { error: closeErrorText(state) });
|
|
292
|
+
}
|
|
293
|
+
// ---- cards ------------------------------------------------------------------------------------
|
|
294
|
+
function respond(state, out, rpcKey, result) {
|
|
295
|
+
const inbound = state.inbound[rpcKey];
|
|
296
|
+
if (!inbound)
|
|
297
|
+
return;
|
|
298
|
+
delete state.inbound[rpcKey];
|
|
299
|
+
if (connected(state))
|
|
300
|
+
out.result(inbound.id, result);
|
|
301
|
+
}
|
|
302
|
+
/** `AcpSession.settleApproval`'s bookkeeping: remembered with its reason, or — past the bound — the snapshot goes incomplete. */
|
|
303
|
+
function rememberSettledPermission(state, requestId, reason) {
|
|
304
|
+
if (state.settledPermissions.length < SETTLED_PERMISSION_LIMIT)
|
|
305
|
+
state.settledPermissions.push({ requestId, reason });
|
|
306
|
+
else
|
|
307
|
+
state.callbacks.complete = false;
|
|
308
|
+
}
|
|
309
|
+
/** Settle one card and answer the agent; `publish: false` inside a drain, which publishes once for all. */
|
|
310
|
+
function settlePermission(state, out, requestId, result, reason, publish = true) {
|
|
311
|
+
const entry = state.permissions[requestId];
|
|
312
|
+
if (!entry)
|
|
313
|
+
return;
|
|
314
|
+
delete state.permissions[requestId];
|
|
315
|
+
rememberSettledPermission(state, requestId, reason);
|
|
316
|
+
if (publish)
|
|
317
|
+
publishCallbacks(state, out);
|
|
318
|
+
respond(state, out, entry.rpcKey, result);
|
|
319
|
+
}
|
|
320
|
+
function settleElicitation(state, out, requestId, result) {
|
|
321
|
+
const entry = state.elicitations[requestId];
|
|
322
|
+
if (!entry)
|
|
323
|
+
return;
|
|
324
|
+
delete state.elicitations[requestId];
|
|
325
|
+
respond(state, out, entry.rpcKey, result);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* `drainPendingApprovals` + `drainPendingElicitations`: nobody will answer these any more. The
|
|
329
|
+
* callback snapshot goes incomplete and is published once (`drainPendingApprovals` publishes before
|
|
330
|
+
* it resolves the cards).
|
|
331
|
+
*/
|
|
332
|
+
function drainCards(state, out) {
|
|
333
|
+
state.callbacks.complete = false;
|
|
334
|
+
publishCallbacks(state, out);
|
|
335
|
+
for (const requestId of Object.keys(state.permissions))
|
|
336
|
+
settlePermission(state, out, requestId, PERMISSION_CANCELLED, 'canceled', false);
|
|
337
|
+
for (const requestId of Object.keys(state.elicitations))
|
|
338
|
+
settleElicitation(state, out, requestId, ELICITATION_CANCEL);
|
|
339
|
+
}
|
|
340
|
+
/** A request answered without a card. */
|
|
341
|
+
function refuseRequest(state, out, id, result) {
|
|
342
|
+
state.diagnostics.refusedRequests += 1;
|
|
343
|
+
out.result(id, result);
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
/** `AcpSession.registerPendingApproval`'s field rule. */
|
|
347
|
+
function badCardField(value) {
|
|
348
|
+
// `Buffer.byteLength` without `Buffer`: UTF-8 length of a JS string.
|
|
349
|
+
let bytes = 0;
|
|
350
|
+
for (const char of value) {
|
|
351
|
+
const code = char.codePointAt(0);
|
|
352
|
+
bytes += code < 0x80 ? 1 : code < 0x800 ? 2 : code < 0x10000 ? 3 : 4;
|
|
353
|
+
}
|
|
354
|
+
return !value.trim() || bytes > 256 || /[\x00-\x1f\x7f]/.test(value);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* `session/request_permission` (params already through the SDK's zod schema) — `registerPermissionHandlers`
|
|
358
|
+
* → `handleRequestPermission` → `registerPendingApproval` → `emitEvent`, in their order. Returns the
|
|
359
|
+
* card id if a card now waits for the user (the request stays held), `undefined` if it was answered.
|
|
360
|
+
*/
|
|
361
|
+
export function onPermissionRequest(state, id, params, out) {
|
|
362
|
+
const sessionId = params.sessionId;
|
|
363
|
+
if (typeof sessionId !== 'string' || !sessionId.trim())
|
|
364
|
+
return refuseRequest(state, out, id, PERMISSION_CANCELLED);
|
|
365
|
+
const options = Array.isArray(params.options) ? params.options : [];
|
|
366
|
+
if (options.length === 0)
|
|
367
|
+
return refuseRequest(state, out, id, PERMISSION_CANCELLED);
|
|
368
|
+
if (state.process.status !== 'running')
|
|
369
|
+
return refuseRequest(state, out, id, PERMISSION_CANCELLED);
|
|
370
|
+
const requestId = permissionRequestId(state.process.spawn.spawnId, id);
|
|
371
|
+
const rpcRequestId = String(id);
|
|
372
|
+
const event = buildPermissionRequestedEvent({
|
|
373
|
+
requestId, rpcRequestId, sessionId, toolCall: params.toolCall, options: options,
|
|
374
|
+
});
|
|
375
|
+
if (validateWireEvent(event))
|
|
376
|
+
return refuseRequest(state, out, id, PERMISSION_CANCELLED);
|
|
377
|
+
const origin = sessionId;
|
|
378
|
+
// Inside a load's history window the root is not bound yet, but the session being loaded is it
|
|
379
|
+
// (today's attached session already knows its id).
|
|
380
|
+
const root = state.rootSessionId ?? state.history?.rootSessionId ?? null;
|
|
381
|
+
if (origin !== root && !getOwn(state.permissionOrigins, origin))
|
|
382
|
+
return refuseRequest(state, out, id, PERMISSION_CANCELLED);
|
|
383
|
+
if (state.permissionsInterrupted)
|
|
384
|
+
return refuseRequest(state, out, id, PERMISSION_CANCELLED);
|
|
385
|
+
if ([requestId, origin, rpcRequestId].some(badCardField) || Object.keys(state.permissions).length >= PENDING_PERMISSION_LIMIT
|
|
386
|
+
|| state.permissions[requestId] !== undefined || state.settledPermissions.some((settled) => settled.requestId === requestId)) {
|
|
387
|
+
// `registerPendingApproval`'s refusal: the snapshot can no longer vouch for every card.
|
|
388
|
+
state.callbacks.complete = false;
|
|
389
|
+
publishCallbacks(state, out);
|
|
390
|
+
return refuseRequest(state, out, id, PERMISSION_CANCELLED);
|
|
391
|
+
}
|
|
392
|
+
const rpcKey = inboundKey(id);
|
|
393
|
+
state.permissions[requestId] = { requestId, rpcKey, origin, rpcRequestId, optionIds: options.map((option) => option.optionId) };
|
|
394
|
+
publishCallbacks(state, out);
|
|
395
|
+
const payload = event.payload;
|
|
396
|
+
emitSessionEvent(state, out, { eventName: event.eventName, payload, acpSessionId: origin });
|
|
397
|
+
return requestId;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* `elicitation/create` — `handleCreateElicitation` on a session connection (no `present`, so `'url'`
|
|
401
|
+
* declines) → `handleFormElicitation` → `registerPendingElicitation`. Returns the card id, as
|
|
402
|
+
* {@link onPermissionRequest} does.
|
|
403
|
+
*/
|
|
404
|
+
export function onElicitationRequest(state, id, params, out) {
|
|
405
|
+
if (params.mode !== 'form')
|
|
406
|
+
return refuseRequest(state, out, id, ELICITATION_DECLINE);
|
|
407
|
+
const requestId = String(id);
|
|
408
|
+
if (!requestId)
|
|
409
|
+
return refuseRequest(state, out, id, ELICITATION_DECLINE);
|
|
410
|
+
const request = params;
|
|
411
|
+
if (elicitationFieldKeys(request.requestedSchema).length === 0)
|
|
412
|
+
return refuseRequest(state, out, id, ELICITATION_DECLINE);
|
|
413
|
+
const event = buildElicitationRequestedEvent(request, requestId);
|
|
414
|
+
if (validateWireEvent(event))
|
|
415
|
+
return refuseRequest(state, out, id, ELICITATION_DECLINE);
|
|
416
|
+
if (typeof request.message === 'string' && isBlank(request.message)) {
|
|
417
|
+
// Legible, just not collectible: raise it, then decline (`handleFormElicitation`).
|
|
418
|
+
emitSessionEvent(state, out, event);
|
|
419
|
+
return refuseRequest(state, out, id, ELICITATION_DECLINE);
|
|
420
|
+
}
|
|
421
|
+
if (state.elicitations[requestId] === undefined && Object.keys(state.elicitations).length >= PENDING_ELICITATION_LIMIT) {
|
|
422
|
+
return refuseRequest(state, out, id, ELICITATION_DECLINE);
|
|
423
|
+
}
|
|
424
|
+
// Same id twice replaces the earlier card, as `pendingElicitations.set` does.
|
|
425
|
+
state.elicitations[requestId] = { requestId, rpcKey: inboundKey(id) };
|
|
426
|
+
emitSessionEvent(state, out, event);
|
|
427
|
+
return requestId;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* The agent sent `$/cancel_request` for one of its requests: a card waiting on it is settled the way
|
|
431
|
+
* the handler's abort listener settles it (permission → cancelled, elicitation → `cancel`). The card
|
|
432
|
+
* the server shows is not retracted, as today.
|
|
433
|
+
*/
|
|
434
|
+
export function onInboundCancelled(state, rpcKey, out) {
|
|
435
|
+
const card = state.inbound[rpcKey]?.card;
|
|
436
|
+
if (card === undefined)
|
|
437
|
+
return;
|
|
438
|
+
if (state.permissions[card]?.rpcKey === rpcKey)
|
|
439
|
+
settlePermission(state, out, card, PERMISSION_CANCELLED, 'canceled');
|
|
440
|
+
else if (state.elicitations[card]?.rpcKey === rpcKey)
|
|
441
|
+
settleElicitation(state, out, card, ELICITATION_CANCEL);
|
|
442
|
+
}
|
|
443
|
+
/** A low-level `rpc.respond` answered a carded request: the card goes with it (settled as answered). */
|
|
444
|
+
export function forgetCard(state, rpcKey, card, out) {
|
|
445
|
+
if (card === undefined)
|
|
446
|
+
return;
|
|
447
|
+
if (state.permissions[card]?.rpcKey === rpcKey) {
|
|
448
|
+
delete state.permissions[card];
|
|
449
|
+
rememberSettledPermission(state, card, 'answered');
|
|
450
|
+
publishCallbacks(state, out);
|
|
451
|
+
}
|
|
452
|
+
else if (state.elicitations[card]?.rpcKey === rpcKey) {
|
|
453
|
+
delete state.elicitations[card];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/** `agent_answer{requestId, outcome}` — `AcpSession.answer`; an unknown id is ignored (counted). */
|
|
457
|
+
export function onAnswerNote(state, note, out) {
|
|
458
|
+
const { requestId, outcome } = note;
|
|
459
|
+
const entry = typeof requestId === 'string' ? state.permissions[requestId] : undefined;
|
|
460
|
+
if (!entry || typeof outcome !== 'string' || !connected(state)) {
|
|
461
|
+
state.diagnostics.droppedNotes += 1;
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
settlePermission(state, out, entry.requestId, entry.optionIds.includes(outcome)
|
|
465
|
+
? { outcome: { outcome: 'selected', optionId: outcome } }
|
|
466
|
+
: PERMISSION_CANCELLED, 'answered');
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* `agent_answer_undeliverable{requestId}` — a permission card the server cannot be shown: the send-time
|
|
470
|
+
* projection dropped it (a child session's card for a peer older than `AGENT_WIRE_SCOPED_ORIGIN_VERSION`),
|
|
471
|
+
* so the daemon logs this note and the agent is answered "cancelled", as `onAgentEvent` answers it
|
|
472
|
+
* today. Driven by a note, so a replay answers it again from the log, never from the connection.
|
|
473
|
+
*/
|
|
474
|
+
export function onUndeliverableNote(state, note, out) {
|
|
475
|
+
const { requestId } = note;
|
|
476
|
+
const entry = typeof requestId === 'string' ? getOwn(state.permissions, requestId) : undefined;
|
|
477
|
+
if (!entry || !connected(state)) {
|
|
478
|
+
state.diagnostics.droppedNotes += 1;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
settlePermission(state, out, entry.requestId, PERMISSION_CANCELLED, 'answered');
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* `agent_elicitation_answer{requestId, action, content?}` — `agentSessions.ts`'s narrowing (an action
|
|
485
|
+
* other than accept/decline/cancel declines) + `AcpSession.answerElicitation`.
|
|
486
|
+
*/
|
|
487
|
+
export function onElicitationAnswerNote(state, note, out) {
|
|
488
|
+
const { requestId, action, content } = note;
|
|
489
|
+
const entry = typeof requestId === 'string' ? state.elicitations[requestId] : undefined;
|
|
490
|
+
if (!entry || typeof action !== 'string' || !connected(state) || (content !== undefined && !isRecord(content))) {
|
|
491
|
+
state.diagnostics.droppedNotes += 1;
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const known = action === 'accept' || action === 'decline' || action === 'cancel';
|
|
495
|
+
settleElicitation(state, out, entry.requestId, known ? { action, ...(content ? { content } : {}) } : ELICITATION_DECLINE);
|
|
496
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** State history: 2 — open-session layer; 3 — callback snapshots and adapter fold data;
|
|
2
|
+
* 4 — polled process state; 5 — adapter fold data and retired optional state removed.
|
|
3
|
+
* Schemas before 4 remain unreadable; schema 4 is upgraded by checkpoint.ts. */
|
|
4
|
+
export const PROCESSOR_STATE_SCHEMA = 5;
|
|
5
|
+
export const SETTLED_HISTORY_LIMIT = 16;
|
|
6
|
+
export const SETTLED_PERMISSION_LIMIT = 64;
|
|
7
|
+
export const PENDING_PERMISSION_LIMIT = 64;
|
|
8
|
+
export const UNRESOLVED_ORIGIN_LIMIT = 1024;
|
|
9
|
+
export const STEERED_HISTORY_LIMIT = 64;
|
|
10
|
+
/** Unfinished async tasks tracked per connection, all sessions together. */
|
|
11
|
+
export const ASYNC_TASK_LIMIT = 1024;
|
|
12
|
+
export function initialRuntime() {
|
|
13
|
+
return { revision: 0, available: true };
|
|
14
|
+
}
|
|
15
|
+
export function initialCallbacks() {
|
|
16
|
+
return { complete: true, rootSessionId: null };
|
|
17
|
+
}
|
|
18
|
+
export function initialMessage() {
|
|
19
|
+
return { open: false, messageId: null, outOfTurn: false };
|
|
20
|
+
}
|
|
21
|
+
export function initialState() {
|
|
22
|
+
return {
|
|
23
|
+
schema: PROCESSOR_STATE_SCHEMA,
|
|
24
|
+
offset: -1,
|
|
25
|
+
t: 0,
|
|
26
|
+
process: { status: 'none' },
|
|
27
|
+
initialize: { status: 'idle' },
|
|
28
|
+
outbound: {},
|
|
29
|
+
inbound: {},
|
|
30
|
+
rootSessionId: null,
|
|
31
|
+
seq: 0,
|
|
32
|
+
turns: [],
|
|
33
|
+
exactExecutionUnconfirmed: false,
|
|
34
|
+
backgroundWriterUnconfirmed: false,
|
|
35
|
+
permissionsInterrupted: false,
|
|
36
|
+
permissions: {},
|
|
37
|
+
elicitations: {},
|
|
38
|
+
settledPermissions: [],
|
|
39
|
+
callbacks: initialCallbacks(),
|
|
40
|
+
permissionOrigins: {},
|
|
41
|
+
permissionParents: {},
|
|
42
|
+
unresolvedPermissionOrigins: {},
|
|
43
|
+
open: { status: 'idle' },
|
|
44
|
+
configOptions: null,
|
|
45
|
+
history: null,
|
|
46
|
+
mapperScopes: {},
|
|
47
|
+
message: initialMessage(),
|
|
48
|
+
steers: {},
|
|
49
|
+
steered: [],
|
|
50
|
+
draftSubagents: { children: {}, overflow: false },
|
|
51
|
+
asyncTasks: {},
|
|
52
|
+
activity: null,
|
|
53
|
+
runtime: initialRuntime(),
|
|
54
|
+
diagnostics: {
|
|
55
|
+
orphanResponses: 0, protocolErrors: 0, ignoredLines: 0, unknownNotes: 0, droppedNotes: 0, refusedRequests: 0,
|
|
56
|
+
rejectedRelations: 0, droppedUpdates: 0, invalidEvents: 0, untrackedAsyncTasks: 0, settled: [],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Set `record[key]` as an own property: agent-chosen keys (`__proto__`) stay plain keys. */
|
|
61
|
+
export function putOwn(record, key, value) {
|
|
62
|
+
Object.defineProperty(record, key, { value, enumerable: true, writable: true, configurable: true });
|
|
63
|
+
}
|
|
64
|
+
/** `record[key]` if it is an own property. */
|
|
65
|
+
export function getOwn(record, key) {
|
|
66
|
+
return Object.hasOwn(record, key) ? record[key] : undefined;
|
|
67
|
+
}
|