@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import semver from 'semver';
|
|
4
|
+
import { assertSafePackageName } from './builds/model.js';
|
|
4
5
|
// Production reads only assets shipped in this CLI. The .ts path is for source-based tests.
|
|
5
6
|
export const patchAssetRoot = new URL(import.meta.url.endsWith('.ts') ? '../../../../axom-patch/cli/' : '../../axom-patch/cli/', import.meta.url);
|
|
6
7
|
const runtime = JSON.parse(readFileSync(new URL('runtime.json', patchAssetRoot), 'utf8'));
|
|
@@ -20,9 +21,16 @@ function validateCondition(id, when, until, whenKey, untilKey) {
|
|
|
20
21
|
export function validateOverride(entry) {
|
|
21
22
|
validateCondition(entry.id, entry.when_registry_version, entry.until_registry_version, 'when_registry_version', 'until_registry_version');
|
|
22
23
|
const until = entry.until_registry_version;
|
|
23
|
-
// A pin
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// A catch-up pin may equal its bound: use it until the registry publishes that release.
|
|
25
|
+
// A pin above the bound would instead let the registry replace it with an older release.
|
|
26
|
+
if (until !== undefined && entry.distribution && !(semver.valid(entry.distribution.version) && semver.lte(entry.distribution.version, until))) {
|
|
27
|
+
throw new Error(`Axom patch ${entry.id}: pinned ${entry.distribution.version} must not sort above ${until}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Portable relative paths only: no platform-specific interpretation or normalization. */
|
|
31
|
+
export function validatePatchPath(value) {
|
|
32
|
+
if (typeof value !== 'string' || !value || /[\\:\0]/.test(value) || value.split('/').some(part => !part || part === '.' || part === '..')) {
|
|
33
|
+
throw new Error('Axom patch path escapes its root or is not a portable relative path');
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
export function validatePackagePatch(recipe) {
|
|
@@ -30,6 +38,16 @@ export function validatePackagePatch(recipe) {
|
|
|
30
38
|
if (recipe.versions === undefined && recipe.until_version === undefined) {
|
|
31
39
|
throw new Error(`Axom patch ${recipe.id}: needs versions or until_version`);
|
|
32
40
|
}
|
|
41
|
+
for (const edit of recipe.edits)
|
|
42
|
+
validatePatchPath(edit.file);
|
|
43
|
+
if (recipe.assets !== undefined && !Array.isArray(recipe.assets))
|
|
44
|
+
throw new Error('Axom patch assets must be an array');
|
|
45
|
+
for (const asset of recipe.assets ?? []) {
|
|
46
|
+
validatePatchPath(asset.source);
|
|
47
|
+
validatePatchPath(asset.target);
|
|
48
|
+
if (!/^[a-f0-9]{64}$/.test(asset.sha256))
|
|
49
|
+
throw new Error('Axom patch asset sha256 must be a lowercase SHA-256 digest');
|
|
50
|
+
}
|
|
33
51
|
}
|
|
34
52
|
function inRange(version, when, until) {
|
|
35
53
|
if (when)
|
|
@@ -61,3 +79,42 @@ export const packagePatches = runtime.package_patches.map((file) => {
|
|
|
61
79
|
validatePackagePatch(recipe);
|
|
62
80
|
return recipe;
|
|
63
81
|
});
|
|
82
|
+
/** The declared native wrappers, for the catalog↔patch consistency guard in `catalog.test.ts`. */
|
|
83
|
+
export const nativeExtensions = runtime.native_extensions ?? [];
|
|
84
|
+
/** The bundled overrides, for the same catalog↔patch consistency guard. */
|
|
85
|
+
export const runtimeOverrides = runtime.overrides;
|
|
86
|
+
/** Native wrappers are declared in the patch inventory, never selected by agent-specific launch branches. */
|
|
87
|
+
export function nativeExtensionFor(agent, version) {
|
|
88
|
+
const extension = runtime.native_extensions?.find(entry => entry.agent === agent && entry.versions.includes(version));
|
|
89
|
+
if (!extension)
|
|
90
|
+
return undefined;
|
|
91
|
+
if (extension.entry.startsWith('/') || extension.entry.split('/').includes('..'))
|
|
92
|
+
throw new Error('Invalid native extension path');
|
|
93
|
+
return new URL(extension.entry, patchAssetRoot);
|
|
94
|
+
}
|
|
95
|
+
export function nativeAccountLimitsVersionFor(agent, version) {
|
|
96
|
+
return runtime.native_extensions?.find(entry => entry.agent === agent && entry.versions.includes(version))?.accountLimitsVersion ?? 2;
|
|
97
|
+
}
|
|
98
|
+
/** One entry per package; the only policy is skipping npm lifecycle scripts a package would run in the user's HOME. */
|
|
99
|
+
export function validateInstallPolicies(policies) {
|
|
100
|
+
if (!Array.isArray(policies))
|
|
101
|
+
throw new Error('Axom install_policies must be an array');
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
for (const policy of policies) {
|
|
104
|
+
if (typeof policy?.package !== 'string')
|
|
105
|
+
throw new Error('Axom install policy needs a package name');
|
|
106
|
+
assertSafePackageName(policy.package);
|
|
107
|
+
if (seen.has(policy.package))
|
|
108
|
+
throw new Error(`Axom install policy for ${policy.package} is duplicated`);
|
|
109
|
+
if (policy.ignore_scripts !== true)
|
|
110
|
+
throw new Error(`Axom install policy for ${policy.package} must set ignore_scripts: true`);
|
|
111
|
+
seen.add(policy.package);
|
|
112
|
+
}
|
|
113
|
+
return policies;
|
|
114
|
+
}
|
|
115
|
+
export const installPolicies = validateInstallPolicies(runtime.install_policies ?? []);
|
|
116
|
+
export function installPolicyFor(name, policies = installPolicies) {
|
|
117
|
+
return policies.find((policy) => policy.package === name);
|
|
118
|
+
}
|
|
119
|
+
/** Optional strict release policies; activation is an explicit bundled manifest change. */
|
|
120
|
+
export const managedRuntimePolicies = runtime.managed_runtimes ?? [];
|
|
@@ -78,7 +78,6 @@
|
|
|
78
78
|
* complete it, so `authenticate` remembers every id it handed out and retracts what is left when
|
|
79
79
|
* the login settles.
|
|
80
80
|
*/
|
|
81
|
-
import { randomUUID } from 'node:crypto';
|
|
82
81
|
import { methods } from '@agentclientprotocol/sdk';
|
|
83
82
|
import { validateWireEvent } from './validate.js';
|
|
84
83
|
/** A value guaranteed not to equal any real `optionId` in `input.options` for a given call — used to purge a pending entry without accidentally supplying a matchable answer. Regenerated per call rather than a fixed literal, for the same anti-collision reasoning as `AcpSession`'s own abandonment sentinel (review finding, task-5 item 6). */
|
|
@@ -123,8 +122,9 @@ function toolCallPayload(toolCall) {
|
|
|
123
122
|
* ACP `RequestPermissionRequest` (`types.gen.d.ts:108`) on Axom's wire: `{toolCall, options}` plus
|
|
124
123
|
* `requestId`.
|
|
125
124
|
*
|
|
126
|
-
* `requestId` is a host-
|
|
127
|
-
*
|
|
125
|
+
* `requestId` is a host-derived resolver token — {@link permissionRequestId}, the same on the live
|
|
126
|
+
* path and in the ACP processor. The raw JSON-RPC ID stays local to its connection; exact `sessionId`
|
|
127
|
+
* travels in the wire event's `acpSessionId` origin field.
|
|
128
128
|
*
|
|
129
129
|
* **`toolCall` is nested, not flattened.** The flattened version lifted the tool's
|
|
130
130
|
* `name`/`title`/`kind`/`arguments` to the top level, which put `name` (the tool's programmatic name,
|
|
@@ -135,7 +135,7 @@ function toolCallPayload(toolCall) {
|
|
|
135
135
|
* `toolCall.title`, and the renderer computes it from the same field now
|
|
136
136
|
* (`WorkTimelineInteractions.foldPermission`) instead of storing a second, drifting copy.
|
|
137
137
|
*/
|
|
138
|
-
function buildPermissionRequestedEvent(input) {
|
|
138
|
+
export function buildPermissionRequestedEvent(input) {
|
|
139
139
|
return {
|
|
140
140
|
eventName: 'permission_request',
|
|
141
141
|
...(input.sessionId !== undefined ? { acpSessionId: input.sessionId } : {}),
|
|
@@ -164,7 +164,7 @@ function buildPermissionRequestedEvent(input) {
|
|
|
164
164
|
* `sink.answer` — the agent gave up on this specific request, so nothing should still expect an
|
|
165
165
|
* answer for it. Session-level abandonment (the session closing, or the connection dying) is instead
|
|
166
166
|
* `AcpSession`'s own job (`registerPendingApproval` resolving immediately when already closed, and
|
|
167
|
-
* `runReadLoop` draining on an aborted connection) — see `acpSession.ts
|
|
167
|
+
* `runReadLoop` draining on an aborted connection) — see the old `acpSession.ts` (deleted in Task 29).
|
|
168
168
|
*
|
|
169
169
|
* The eventual answer is whatever string reaches `sink`'s registered resolver: normally one of
|
|
170
170
|
* `input.options`' own `optionId`s (relayed from the user through `AcpSession.answer`), but both
|
|
@@ -320,7 +320,7 @@ function isFoldedCompanionKey(key, properties) {
|
|
|
320
320
|
* each return `null` from `elicitationRequestFromPayload`, because `MODE_FORM` requires
|
|
321
321
|
* `fields.isNotEmpty()`. Everything else it now decodes (see {@link handleFormElicitation}).
|
|
322
322
|
*/
|
|
323
|
-
function elicitationFieldKeys(requestedSchema) {
|
|
323
|
+
export function elicitationFieldKeys(requestedSchema) {
|
|
324
324
|
const properties = requestedSchema?.properties;
|
|
325
325
|
if (properties === null || typeof properties !== 'object' || Array.isArray(properties))
|
|
326
326
|
return [];
|
|
@@ -336,7 +336,7 @@ function elicitationFieldKeys(requestedSchema) {
|
|
|
336
336
|
* request is dropped server-side, which is why this check survived Task 9's removal of the
|
|
337
337
|
* per-property profile predicate (see the ruling-R24 block below).
|
|
338
338
|
*/
|
|
339
|
-
function isBlank(value) {
|
|
339
|
+
export function isBlank(value) {
|
|
340
340
|
return value.trim().length === 0;
|
|
341
341
|
}
|
|
342
342
|
/**
|
|
@@ -406,7 +406,7 @@ function isBlank(value) {
|
|
|
406
406
|
* event always carries exactly what the agent asked; {@link handleFormElicitation} is what decides,
|
|
407
407
|
* from that same schema, whether to wait for an answer or decline right after raising the card.
|
|
408
408
|
*/
|
|
409
|
-
function buildElicitationRequestedEvent(request, requestId) {
|
|
409
|
+
export function buildElicitationRequestedEvent(request, requestId) {
|
|
410
410
|
return {
|
|
411
411
|
eventName: 'elicitation_request',
|
|
412
412
|
payload: {
|
|
@@ -521,6 +521,35 @@ async function handleFormElicitation(request, ctx, sink) {
|
|
|
521
521
|
export function handleElicitationComplete(notification, deps) {
|
|
522
522
|
deps.dismiss(notification.elicitationId);
|
|
523
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* The card id of one `session/request_permission`: `perm:<hostGeneration>:<JSON agent id>` (spec
|
|
526
|
+
* 2026-10-11 §5.4).
|
|
527
|
+
*
|
|
528
|
+
* ## Why derived, not random
|
|
529
|
+
*
|
|
530
|
+
* It used to be `randomUUID()` per invocation. A card id the server is already showing must still
|
|
531
|
+
* resolve after the daemon restarts underneath a live adapter, and a random id cannot: the re-presented
|
|
532
|
+
* request would get a new one, `settleApproval` ignores unknown ids, and the user's answer to the
|
|
533
|
+
* visible card would be lost (analysis A §4). Derived from the request itself, the same request gets
|
|
534
|
+
* the same id in every daemon generation, and the ACP processor — which may not draw randomness at
|
|
535
|
+
* all (`processor/guard.test.ts`) — computes it with this very function.
|
|
536
|
+
*
|
|
537
|
+
* ## Why it is still unique
|
|
538
|
+
*
|
|
539
|
+
* - `hostGeneration` identifies one adapter process: the keeper's `spawnId` on the processor path,
|
|
540
|
+
* `AgentRuntimeDiagnostics.generation` (fresh per `connectAgent`) on today's live path — the same
|
|
541
|
+
* value published as `callbackContext.hostGeneration`. A reconnect is a new process, so a new
|
|
542
|
+
* generation: the agent's ids restart at 0 there without colliding with an earlier card.
|
|
543
|
+
* - Within one process the agent never reuses a pending JSON-RPC id. `JSON.stringify` keeps the
|
|
544
|
+
* number `1` and the string `"1"` apart.
|
|
545
|
+
*
|
|
546
|
+
* Kept short enough for `AcpSession.registerPendingApproval`'s 256-byte rule for every id a real
|
|
547
|
+
* adapter sends; a pathological id longer than ~200 bytes is refused there, as an overlong
|
|
548
|
+
* `rpcRequestId` already was.
|
|
549
|
+
*/
|
|
550
|
+
export function permissionRequestId(hostGeneration, agentRequestId) {
|
|
551
|
+
return `perm:${hostGeneration}:${JSON.stringify(agentRequestId)}`;
|
|
552
|
+
}
|
|
524
553
|
/**
|
|
525
554
|
* Register this module's three handlers onto `app`. Must run inside `connectAgent`'s `configure`
|
|
526
555
|
* hook — see the file doc comment for why a post-connect `.onRequest`/`.onNotification` would compile
|
|
@@ -534,15 +563,18 @@ export function handleElicitationComplete(notification, deps) {
|
|
|
534
563
|
* the call site instead. Its own `present` may still be omitted (see {@link PresentElicitationUrl}) —
|
|
535
564
|
* that is `acpAgents.ts`'s session connection saying, on purpose, that a `url` elicitation raised
|
|
536
565
|
* outside a login has nowhere to go, and it declines rather than accepting into nothing.
|
|
566
|
+
*
|
|
567
|
+
* `connection.generation` is the adapter process's generation — `connectAgent` hands it to its
|
|
568
|
+
* `configure` hook — and scopes the card ids {@link permissionRequestId} derives.
|
|
537
569
|
*/
|
|
538
|
-
export function registerPermissionHandlers(app, sink, elicitationDeps) {
|
|
570
|
+
export function registerPermissionHandlers(app, sink, elicitationDeps, connection) {
|
|
539
571
|
app
|
|
540
572
|
.onRequest(methods.client.session.requestPermission, async (ctx) => {
|
|
541
573
|
if (typeof ctx.params.sessionId !== 'string' || !ctx.params.sessionId.trim()) {
|
|
542
574
|
console.error(JSON.stringify({ component: 'acp', error: 'invalid_permission_origin', rpcRequestId: String(ctx.requestId) }));
|
|
543
575
|
return { outcome: { outcome: 'cancelled' } };
|
|
544
576
|
}
|
|
545
|
-
const decision = await handleRequestPermission({ requestId:
|
|
577
|
+
const decision = await handleRequestPermission({ requestId: permissionRequestId(connection.generation, ctx.requestId), rpcRequestId: String(ctx.requestId), sessionId: ctx.params.sessionId, toolCall: ctx.params.toolCall, options: ctx.params.options, signal: ctx.signal }, sink);
|
|
546
578
|
return {
|
|
547
579
|
outcome: decision.outcome === 'answered' ? { outcome: 'selected', optionId: decision.optionId } : { outcome: 'cancelled' },
|
|
548
580
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Checkpoint schema answers whether state is readable; processorVersion identifies output semantics. Readable older processor versions remain restorable; replay differences across versions do not count as divergence. Version 9 moves adapter-specific compatibility folds out and removes retired optional state. */
|
|
2
|
+
import { initialState, PROCESSOR_STATE_SCHEMA } from './state.js';
|
|
3
|
+
/**
|
|
4
|
+
* Output history: 2 — turns, cards, seq and semantic-only request checks;
|
|
5
|
+
* 3 — session updates, binding, config, steering and validated events;
|
|
6
|
+
* 4 — spawn-bound commands and history dropped counts; prompt closes history;
|
|
7
|
+
* 5 — callback snapshots, runtime revisions and adapter compatibility folds;
|
|
8
|
+
* 6 — child status precedes process results; ended calls release data;
|
|
9
|
+
* 7 — install outcomes become step events;
|
|
10
|
+
* 8 — loaded-session levels fold live; output gaps become steps;
|
|
11
|
+
* 9 — adapter compatibility folds moved out and retired optional state removed.
|
|
12
|
+
*/
|
|
13
|
+
export const PROCESSOR_VERSION = 9;
|
|
14
|
+
export const CHECKPOINT_SCHEMA = 1;
|
|
15
|
+
export class CheckpointUnreadableError extends Error {
|
|
16
|
+
}
|
|
17
|
+
function clone(value) {
|
|
18
|
+
return JSON.parse(JSON.stringify(value));
|
|
19
|
+
}
|
|
20
|
+
/** A detached copy of `state`, so later steps cannot reach into a checkpoint already handed out. */
|
|
21
|
+
export function checkpointOf(state) {
|
|
22
|
+
return { schema: CHECKPOINT_SCHEMA, processorVersion: PROCESSOR_VERSION, offset: state.offset, state: clone(state) };
|
|
23
|
+
}
|
|
24
|
+
export function encodeCheckpoint(checkpoint) {
|
|
25
|
+
return JSON.stringify(checkpoint);
|
|
26
|
+
}
|
|
27
|
+
function isRecord(value) {
|
|
28
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
/** Parse and check a checkpoint blob (string or already-parsed JSON). */
|
|
31
|
+
export function decodeCheckpoint(blob) {
|
|
32
|
+
let value = blob;
|
|
33
|
+
if (typeof blob === 'string') {
|
|
34
|
+
try {
|
|
35
|
+
value = JSON.parse(blob);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new CheckpointUnreadableError('checkpoint is not JSON');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!isRecord(value))
|
|
42
|
+
throw new CheckpointUnreadableError('checkpoint is not an object');
|
|
43
|
+
if (value.schema !== CHECKPOINT_SCHEMA)
|
|
44
|
+
throw new CheckpointUnreadableError(`unknown checkpoint schema ${JSON.stringify(value.schema)}`);
|
|
45
|
+
const { processorVersion, offset, state } = value;
|
|
46
|
+
if (typeof processorVersion !== 'number' || !Number.isInteger(processorVersion))
|
|
47
|
+
throw new CheckpointUnreadableError('checkpoint has no processorVersion');
|
|
48
|
+
if (typeof offset !== 'number' || !Number.isSafeInteger(offset) || offset < -1)
|
|
49
|
+
throw new CheckpointUnreadableError('checkpoint offset is invalid');
|
|
50
|
+
if (!isRecord(state) || (state.schema !== 4 && state.schema !== PROCESSOR_STATE_SCHEMA))
|
|
51
|
+
throw new CheckpointUnreadableError('checkpoint state schema is unknown');
|
|
52
|
+
if (state.offset !== offset)
|
|
53
|
+
throw new CheckpointUnreadableError('checkpoint offset disagrees with its state');
|
|
54
|
+
for (const key of ['process', 'initialize', 'outbound', 'inbound', 'diagnostics', 'permissions', 'elicitations',
|
|
55
|
+
'permissionOrigins', 'permissionParents', 'unresolvedPermissionOrigins', 'open', 'mapperScopes', 'message', 'steers',
|
|
56
|
+
'asyncTasks', 'runtime', 'callbacks']) {
|
|
57
|
+
if (!isRecord(state[key]))
|
|
58
|
+
throw new CheckpointUnreadableError(`checkpoint state has no ${key}`);
|
|
59
|
+
}
|
|
60
|
+
if (typeof state.seq !== 'number' || !Array.isArray(state.turns) || !Array.isArray(state.settledPermissions)) {
|
|
61
|
+
throw new CheckpointUnreadableError('checkpoint state has no session layer (seq/turns)');
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(state.steered) || !('configOptions' in state) || !('history' in state)
|
|
64
|
+
|| !('activity' in state)) {
|
|
65
|
+
throw new CheckpointUnreadableError('checkpoint state has no open-session layer (Task 17b)');
|
|
66
|
+
}
|
|
67
|
+
return { schema: CHECKPOINT_SCHEMA, processorVersion, offset, state: clone({ ...(state.schema === 4 ? upgradeStateFromSchema4(state) : state), ...(!Object.hasOwn(state, 'draftSubagents') ? { draftSubagents: { children: {}, overflow: false } } : {}) }) };
|
|
68
|
+
}
|
|
69
|
+
/** Read only the current neutral keys; retired adapter state is deliberately not copied. */
|
|
70
|
+
function upgradeStateFromSchema4(state) {
|
|
71
|
+
const keys = [...Object.keys(initialState()), 'closed', 'accountAuthBillingModes', 'accountLimits'];
|
|
72
|
+
const current = Object.fromEntries(keys.filter((key) => Object.hasOwn(state, key)).map((key) => [key, state[key]]));
|
|
73
|
+
const runtime = state.runtime;
|
|
74
|
+
return { ...current, schema: PROCESSOR_STATE_SCHEMA, runtime: {
|
|
75
|
+
revision: runtime.revision, available: runtime.available,
|
|
76
|
+
...(Object.hasOwn(runtime, 'launch') ? { launch: runtime.launch } : {}),
|
|
77
|
+
} };
|
|
78
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** The line to write for `command`: `fill`ed from `resolve`, or `undefined` if its blocks are gone. */
|
|
2
|
+
export function materializeLine(command, resolve) {
|
|
3
|
+
if (command.fill === undefined)
|
|
4
|
+
return command.line;
|
|
5
|
+
const blocks = resolve?.(command.fill.blocksRef);
|
|
6
|
+
if (blocks === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
const message = JSON.parse(command.line);
|
|
9
|
+
// Assigning an existing key keeps its position: the line keeps the SDK's key order.
|
|
10
|
+
message.params.prompt = blocks;
|
|
11
|
+
return JSON.stringify(message);
|
|
12
|
+
}
|
|
13
|
+
export function executeDownCommands(sink, commands, resolve) {
|
|
14
|
+
const written = [];
|
|
15
|
+
const dropped = [];
|
|
16
|
+
const refused = [];
|
|
17
|
+
const unresolved = [];
|
|
18
|
+
for (const command of commands) {
|
|
19
|
+
if (sink.hasDown(command.cause)) {
|
|
20
|
+
dropped.push(command);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const line = materializeLine(command, resolve);
|
|
24
|
+
if (line === undefined) {
|
|
25
|
+
unresolved.push(command);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const { fill: _fill, ...rest } = command;
|
|
29
|
+
if (sink.writeDown({ ...rest, line }) === 'refused')
|
|
30
|
+
refused.push(command);
|
|
31
|
+
else
|
|
32
|
+
written.push(command);
|
|
33
|
+
}
|
|
34
|
+
return { written, dropped, refused, unresolved };
|
|
35
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/** Open-session binding, configuration, steering and launch notes. A response binds the native root; a failed load never silently creates another session here. The daemon supplies the finished MCP list with secrets replaced by references. */
|
|
2
|
+
import { newSessionMetaFor } from '../activityReporting.js';
|
|
3
|
+
import { flatChoicesOf, modesAlreadyRepresented, modelsAlreadyRepresented, normalizeConfigOptions, SYNTHESIZED_MODES_CONFIG_ID, SYNTHESIZED_MODEL_CONFIG_ID } from '../configOptions.js';
|
|
4
|
+
import { ASYNC_TASK_STOP_METHOD, mapSteerOutcome, STEER_METHOD } from '../steering.js';
|
|
5
|
+
import { emitEvent, isRecord } from './emit.js';
|
|
6
|
+
import { settlementErrorCode } from './jsonrpc.js';
|
|
7
|
+
import { sameCause } from './record.js';
|
|
8
|
+
import { onLaunchNote } from './runtime.js';
|
|
9
|
+
import { bindRootSession, closeErrorText, connected } from './session.js';
|
|
10
|
+
import { getOwn, putOwn, STEERED_HISTORY_LIMIT, } from './state.js';
|
|
11
|
+
import { endHistory } from './updates.js';
|
|
12
|
+
const OPEN_METHODS = { new: 'session/new', load: 'session/load', resume: 'session/resume' };
|
|
13
|
+
export const NOT_INITIALIZED = 'The ACP connection has not finished initialize';
|
|
14
|
+
export const ALREADY_OPEN = 'An ACP session is already open on this connection';
|
|
15
|
+
export const NOT_OPEN = 'No ACP session is open on this connection';
|
|
16
|
+
function request(state, out, method, params, kind, ctx, extras = {}) {
|
|
17
|
+
const { id, cause } = out.request(method, params, extras);
|
|
18
|
+
const pending = { id, method, kind, cause, ...(ctx !== undefined ? { ctx } : {}) };
|
|
19
|
+
state.outbound[String(id)] = pending;
|
|
20
|
+
return pending;
|
|
21
|
+
}
|
|
22
|
+
function isStringArray(value) {
|
|
23
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
24
|
+
}
|
|
25
|
+
// ---- open ---------------------------------------------------------------------------------------
|
|
26
|
+
export function onOpenNote(state, note, out) {
|
|
27
|
+
const { mode, cwd, sessionId, mcpServers, redact } = note;
|
|
28
|
+
if ((mode !== 'new' && mode !== 'load' && mode !== 'resume') || typeof cwd !== 'string' || cwd === ''
|
|
29
|
+
|| (mode === 'new' ? sessionId !== undefined : typeof sessionId !== 'string' || sessionId === '')
|
|
30
|
+
|| (mcpServers !== undefined && !Array.isArray(mcpServers)) || (redact !== undefined && !isStringArray(redact))) {
|
|
31
|
+
state.diagnostics.droppedNotes += 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const refuse = (error) => out.unjournaled({ t: 'processor.open_result', ok: false, mode, error });
|
|
35
|
+
if (!connected(state))
|
|
36
|
+
return refuse(closeErrorText(state));
|
|
37
|
+
if (state.initialize.status !== 'ready')
|
|
38
|
+
return refuse(NOT_INITIALIZED);
|
|
39
|
+
if (state.open.status === 'pending' || state.open.status === 'open')
|
|
40
|
+
return refuse(ALREADY_OPEN);
|
|
41
|
+
const meta = mode === 'resume' ? undefined : newSessionMetaFor(state.initialize.facts.agentInfo?.name);
|
|
42
|
+
const servers = mcpServers ?? [];
|
|
43
|
+
const params = mode === 'new'
|
|
44
|
+
? { cwd, mcpServers: servers, ...(meta ? { _meta: meta } : {}) }
|
|
45
|
+
: mode === 'load'
|
|
46
|
+
? { sessionId, cwd, mcpServers: servers, ...(meta ? { _meta: meta } : {}) }
|
|
47
|
+
: { sessionId, cwd, mcpServers: servers };
|
|
48
|
+
const pending = request(state, out, OPEN_METHODS[mode], params, 'open', { mode, ...(sessionId !== undefined ? { sessionId } : {}) }, redact !== undefined && redact.length > 0 ? { redact } : {});
|
|
49
|
+
state.open = { status: 'pending', requestId: pending.id, mode };
|
|
50
|
+
if (mode === 'load')
|
|
51
|
+
state.history = { rootSessionId: sessionId, scopes: {}, messages: [], droppedCount: 0 };
|
|
52
|
+
}
|
|
53
|
+
/** The open request settled: on success the response binds the root session, now. */
|
|
54
|
+
export function settleOpen(state, entry, settlement, out) {
|
|
55
|
+
const ctx = entry.ctx;
|
|
56
|
+
if (state.open.status !== 'pending' || state.open.requestId !== entry.id)
|
|
57
|
+
return;
|
|
58
|
+
const { mode } = ctx;
|
|
59
|
+
if (settlement.outcome !== 'result') {
|
|
60
|
+
const code = settlementErrorCode(settlement);
|
|
61
|
+
const error = settlement.outcome === 'error' ? settlement.error.message : closeErrorText(state);
|
|
62
|
+
state.open = { status: 'failed', mode, error, ...(code !== undefined ? { code } : {}) };
|
|
63
|
+
endHistory(state, out, false);
|
|
64
|
+
// A failed open closes the attached session, and its repair with it (`AcpSession.close`).
|
|
65
|
+
out.unjournaled({ t: 'processor.open_result', ok: false, mode, error, ...(code !== undefined ? { code } : {}) });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const result = isRecord(settlement.result) ? settlement.result : {};
|
|
69
|
+
const nativeSessionId = mode === 'new' ? (typeof result.sessionId === 'string' ? result.sessionId : '') : ctx.sessionId;
|
|
70
|
+
if (nativeSessionId.length > 0) {
|
|
71
|
+
bindRootSession(state, nativeSessionId);
|
|
72
|
+
}
|
|
73
|
+
state.configOptions = normalizeConfigOptions(result.configOptions, result.modes, result.models);
|
|
74
|
+
state.open = { status: 'open', mode };
|
|
75
|
+
out.unjournaled({
|
|
76
|
+
t: 'processor.open_result', ok: true, mode,
|
|
77
|
+
nativeSessionId: nativeSessionId.length > 0 ? nativeSessionId : null,
|
|
78
|
+
configOptions: state.configOptions,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** `historyEnd{}` — the daemon's decision that a load's history is over (analysis B A4). */
|
|
82
|
+
export function onHistoryEndNote(state, out) {
|
|
83
|
+
if (!state.history) {
|
|
84
|
+
state.diagnostics.droppedNotes += 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
endHistory(state, out, true);
|
|
88
|
+
}
|
|
89
|
+
/** Usable for a session request: connected, and the open succeeded with a root. */
|
|
90
|
+
function sessionReady(state) {
|
|
91
|
+
if (!connected(state))
|
|
92
|
+
return closeErrorText(state);
|
|
93
|
+
if (state.open.status !== 'open' || state.rootSessionId === null)
|
|
94
|
+
return NOT_OPEN;
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
/** `agent_set_config_option{id, configId, value}` — `setConfigOptionRequest`'s request half. */
|
|
98
|
+
export function onSetConfigOptionNote(state, note, out) {
|
|
99
|
+
const { id, configId, value } = note;
|
|
100
|
+
if (typeof id !== 'string' || typeof configId !== 'string' || (typeof value !== 'string' && typeof value !== 'boolean')) {
|
|
101
|
+
state.diagnostics.droppedNotes += 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const fail = (error) => out.unjournaled({ t: 'agent_config_option_set', id, ok: false, error });
|
|
105
|
+
const unusable = sessionReady(state);
|
|
106
|
+
if (unusable !== undefined)
|
|
107
|
+
return fail(unusable);
|
|
108
|
+
const sessionId = state.rootSessionId;
|
|
109
|
+
const ctx = { id, configId, value };
|
|
110
|
+
if (configId === SYNTHESIZED_MODES_CONFIG_ID) {
|
|
111
|
+
if (typeof value !== 'string')
|
|
112
|
+
return fail(`[acp] ${SYNTHESIZED_MODES_CONFIG_ID} takes a mode id (a string), got ${typeof value}`);
|
|
113
|
+
request(state, out, 'session/set_mode', { sessionId, modeId: value }, 'config', ctx);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// 合成的模型控件走 unstable 的专用 RPC,与 set_mode 同一条保留 id 的路由规则。hermes 回合进行中
|
|
117
|
+
// 会拒绝("Session is busy"),那正是 settle 的错误支要说的话。
|
|
118
|
+
if (configId === SYNTHESIZED_MODEL_CONFIG_ID) {
|
|
119
|
+
if (typeof value !== 'string')
|
|
120
|
+
return fail(`[acp] ${SYNTHESIZED_MODEL_CONFIG_ID} takes a model id (a string), got ${typeof value}`);
|
|
121
|
+
request(state, out, 'session/set_model', { sessionId, modelId: value }, 'config', ctx);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
request(state, out, 'session/set_config_option', typeof value === 'boolean' ? { sessionId, configId, type: 'boolean', value } : { sessionId, configId, value }, 'config', ctx);
|
|
125
|
+
}
|
|
126
|
+
/** The set settled — `setConfigOptionRequest`'s snapshot rules, then `agent_config_option_set`. */
|
|
127
|
+
export function settleConfig(state, entry, settlement, out) {
|
|
128
|
+
const { id, configId, value } = entry.ctx;
|
|
129
|
+
if (settlement.outcome !== 'result') {
|
|
130
|
+
out.unjournaled({ t: 'agent_config_option_set', id, ok: false,
|
|
131
|
+
error: settlement.outcome === 'error' ? settlement.error.message : closeErrorText(state) });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const current = state.configOptions ?? [];
|
|
135
|
+
let next;
|
|
136
|
+
if (entry.method === 'session/set_mode') {
|
|
137
|
+
next = current.map((option) => (option.id === configId && option.type === 'select' ? { ...option, currentValue: value } : option));
|
|
138
|
+
}
|
|
139
|
+
else if (entry.method === 'session/set_model') {
|
|
140
|
+
// hermes 的 SetSessionModelResponse 是空对象(切完不回快照、也不推更新),与 set_mode 同款:
|
|
141
|
+
// 本地把 currentValue 换成刚设的值;agent 若将来回了新状态,也只会被下一次 open 快照纠回真相。
|
|
142
|
+
next = current.map((option) => (option.id === configId && option.type === 'select' ? { ...option, currentValue: value } : option));
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const result = isRecord(settlement.result) ? settlement.result : {};
|
|
146
|
+
const reNormalized = normalizeConfigOptions(result.configOptions, null);
|
|
147
|
+
// 两个合成控件(modes、model)都不在 agent 自己的 configOptions 里,re-normalize 会把它们丢掉;
|
|
148
|
+
// 只要 agent 的新快照里还没有能替代它们的控件,就原样携带。
|
|
149
|
+
next = reNormalized.concat(current.filter((option) => (option.id === SYNTHESIZED_MODES_CONFIG_ID
|
|
150
|
+
&& !modesAlreadyRepresented(reNormalized, flatChoicesOf(option) ?? []))
|
|
151
|
+
|| (option.id === SYNTHESIZED_MODEL_CONFIG_ID && !modelsAlreadyRepresented(reNormalized))));
|
|
152
|
+
}
|
|
153
|
+
state.configOptions = next;
|
|
154
|
+
out.unjournaled({ t: 'agent_config_option_set', id, ok: true, configOptions: next });
|
|
155
|
+
}
|
|
156
|
+
// ---- steering -----------------------------------------------------------------------------------
|
|
157
|
+
/** `agent_steer{id, blocksRef, priority?}` — `steerRequest`, blocks filled by the executor. */
|
|
158
|
+
export function onSteerNote(state, note, out) {
|
|
159
|
+
const { id, blocksRef, priority } = note;
|
|
160
|
+
if (typeof id !== 'string' || id === '' || typeof blocksRef !== 'string' || blocksRef === ''
|
|
161
|
+
|| (priority !== undefined && typeof priority !== 'string')) {
|
|
162
|
+
state.diagnostics.droppedNotes += 1;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const unusable = sessionReady(state);
|
|
166
|
+
if (unusable !== undefined) {
|
|
167
|
+
out.unjournaled({ t: 'agent_steered', id, ok: false, error: unusable });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (getOwn(state.steers, id)) {
|
|
171
|
+
state.diagnostics.droppedNotes += 1;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const pending = request(state, out, STEER_METHOD, {
|
|
175
|
+
sessionId: state.rootSessionId,
|
|
176
|
+
prompt: [],
|
|
177
|
+
_meta: { steering: { idleBehavior: 'promptRequired', ...(priority === undefined ? {} : { priority }) } },
|
|
178
|
+
}, 'steer', { id }, { fill: { blocksRef } });
|
|
179
|
+
putOwn(state.steers, id, { requestId: pending.id, cause: pending.cause, sent: false });
|
|
180
|
+
}
|
|
181
|
+
/** A `down` record: a queued steer's line reached the agent. */
|
|
182
|
+
export function onSteerDown(state, record) {
|
|
183
|
+
const cause = record.annotation?.cause;
|
|
184
|
+
if (!cause)
|
|
185
|
+
return;
|
|
186
|
+
for (const steer of Object.values(state.steers)) {
|
|
187
|
+
if (!steer.sent && sameCause(cause, steer.cause))
|
|
188
|
+
steer.sent = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function rememberSteered(state, id) {
|
|
192
|
+
state.steered.push(id);
|
|
193
|
+
if (state.steered.length > STEERED_HISTORY_LIMIT)
|
|
194
|
+
state.steered.splice(0, state.steered.length - STEERED_HISTORY_LIMIT);
|
|
195
|
+
}
|
|
196
|
+
/** The steer settled: `agent_steered`, with `mapSteerOutcome`'s narrowing (a throw is a failed reply). */
|
|
197
|
+
export function settleSteer(state, entry, settlement, out) {
|
|
198
|
+
const { id } = entry.ctx;
|
|
199
|
+
delete state.steers[id];
|
|
200
|
+
if (settlement.outcome !== 'result') {
|
|
201
|
+
out.unjournaled({ t: 'agent_steered', id, ok: false,
|
|
202
|
+
error: settlement.outcome === 'error' ? settlement.error.message : closeErrorText(state) });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
let outcome;
|
|
206
|
+
try {
|
|
207
|
+
outcome = mapSteerOutcome(settlement.result);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
out.unjournaled({ t: 'agent_steered', id, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (outcome === 'injected')
|
|
214
|
+
rememberSteered(state, id);
|
|
215
|
+
out.unjournaled({ t: 'agent_steered', id, ok: true, outcome });
|
|
216
|
+
}
|
|
217
|
+
/** `agent_prompt_failed` for a steer whose blocks were gone: it never reached the agent. */
|
|
218
|
+
export function failUnsentSteer(state, entry, error, out) {
|
|
219
|
+
const { id } = entry.ctx;
|
|
220
|
+
const steer = getOwn(state.steers, id);
|
|
221
|
+
if (!steer || steer.sent)
|
|
222
|
+
return false;
|
|
223
|
+
delete state.outbound[String(entry.id)];
|
|
224
|
+
delete state.steers[id];
|
|
225
|
+
out.unjournaled({ t: 'agent_steered', id, ok: false, error });
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
/** `agent_opened.steeredIds` (spec §5.5.4): see the file header. */
|
|
229
|
+
export function steeredIds(state) {
|
|
230
|
+
const inFlight = Object.entries(state.steers).filter(([, steer]) => steer.sent).map(([id]) => id);
|
|
231
|
+
return [...state.steered, ...inFlight];
|
|
232
|
+
}
|
|
233
|
+
// ---- background tasks, launch -------------------------------------------------------
|
|
234
|
+
/** `agent_async_task_stop{asyncTaskId}` — `asyncTaskStopRequest`; its answer is diagnostics only. */
|
|
235
|
+
export function onAsyncTaskStopNote(state, note, out) {
|
|
236
|
+
const { asyncTaskId } = note;
|
|
237
|
+
if (typeof asyncTaskId !== 'string' || !asyncTaskId.trim() || sessionReady(state) !== undefined) {
|
|
238
|
+
state.diagnostics.droppedNotes += 1;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
request(state, out, ASYNC_TASK_STOP_METHOD, { sessionId: state.rootSessionId, asyncTaskId }, 'rpc', undefined);
|
|
242
|
+
}
|
|
243
|
+
/** `agent_launch{launch}` (see `runtime.ts`). */
|
|
244
|
+
export function onLaunch(state, note) {
|
|
245
|
+
if (!onLaunchNote(state, note.launch))
|
|
246
|
+
state.diagnostics.droppedNotes += 1;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* `agent_install_outcome{ok, agentName, version, error?}` (Task 20): the adapter install an open had to run first finished within
|
|
250
|
+
* the open's budget (spec §6.2). The daemon writes it right after `agent_launch`; it becomes one past-tense `step` on the session's
|
|
251
|
+
* timeline, numbered like any other event — the words the manager used to journal itself (interface-contracts, 15a/15c).
|
|
252
|
+
*/
|
|
253
|
+
export function onInstallOutcomeNote(state, note, out) {
|
|
254
|
+
const { ok, agentName, version, error } = note;
|
|
255
|
+
if (typeof ok !== 'boolean' || typeof agentName !== 'string' || typeof version !== 'string' || (!ok && typeof error !== 'string')) {
|
|
256
|
+
state.diagnostics.droppedNotes += 1;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
emitEvent(state, out, {
|
|
260
|
+
eventName: 'step',
|
|
261
|
+
payload: ok
|
|
262
|
+
? { title: 'Agent adapter', message: `Installed ${agentName} adapter ${version}`, tone: 'neutral' }
|
|
263
|
+
: { title: 'Agent adapter', message: `Could not install ${agentName} adapter ${version}`, detail: error, tone: 'error' },
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* `agent_output_gap{detail}` (Task 21): this session numbers on in a transport other than the one an earlier life of the ref ran in,
|
|
268
|
+
* and that life's unacknowledged output could not be read (keeper-acp unreachable while the session runs in the daemon) — its frames
|
|
269
|
+
* may never reach the server. The daemon seeded the seq above everything that life may have numbered (spec §5.5), so nothing is
|
|
270
|
+
* reused; this past-tense `step` says that output may be missing.
|
|
271
|
+
*/
|
|
272
|
+
export function onOutputGapNote(state, note, out) {
|
|
273
|
+
const { detail } = note;
|
|
274
|
+
if (typeof detail !== 'string' || detail.length === 0) {
|
|
275
|
+
state.diagnostics.droppedNotes += 1;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
emitEvent(state, out, {
|
|
279
|
+
eventName: 'step',
|
|
280
|
+
payload: { title: 'Agent session', message: 'Earlier output of this session could not be recovered', detail, tone: 'error' },
|
|
281
|
+
});
|
|
282
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { validateWireEvent } from '../validate.js';
|
|
2
|
+
export function nextSeq(state) {
|
|
3
|
+
state.seq += 1;
|
|
4
|
+
return state.seq;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A journaled `agent_event` (the wire frame minus `sessionRef`, which the projection adds). Returns
|
|
8
|
+
* whether it was emitted.
|
|
9
|
+
*/
|
|
10
|
+
export function emitEvent(state, out, event) {
|
|
11
|
+
if (validateWireEvent(event)) {
|
|
12
|
+
state.diagnostics.invalidEvents += 1;
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
out.frame({
|
|
16
|
+
t: 'agent_event',
|
|
17
|
+
seq: nextSeq(state),
|
|
18
|
+
eventName: event.eventName,
|
|
19
|
+
payload: event.payload,
|
|
20
|
+
...(event.acpSessionId ? { acpSessionId: event.acpSessionId } : {}),
|
|
21
|
+
});
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
export function isRecord(value) {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|