@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
|
@@ -3,15 +3,13 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { Readable, Writable } from 'node:stream';
|
|
4
4
|
import { client, ndJsonStream, PROTOCOL_VERSION, RequestError } from '@agentclientprotocol/sdk';
|
|
5
5
|
import { sniffAuthUrl } from './authUrlSniffer.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export class UnsupportedNodeVersionError extends Error {
|
|
14
|
-
}
|
|
6
|
+
import { overlayEnvSecrets } from './envSecrets.js';
|
|
7
|
+
import { secretForms } from '../keepers/acp/stderr.js';
|
|
8
|
+
import { initializeRequestParams, jsonRpcErrorCodeOf, parseInitializeResult, } from './initializeResponse.js';
|
|
9
|
+
import { withPathPrefix } from './registry/launch.js';
|
|
10
|
+
// Moved to `initializeResponse.ts` (shared with the deterministic processor); re-exported so every
|
|
11
|
+
// existing importer of these names is unchanged.
|
|
12
|
+
export { STEER_TIMINGS, steeringAdvertised, steeringTimings, toAuthMethodSnapshot, } from './initializeResponse.js';
|
|
15
13
|
/**
|
|
16
14
|
* A subprocess failed to start, or died before it finished responding to `initialize`.
|
|
17
15
|
*
|
|
@@ -19,8 +17,7 @@ export class UnsupportedNodeVersionError extends Error {
|
|
|
19
17
|
* 上写的那几行 —— npx 的 `npm error code ETARGET`、adapter 的崩溃栈、代理的拒绝。这里以前是
|
|
20
18
|
* `stdio: 'inherit'`,那几行只落进 daemon.log,调用方拿到的是一句
|
|
21
19
|
* "exited before responding to initialize (code=1)",服务端那头再转手就成了一条内容为空的
|
|
22
|
-
* "ACP connection closed" ——
|
|
23
|
-
* 需要的是证据,不是从「管子断了」倒推出来的猜测。
|
|
20
|
+
* "ACP connection closed" —— 排查的人手上什么都没有。
|
|
24
21
|
*/
|
|
25
22
|
export class AgentSpawnError extends Error {
|
|
26
23
|
stderr;
|
|
@@ -50,156 +47,15 @@ export class AgentInitializeTimeoutError extends Error {
|
|
|
50
47
|
*/
|
|
51
48
|
const NPM_ETARGET = /(?:^|[^A-Z])ETARGET(?![A-Z])/;
|
|
52
49
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* `InstallOutcome.error`/`stderr` 字符串,不是这两个类里的一个实例,没有 `.stderr` 字段可供
|
|
56
|
-
* {@link isMissingPackageVersion} 去 `instanceof` 窄化。两个调用点都要认同一件事——npm 自己打在
|
|
57
|
-
* stderr 上的 `ETARGET` 错误码——所以正则只在这一处,两个调用点都直接测字符串,而不是各自维护一份。
|
|
50
|
+
* 判定规则本身:npm 自己打在 stderr 上的 `ETARGET` 错误码。读者是安装期的回退
|
|
51
|
+
* (`AcpAgents.installPristineWithFallback`)——启动期不再有回退,启动从不解析版本(spec 2026-10-11 §6.2)。
|
|
58
52
|
*/
|
|
59
53
|
export function stderrShowsMissingPackageVersion(stderr) {
|
|
60
54
|
return NPM_ETARGET.test(stderr);
|
|
61
55
|
}
|
|
62
|
-
/**
|
|
63
|
-
* 这次启动失败,是不是因为 registry 钉的版本 npm 根本没有。
|
|
64
|
-
*
|
|
65
|
-
* 只看 {@link AgentSpawnError.stderr} / {@link AgentInitializeTimeoutError.stderr} 里的原始文本 ——
|
|
66
|
-
* 超时也算进来是因为实测过:npx 在一台慢机器上可以把 20s 的 initialize 预算耗光之后才让子进程死掉,
|
|
67
|
-
* 那时候 ETARGET 早就写在 stderr 上了,只是赢下 race 的是计时器。
|
|
68
|
-
*/
|
|
69
|
-
export function isMissingPackageVersion(error) {
|
|
70
|
-
if (!(error instanceof AgentSpawnError) && !(error instanceof AgentInitializeTimeoutError))
|
|
71
|
-
return false;
|
|
72
|
-
return stderrShowsMissingPackageVersion(error.stderr);
|
|
73
|
-
}
|
|
74
56
|
/** The agent negotiated a protocol version this client does not speak. */
|
|
75
57
|
export class UnsupportedProtocolVersionError extends Error {
|
|
76
58
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Minimum Node.js major version a known agent package requires, keyed by its npx package name
|
|
79
|
-
* (unversioned — matched against both a bare specifier and a version-pinned one; see
|
|
80
|
-
* `argMatchesPackage`).
|
|
81
|
-
*
|
|
82
|
-
* The registry has no field for this — verified fact, not an oversight to fix here. The only place
|
|
83
|
-
* a Node requirement is declared is the agent package's own `package.json#engines`, and `npx` does
|
|
84
|
-
* not enforce it: it just runs the package under whatever `node` is already on PATH. Getting this
|
|
85
|
-
* wrong means the user sees a subprocess crash stack instead of an actionable message (ruling R7),
|
|
86
|
-
* so this table exists to catch the one adapter known, as of 2026-08-21, to need it.
|
|
87
|
-
*/
|
|
88
|
-
const KNOWN_MIN_NODE_MAJOR = [
|
|
89
|
-
{ packageName: '@agentclientprotocol/claude-agent-acp', minMajor: 22 },
|
|
90
|
-
];
|
|
91
|
-
/**
|
|
92
|
-
* True when `arg` is exactly `packageName`, or `packageName` followed by an `@version` suffix.
|
|
93
|
-
* `arg.startsWith(packageName)` alone would also match an unrelated package that merely shares a
|
|
94
|
-
* prefix (e.g. `@agentclientprotocol/claude-agent-acp-extra`); anchoring on an exact match or an
|
|
95
|
-
* `@` boundary rules that out while still matching ruling R3's user-supplied, unpinned
|
|
96
|
-
* `command`/`args` override, which has no version suffix to key off at all.
|
|
97
|
-
*/
|
|
98
|
-
function argMatchesPackage(arg, packageName) {
|
|
99
|
-
return arg === packageName || arg.startsWith(`${packageName}@`);
|
|
100
|
-
}
|
|
101
|
-
/** Exported for tests: derives the minimum Node major version a launch spec requires, if known. */
|
|
102
|
-
export function requiredNodeMajorFor(spec) {
|
|
103
|
-
const match = KNOWN_MIN_NODE_MAJOR.find((entry) => spec.args.some((arg) => argMatchesPackage(arg, entry.packageName)));
|
|
104
|
-
return match?.minMajor;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Throws a message a user can act on when this machine's Node is older than a known agent
|
|
108
|
-
* requires. `nodeVersion` defaults to the real running Node; it takes an explicit override so tests
|
|
109
|
-
* can exercise both sides of the gate without depending on the host's actual Node version —
|
|
110
|
-
* `process.versions.node` is a frozen property and cannot be stubbed.
|
|
111
|
-
*/
|
|
112
|
-
export function assertNodeVersionSupported(spec, nodeVersion = process.versions.node) {
|
|
113
|
-
const required = requiredNodeMajorFor(spec);
|
|
114
|
-
if (required === undefined)
|
|
115
|
-
return;
|
|
116
|
-
const currentMajor = Number.parseInt(nodeVersion.split('.')[0] ?? '', 10);
|
|
117
|
-
if (Number.isNaN(currentMajor) || currentMajor < required) {
|
|
118
|
-
throw new UnsupportedNodeVersionError(`This agent requires Node.js >= ${required}, but this machine is running Node.js ${nodeVersion}. ` +
|
|
119
|
-
'Install a newer Node.js and try again.');
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Whether an `InitializeResponse._meta` advertises the `_session/steering` extension.
|
|
124
|
-
*
|
|
125
|
-
* ## `_session/steering` is not ACP
|
|
126
|
-
*
|
|
127
|
-
* It is absent from the schema this client is pinned to (`grep steer
|
|
128
|
-
* node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.d.ts` returns nothing). It is an
|
|
129
|
-
* extension `claude-agent-acp` implements: the method constant lives at its `acp-agent.js@0.76.0:164`
|
|
130
|
-
* and its own comment calls it a *"Custom (extension) request method ... Named `_session/steering`
|
|
131
|
-
* per the agreed ACP steering wire protocol"*. The leading underscore is ACP's extension namespace.
|
|
132
|
-
*
|
|
133
|
-
* ## Why the top-level `_meta` specifically
|
|
134
|
-
*
|
|
135
|
-
* The adapter advertises it at `InitializeResponse._meta.steering.supported`, a **sibling of
|
|
136
|
-
* `agentCapabilities`** (`acp-agent.js@0.76.0:944-950`), not a key inside it. `_meta` is typed
|
|
137
|
-
* `{[key: string]: unknown} | null` (`types.gen.d.ts:1498-1500`), so every hop down has to be
|
|
138
|
-
* narrowed rather than indexed through a cast.
|
|
139
|
-
*
|
|
140
|
-
* Strictly `=== true`. An agent that sends `steering: {}` (the shape ACP uses for its *own*
|
|
141
|
-
* capability markers) has not said `supported`, and this extension does not define that spelling —
|
|
142
|
-
* reading it as a yes would be this client inventing a wire contract.
|
|
143
|
-
*/
|
|
144
|
-
export function steeringAdvertised(meta) {
|
|
145
|
-
if (typeof meta !== 'object' || meta === null)
|
|
146
|
-
return false;
|
|
147
|
-
const steering = meta.steering;
|
|
148
|
-
if (typeof steering !== 'object' || steering === null)
|
|
149
|
-
return false;
|
|
150
|
-
return steering.supported === true;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* The delivery timings the `_session/steering` extension defines — when the agent hands an injected
|
|
154
|
-
* message to the model. `next` is the only lossless one: `now` aborts the running generation and
|
|
155
|
-
* discards whatever tool calls were still outstanding.
|
|
156
|
-
*/
|
|
157
|
-
export const STEER_TIMINGS = ['now', 'next', 'later'];
|
|
158
|
-
/**
|
|
159
|
-
* Which delivery timings an `InitializeResponse._meta` says the agent accepts —
|
|
160
|
-
* `_meta.steering.timings`, the sibling of the `supported` flag {@link steeringAdvertised} reads,
|
|
161
|
-
* and read from the **top-level** `_meta` for exactly the same reason.
|
|
162
|
-
*
|
|
163
|
-
* ## Empty is the answer, not the absence of one
|
|
164
|
-
*
|
|
165
|
-
* An agent that declares no timings has told us we may not name one. That is not "assume the
|
|
166
|
-
* default" — the extension ignores an unrecognised `priority` **silently** (the measurement behind
|
|
167
|
-
* ruling N4: sending `next` to today's adapters lands as `now`, which throws away the user's
|
|
168
|
-
* in-flight work). So an empty result must reach the caller as "do not specify a timing at all",
|
|
169
|
-
* and today — before the upstream PR that makes adapters broadcast this — every agent in the wild
|
|
170
|
-
* produces exactly that. Steady state, not a bug.
|
|
171
|
-
*
|
|
172
|
-
* ## Unknown values are dropped, never rejected
|
|
173
|
-
*
|
|
174
|
-
* A timing this build has never heard of is skipped and the known ones beside it survive. This
|
|
175
|
-
* client is deployed on user machines and can be older than both the agent it launches and the
|
|
176
|
-
* server it reports to; throwing (or discarding the whole array) on a value from a future version
|
|
177
|
-
* of the extension would take steering away from an agent that does support it. Non-strings are
|
|
178
|
-
* dropped the same way: `_meta` is typed `{[key: string]: unknown}`, so nothing here is guaranteed.
|
|
179
|
-
*
|
|
180
|
-
* Duplicates collapse — the wire shape is a JSON array but the fact is a set.
|
|
181
|
-
*/
|
|
182
|
-
export function steeringTimings(meta) {
|
|
183
|
-
if (typeof meta !== 'object' || meta === null)
|
|
184
|
-
return [];
|
|
185
|
-
const steering = meta.steering;
|
|
186
|
-
if (typeof steering !== 'object' || steering === null)
|
|
187
|
-
return [];
|
|
188
|
-
const declared = steering.timings;
|
|
189
|
-
if (!Array.isArray(declared))
|
|
190
|
-
return [];
|
|
191
|
-
const known = [];
|
|
192
|
-
for (const entry of declared) {
|
|
193
|
-
if (typeof entry !== 'string')
|
|
194
|
-
continue;
|
|
195
|
-
if (!STEER_TIMINGS.includes(entry))
|
|
196
|
-
continue;
|
|
197
|
-
if (known.includes(entry))
|
|
198
|
-
continue;
|
|
199
|
-
known.push(entry);
|
|
200
|
-
}
|
|
201
|
-
return known;
|
|
202
|
-
}
|
|
203
59
|
/**
|
|
204
60
|
* The JSON-RPC error code an agent answered with, or `undefined` when the failure was not one an
|
|
205
61
|
* agent answered at all.
|
|
@@ -230,10 +86,7 @@ export function steeringTimings(meta) {
|
|
|
230
86
|
export function jsonRpcErrorCode(error) {
|
|
231
87
|
if (!(error instanceof RequestError))
|
|
232
88
|
return undefined;
|
|
233
|
-
|
|
234
|
-
if (!Number.isInteger(code))
|
|
235
|
-
return undefined;
|
|
236
|
-
return code >= -2_147_483_648 && code <= 2_147_483_647 ? code : undefined;
|
|
89
|
+
return jsonRpcErrorCodeOf(error.code);
|
|
237
90
|
}
|
|
238
91
|
function translateSpawnError(command, error, stderr) {
|
|
239
92
|
if (error.code === 'ENOENT') {
|
|
@@ -274,12 +127,19 @@ const STDERR_FLUSH_GRACE_MS = 250;
|
|
|
274
127
|
*/
|
|
275
128
|
function captureChildStderr(stream, present) {
|
|
276
129
|
if (!stream)
|
|
277
|
-
return { text: () => '', flushed: () => Promise.resolve(),
|
|
130
|
+
return { text: () => '', flushed: () => Promise.resolve(), redactValue: () => { } };
|
|
278
131
|
let tail = '';
|
|
279
132
|
let lineBuffer = '';
|
|
280
133
|
let protectedLine = '';
|
|
281
134
|
let droppingLongLine = false;
|
|
282
|
-
|
|
135
|
+
/** {@link ChildStderr.redactValue}'s forms. */
|
|
136
|
+
const quiet = new Set();
|
|
137
|
+
const redactAll = (text) => {
|
|
138
|
+
let safe = text;
|
|
139
|
+
for (const secret of quiet)
|
|
140
|
+
safe = safe.replaceAll(secret, '[REDACTED]');
|
|
141
|
+
return safe;
|
|
142
|
+
};
|
|
283
143
|
const forward = (value) => {
|
|
284
144
|
tail = (tail + value).slice(-STDERR_TAIL_LIMIT);
|
|
285
145
|
process.stderr.write(value);
|
|
@@ -292,10 +152,7 @@ function captureChildStderr(stream, present) {
|
|
|
292
152
|
droppingLongLine = false;
|
|
293
153
|
}
|
|
294
154
|
else {
|
|
295
|
-
|
|
296
|
-
for (const secret of secrets)
|
|
297
|
-
safe = safe.replaceAll(secret, '[REDACTED]');
|
|
298
|
-
forward(safe);
|
|
155
|
+
forward(redactAll(protectedLine.slice(0, newline + 1)));
|
|
299
156
|
}
|
|
300
157
|
protectedLine = protectedLine.slice(newline + 1);
|
|
301
158
|
}
|
|
@@ -348,8 +205,10 @@ function captureChildStderr(stream, present) {
|
|
|
348
205
|
};
|
|
349
206
|
stream.on('data', (chunk) => {
|
|
350
207
|
const text = chunk.toString();
|
|
351
|
-
if (
|
|
208
|
+
if (quiet.size > 0) {
|
|
352
209
|
forwardProtected(text);
|
|
210
|
+
scanForAuthUrl(text);
|
|
211
|
+
}
|
|
353
212
|
else {
|
|
354
213
|
tail = (tail + text).slice(-STDERR_TAIL_LIMIT);
|
|
355
214
|
process.stderr.write(chunk);
|
|
@@ -362,25 +221,19 @@ function captureChildStderr(stream, present) {
|
|
|
362
221
|
// A final unterminated line (no trailing `\n` before the process exits) is still a real line —
|
|
363
222
|
// scan whatever is left in the buffer once the stream itself says there is no more coming.
|
|
364
223
|
void ended.then(() => {
|
|
365
|
-
if (protectedLine && !droppingLongLine)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
safe = safe.replaceAll(secret, '[REDACTED]');
|
|
369
|
-
forward(safe);
|
|
370
|
-
}
|
|
371
|
-
if (secrets.size === 0)
|
|
372
|
-
scanForAuthUrl('\n');
|
|
224
|
+
if (protectedLine && !droppingLongLine)
|
|
225
|
+
forward(redactAll(protectedLine));
|
|
226
|
+
scanForAuthUrl('\n');
|
|
373
227
|
});
|
|
374
228
|
return {
|
|
375
229
|
text: () => tail,
|
|
376
230
|
flushed: (ms) => settledWithin(ended, ms).then(() => undefined),
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
secrets.add(secret.slice('Bearer '.length));
|
|
231
|
+
redactValue: (value) => {
|
|
232
|
+
// Longest first, so a form containing another is removed whole (Set order is insertion order).
|
|
233
|
+
const all = [...quiet, ...secretForms(value)].sort((a, b) => b.length - a.length);
|
|
234
|
+
quiet.clear();
|
|
235
|
+
for (const form of all)
|
|
236
|
+
quiet.add(form);
|
|
384
237
|
},
|
|
385
238
|
};
|
|
386
239
|
}
|
|
@@ -404,7 +257,7 @@ function rejectionWithin(failure, ms) {
|
|
|
404
257
|
});
|
|
405
258
|
});
|
|
406
259
|
}
|
|
407
|
-
/** 报错正文里只放最后几行;完整尾巴留在 `.stderr`
|
|
260
|
+
/** 报错正文里只放最后几行;完整尾巴留在 `.stderr` 上给程序读。 */
|
|
408
261
|
function lastStderrLines(text, count) {
|
|
409
262
|
return text.split('\n').map((line) => line.trim()).filter(Boolean).slice(-count).join(' | ');
|
|
410
263
|
}
|
|
@@ -505,54 +358,33 @@ export async function closeAgentTransport(child, connection, graceMs = CHILD_EXI
|
|
|
505
358
|
connection.close();
|
|
506
359
|
await connection.closed.catch(() => { });
|
|
507
360
|
}
|
|
361
|
+
const DEFAULT_INITIALIZE_TIMEOUT_MS = 30_000;
|
|
508
362
|
/**
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
* `initResponse` is another process's JSON that arrived over a pipe; `AuthMethod` is what this
|
|
512
|
-
* client *asked* for, not what it was handed. Both variants of that union make `id` and `name`
|
|
513
|
-
* required (`@agentclientprotocol/sdk` `types.gen.d.ts:2241-2246` for `AuthMethodTerminal`,
|
|
514
|
-
* `:2281-2286` for `AuthMethodAgent`), so an agent that omits them is off-spec -- but nothing
|
|
515
|
-
* between its stdout and here checks, and the snapshot then carries `undefined` in two positions
|
|
516
|
-
* its own type declares non-optional. That lie does not stay local: `JSON.stringify` erases an
|
|
517
|
-
* `undefined` value entirely, so the object passes every `typeof x.id` check in this process and
|
|
518
|
-
* still reaches a peer with the keys missing.
|
|
519
|
-
*
|
|
520
|
-
* `agoragentic-acp` advertises exactly `{type: 'terminal', description: '…'}`. Observing it once
|
|
521
|
-
* put a malformed `AuthMethodInfo` in the roster, which rides in every subsequent
|
|
522
|
-
* `management_hello`, which the server then cannot decode -- and a hello the server cannot decode
|
|
523
|
-
* closes the socket with nothing said, so the workstation retried into a permanent OFFLINE.
|
|
524
|
-
*
|
|
525
|
-
* Dropping rather than repairing: `id` is the value `authenticate` is called with, so inventing
|
|
526
|
-
* one would trade a missing sign-in method for a broken one.
|
|
363
|
+
* 登录 URL 扩展通知的方法名与形状(见 {@link connectAgent} 内的注册点注释)。解析器**不抛**:
|
|
364
|
+
* 形状不对返回 `undefined`,处理器对 `undefined` 不做事——坏参数只少一张卡,不该伤到连接。
|
|
527
365
|
*/
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
return {
|
|
536
|
-
id: method.id,
|
|
537
|
-
name: method.name,
|
|
538
|
-
...(terminal ? { type: 'terminal' } : {}),
|
|
539
|
-
...(method.description != null ? { description: method.description } : {}),
|
|
540
|
-
// `args` only exists on a terminal method — top-level, or under `_meta["terminal-auth"]` when
|
|
541
|
-
// that is the only place the agent put it. Never the `_meta` command: see `terminalLogins`.
|
|
542
|
-
...(args ? { args } : {}),
|
|
543
|
-
};
|
|
366
|
+
const AUTH_URL_NOTIFICATION_METHOD = '_codebuddy.ai/authUrl';
|
|
367
|
+
function authUrlNotificationParams(raw) {
|
|
368
|
+
if (typeof raw !== 'object' || raw === null)
|
|
369
|
+
return undefined;
|
|
370
|
+
const authUrl = raw.authUrl;
|
|
371
|
+
if (typeof authUrl !== 'string' || !/^https?:\/\//.test(authUrl))
|
|
372
|
+
return undefined;
|
|
373
|
+
return { authUrl };
|
|
544
374
|
}
|
|
545
|
-
const DEFAULT_INITIALIZE_TIMEOUT_MS = 30_000;
|
|
546
375
|
/**
|
|
547
376
|
* Spawns an ACP agent subprocess, connects to it over ndjson stdio, and negotiates capabilities
|
|
548
377
|
* via `initialize`. `env` and `agentEnv` are overlay layers on top of the current process
|
|
549
378
|
* environment, not full environments (existing contract carried over from the deleted bridge
|
|
550
379
|
* transport): if `PATH` is present in either, it has already been resolved by the caller and must
|
|
551
|
-
* not be prefixed with anything else here
|
|
380
|
+
* not be prefixed with anything else here — except the spec's own `pathPrefix` (a build's
|
|
381
|
+
* `node_modules/.bin`), which the adapter needs whatever `PATH` it is given.
|
|
552
382
|
*/
|
|
553
383
|
export async function connectAgent(spec, env, agentEnv, options = {}) {
|
|
554
|
-
|
|
555
|
-
|
|
384
|
+
// No Node gate here any more: a build's `engines.node` is checked against the Node that runs it when
|
|
385
|
+
// the spec is made (`launchForBuild`, `builds/nodeGate.ts`); a config `command` override is the user's own.
|
|
386
|
+
// A build's own PATH prefix (`node_modules/.bin`) must survive a server- or agent-supplied PATH.
|
|
387
|
+
const processEnv = withPathPrefix({ ...process.env, ...spec.env, ...env, ...agentEnv }, spec.pathPrefix);
|
|
556
388
|
const child = spawn(spec.command, spec.args, {
|
|
557
389
|
// stderr 是 `pipe` 而不是 `inherit`,而且**依然不接进协议流** —— 它被 `captureChildStderr`
|
|
558
390
|
// 单独读走:原样转发到 daemon.log(`inherit` 以前免费提供的那件事),同时留一份尾巴给启动期的
|
|
@@ -561,131 +393,32 @@ export async function connectAgent(spec, env, agentEnv, options = {}) {
|
|
|
561
393
|
env: processEnv,
|
|
562
394
|
});
|
|
563
395
|
const stderrTail = captureChildStderr(child.stderr, options.present);
|
|
564
|
-
for (const
|
|
565
|
-
stderrTail.
|
|
566
|
-
if (options.onProcessExit)
|
|
567
|
-
child.once('exit', options.onProcessExit);
|
|
396
|
+
for (const value of overlayEnvSecrets({ env, agentEnv }))
|
|
397
|
+
stderrTail.redactValue(value);
|
|
568
398
|
const lifecycle = watchChildLifecycle(child, spec.command, stderrTail);
|
|
569
399
|
// Argument order reads backwards from what the names suggest: argument 1 is the child's stdin
|
|
570
400
|
// (we write to it), argument 2 is the child's stdout (we read from it). Swapping them still
|
|
571
401
|
// compiles and still "connects" — the initialize request just never arrives, so the failure mode
|
|
572
402
|
// is a hang, not an error.
|
|
573
|
-
|
|
574
|
-
// passed in, because the interceptor has to be in place before `initialize` — the agent may send
|
|
575
|
-
// its first update before anyone has had a chance to register anything.
|
|
576
|
-
let updateCapture = null;
|
|
577
|
-
let ownSessionId = null;
|
|
578
|
-
let diagnostics = {
|
|
579
|
-
generation: randomUUID(), revision: 0, available: true,
|
|
580
|
-
launch: { source: options.launchSource ?? 'direct',
|
|
581
|
-
executable: (findOnPath(spec.command, probeEnvironment(processEnv)) ?? spec.command).slice(0, 4096),
|
|
582
|
-
platform: process.platform, architecture: process.arch, transport: 'stdio' },
|
|
583
|
-
};
|
|
584
|
-
const diagnosticListeners = new Set();
|
|
585
|
-
const providerTaskListeners = new Set();
|
|
586
|
-
child.once('exit', () => {
|
|
587
|
-
diagnostics = { ...diagnostics, available: false, revision: diagnostics.revision + 1,
|
|
588
|
-
profile: diagnostics.profile ? { ...diagnostics.profile, observerHealthy: false, rootConfirmed: false } : undefined };
|
|
589
|
-
for (const listener of diagnosticListeners)
|
|
590
|
-
listener(diagnostics);
|
|
591
|
-
});
|
|
592
|
-
let adapterRevision = 0;
|
|
593
|
-
const pendingProfiles = new Map();
|
|
594
|
-
const applyProfile = (sessionId, revision, profile) => {
|
|
595
|
-
const previous = diagnostics.profile;
|
|
596
|
-
if (!previous || !diagnostics.available || sessionId !== ownSessionId || revision <= adapterRevision ||
|
|
597
|
-
!sameProfileIdentity(previous, profile) || profile.rootSessionId !== ownSessionId ||
|
|
598
|
-
(previous.runtimeGeneration !== undefined && profile.runtimeGeneration !== previous.runtimeGeneration) ||
|
|
599
|
-
(profile.rootConfirmed === true && !profile.runtimeGeneration))
|
|
600
|
-
return;
|
|
601
|
-
adapterRevision = revision;
|
|
602
|
-
diagnostics = { ...diagnostics, revision: diagnostics.revision + 1, profile };
|
|
603
|
-
for (const listener of diagnosticListeners)
|
|
604
|
-
listener(diagnostics);
|
|
605
|
-
};
|
|
606
|
-
let draftSink = null;
|
|
607
|
-
// Same "filled in later" shape as `draftSink`, for `bindActivity` — see that method's own doc
|
|
608
|
-
// comment on `AcpConnection`.
|
|
609
|
-
let activitySink = null;
|
|
610
|
-
// Sessions this connection handles by some route other than an `AcpSession` — today only
|
|
611
|
-
// adoption's replay. Kept beside `ownSessionId` rather than folded into it because they mean
|
|
612
|
-
// different things: one is "the session we drive", these are "sessions someone else on this
|
|
613
|
-
// connection is already reading".
|
|
614
|
-
const knownSessionIds = new Set();
|
|
615
|
-
// p3-fix7 N9: updates diverted before any session is bound. An agent may push its first `session/update` (Claude's
|
|
616
|
-
// `available_commands_update`) before this client has processed the `session/new` response that names the session,
|
|
617
|
-
// so it reads as a stranger's. Dropping it lost the command table of every first probe (run 4 and run 5 daemon
|
|
618
|
-
// logs); kept here, bounded, and replayed to the sink once the session binds.
|
|
619
|
-
const preBind = [];
|
|
620
|
-
const stream = ndJsonStream(Writable.toWeb(child.stdin),
|
|
621
|
-
// The child's stdout, minus the notifications the pinned SDK cannot deliver. Everything else
|
|
622
|
-
// reaches it byte-for-byte; see `draftInterceptor.ts` for the measurements behind this.
|
|
623
|
-
interceptDraftUpdates(Readable.toWeb(child.stdout), {
|
|
624
|
-
// Unbound means unknown, and unknown must not be claimed as ours: treating a session we have
|
|
625
|
-
// not been told about as our own would send its frames to the SDK, which drops them silently.
|
|
626
|
-
// Diverting is the recoverable direction — the sink can decide, and it can log.
|
|
627
|
-
isOwnSession: (sessionId) => (ownSessionId !== null && sessionId === ownSessionId) || knownSessionIds.has(sessionId),
|
|
628
|
-
captureUpdate: (notification) => updateCapture?.(notification) ?? false,
|
|
629
|
-
onDiverted: (update) => {
|
|
630
|
-
if (draftSink) {
|
|
631
|
-
draftSink(update);
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
// No sink yet: keep it until the session binds (bounded), then replay what is ours.
|
|
635
|
-
if (preBind.length >= PRE_BIND_MAX_UPDATES) {
|
|
636
|
-
const dropped = preBind.shift();
|
|
637
|
-
warnDroppedPreBind(dropped.update, 'too many updates arrived before the session was bound');
|
|
638
|
-
}
|
|
639
|
-
preBind.push({ update, at: Date.now() });
|
|
640
|
-
},
|
|
641
|
-
}));
|
|
403
|
+
const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
642
404
|
const app = client({ name: 'axom' });
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (!previous && pendingProfiles.size >= 8)
|
|
661
|
-
return;
|
|
662
|
-
pendingProfiles.set(params.sessionId, { revision: params.revision, profile });
|
|
663
|
-
}
|
|
664
|
-
});
|
|
665
|
-
app.onNotification('_qoder/task_event', (params) => params, (ctx) => {
|
|
666
|
-
const event = parseTaskObservation(ctx.params);
|
|
667
|
-
if (!event || !taskEventMatchesHost(event, {
|
|
668
|
-
selected: options.providerTaskAdapterSelected === true, available: diagnostics.available,
|
|
669
|
-
confirmed: diagnostics.profile?.rootConfirmed === true, rootSessionId: ownSessionId,
|
|
670
|
-
runtimeGeneration: diagnostics.profile?.runtimeGeneration,
|
|
671
|
-
}))
|
|
672
|
-
return;
|
|
673
|
-
for (const listener of providerTaskListeners)
|
|
674
|
-
listener(event);
|
|
675
|
-
});
|
|
676
|
-
// claude-agent-acp's raw-SDK-message channel (design §5.1/§2.1): registered unconditionally on
|
|
677
|
-
// every connection, before `options.configure?.(app)` and before `initialize` — `_claude/sdkMessage`
|
|
678
|
-
// is not a built-in ACP method, so `ClientApp.onNotification` needs the custom-method overload (a
|
|
679
|
-
// parser plus a handler) rather than the typed-literal one `registerPermissionHandlers` uses. The
|
|
680
|
-
// method name is vendor-specific, so this handler simply never fires on a connection to any other
|
|
681
|
-
// agent — there is no need to gate the registration on `agentInfo`, which in any case is not yet
|
|
682
|
-
// known this early (`initialize` has not happened).
|
|
683
|
-
app.onNotification('_claude/sdkMessage', (params) => (params ?? {}), (ctx) => {
|
|
684
|
-
const raw = claudeActivityRaw(ctx.params);
|
|
685
|
-
if (raw)
|
|
686
|
-
activitySink?.(raw);
|
|
687
|
-
});
|
|
688
|
-
options.configure?.(app);
|
|
405
|
+
// This adapter process's generation: the scope `permission.ts`'s `permissionRequestId` derives card ids in (a sign-in's
|
|
406
|
+
// connection registers the permission handlers).
|
|
407
|
+
const generation = randomUUID();
|
|
408
|
+
options.configure?.(app, { generation });
|
|
409
|
+
// 登录 URL 的第三条通道:扩展通知。实测(codebuddy-code 2.171.0,2026-10-21):CodeBuddy 适配器在
|
|
410
|
+
// 应答 `authenticate` 期间推送 `_codebuddy.ai/authUrl`(`{authUrl, provider}`)——既不走
|
|
411
|
+
// `elicitation/create`,也不打 stderr(它把链接写进自己的日志文件、并打开工作站本机的浏览器,远程
|
|
412
|
+
// 读者两头都看不见)。方法名是厂商命名空间,按实测常量注册;形状校验独立于方法名做(authUrl 必须是
|
|
413
|
+
// http(s) URL),坏形状静默丢弃而不是让解析器抛错——一个登录链接不值得一条连接。与 stderr 通道同
|
|
414
|
+
// 一个 `present` 下游,URL 去重与落定收回由 `createLoginUrlSink`(acpAgents.authenticate)统一负责,
|
|
415
|
+
// 这里只负责递到同一只手里。id 前缀 `ext-auth-url-` 标出来源,与 `stderr-auth-url-` 同一条规矩。
|
|
416
|
+
if (options.present) {
|
|
417
|
+
app.onNotification(AUTH_URL_NOTIFICATION_METHOD, authUrlNotificationParams, (ctx) => {
|
|
418
|
+
if (ctx.params)
|
|
419
|
+
options.present?.(`ext-auth-url-${randomUUID()}`, ctx.params.authUrl);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
689
422
|
const connection = app.connect(stream);
|
|
690
423
|
const timeoutMs = options.initializeTimeoutMs ?? DEFAULT_INITIALIZE_TIMEOUT_MS;
|
|
691
424
|
let timeoutHandle;
|
|
@@ -702,60 +435,7 @@ export async function connectAgent(spec, env, agentEnv, options = {}) {
|
|
|
702
435
|
let initResponse;
|
|
703
436
|
try {
|
|
704
437
|
initResponse = await Promise.race([
|
|
705
|
-
connection.agent.request('initialize',
|
|
706
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
707
|
-
// Three things are declared here, and they are not interchangeable — see
|
|
708
|
-
// `draftSubagents.ts` for the tier each one sits in.
|
|
709
|
-
//
|
|
710
|
-
// 1. `subagents: {}` is the **canonical** draft field from ACP RFD #1992. Both official
|
|
711
|
-
// adapters gate native subagent sessions on it, and both answer with
|
|
712
|
-
// `agentCapabilities.sessionCapabilities.subagents` (measured against each).
|
|
713
|
-
//
|
|
714
|
-
// 2. The AIR list is sent as well, for two separate reasons. It is the *only* channel for
|
|
715
|
-
// `asyncTasks` — nothing canonical exists for background work — and it is the fallback
|
|
716
|
-
// for `nativeSubagentSessions`, because a released SDK sitting between us and an agent
|
|
717
|
-
// can strip a draft field it has no type for (upstream says so in #1017's compatibility
|
|
718
|
-
// note). Declaring both is not belt-and-braces: they cover different failure modes.
|
|
719
|
-
//
|
|
720
|
-
// 3. `subagent-transcript: true` stays. It is claude-agent-acp's older vendor key
|
|
721
|
-
// (`dist/acp-agent.js:155-158`), and it still governs the **fallback** path taken when
|
|
722
|
-
// native subagent sessions are not negotiated — an older adapter, or a stripped
|
|
723
|
-
// capability. Without it that path strips every subagent's text and thinking
|
|
724
|
-
// (`stripSubagentTextAndThinking`, `:3926`), leaving nothing but tool-call wreckage.
|
|
725
|
-
// Adapters that have never heard of any of these keys are required by the spec to
|
|
726
|
-
// ignore them.
|
|
727
|
-
clientCapabilities: asSdkClientCapabilities({
|
|
728
|
-
[DRAFT_SUBAGENTS_CAPABILITY]: {},
|
|
729
|
-
// `ClientSessionCapabilities.configOptions` (`types.gen.d.ts:4306`), part of the schema
|
|
730
|
-
// proper — unlike `subagents` above, this survives the pinned SDK's `zInitializeRequest`
|
|
731
|
-
// stripping because it's a known field, not a draft one. `{}` for `boolean` tells the
|
|
732
|
-
// agent it may send `type: "boolean"` entries in `SessionConfigOption` and that this
|
|
733
|
-
// client will send `session/set_config_option` with a boolean `value`
|
|
734
|
-
// (`SessionConfigOptionsCapabilities`, `types.gen.d.ts:4333-4353`); without it an agent
|
|
735
|
-
// with a boolean toggle downgrades it to a two-value select.
|
|
736
|
-
session: { configOptions: { boolean: {} } },
|
|
737
|
-
// `ClientCapabilities.elicitation` (`types.gen.d.ts:4479`) is a known field of the schema
|
|
738
|
-
// proper, not a draft one, so it survives `zInitializeRequest` stripping as-is. `{}` for
|
|
739
|
-
// `url` (`ElicitationUrlCapabilities`, `:4616`) means the client can put a URL in front of
|
|
740
|
-
// the user; this is the precondition for the whole device-code login shape to exist at all
|
|
741
|
-
// — Codex's own `getCodexAuthMethods` gates its `chat-gpt-device-code` auth method on
|
|
742
|
-
// `clientCapabilities?.elicitation?.url != null`. Without declaring it, Codex never offers
|
|
743
|
-
// that method and `elicitation/create` with `mode: 'url'` never arrives to answer (see
|
|
744
|
-
// `permission.ts`'s `handleCreateElicitation`).
|
|
745
|
-
//
|
|
746
|
-
// `form` (`ElicitationFormCapabilities`, `types.gen.d.ts:4455`) is what keeps Claude's
|
|
747
|
-
// AskUserQuestion alive: without it the adapter puts the tool in `disallowedTools`
|
|
748
|
-
// (`claude-agent-acp/dist/acp-agent.js:5959`), so the model never has it at all — the
|
|
749
|
-
// symptom is prose questions, not an unrendered card. This is the trigger for the whole
|
|
750
|
-
// elicitation-standardization plan: everything through Task 17 built the machinery to
|
|
751
|
-
// answer `elicitation/create` once it arrives; this line is what makes it arrive at all.
|
|
752
|
-
elicitation: { url: {}, form: {} },
|
|
753
|
-
_meta: {
|
|
754
|
-
'subagent-transcript': true,
|
|
755
|
-
...airCapabilityMeta(AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY, AIR_ASYNC_TASKS_CAPABILITY),
|
|
756
|
-
},
|
|
757
|
-
}),
|
|
758
|
-
}),
|
|
438
|
+
connection.agent.request('initialize', initializeRequestParams()),
|
|
759
439
|
lifecycle.failure,
|
|
760
440
|
timeout,
|
|
761
441
|
]);
|
|
@@ -776,130 +456,39 @@ export async function connectAgent(spec, env, agentEnv, options = {}) {
|
|
|
776
456
|
}
|
|
777
457
|
// Global Constraint 1 is v1-only; the schema calls for disconnecting when the agent didn't
|
|
778
458
|
// accept the version this client sent.
|
|
779
|
-
|
|
459
|
+
const reading = parseInitializeResult(initResponse);
|
|
460
|
+
if (!reading.protocolSupported) {
|
|
780
461
|
const error = new UnsupportedProtocolVersionError(`Agent '${spec.command}' negotiated protocol version ${initResponse.protocolVersion}, ` +
|
|
781
462
|
`but this client only supports version ${PROTOCOL_VERSION}.`);
|
|
782
463
|
connection.close(error);
|
|
783
464
|
child.kill();
|
|
784
465
|
throw error;
|
|
785
466
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const capabilities =
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
// correctly by accident but reads `null`/`undefined` correctly too, which hides the real bug:
|
|
792
|
-
// this must stay an explicit `!= null`, never `Boolean(...)` or a bare truthy test.
|
|
793
|
-
list: sessionCapabilities?.list != null,
|
|
794
|
-
delete: sessionCapabilities?.delete != null,
|
|
795
|
-
resume: sessionCapabilities?.resume != null,
|
|
796
|
-
loadSession: initResponse.agentCapabilities?.loadSession === true,
|
|
797
|
-
logout: initResponse.agentCapabilities?.auth?.logout != null,
|
|
798
|
-
/**
|
|
799
|
-
* ACP `PromptCapabilities.image` (`types.gen.d.ts:1601`): "Agent supports
|
|
800
|
-
* [`ContentBlock::Image`]". Every field of that type is optional, so an agent that says
|
|
801
|
-
* nothing supports text only -- `=== true` rather than `!== false`.
|
|
802
|
-
*
|
|
803
|
-
* Verified against codex-acp 1.7.0, which declares `{embeddedContext: true, image: true}`.
|
|
804
|
-
* `audio` is deliberately not read: no agent we target declares it, and an unread capability
|
|
805
|
-
* is a field nobody can act on.
|
|
806
|
-
*/
|
|
807
|
-
promptImage: initResponse.agentCapabilities?.promptCapabilities?.image === true,
|
|
808
|
-
mcpHttp: initResponse.agentCapabilities?.mcpCapabilities?.http === true,
|
|
809
|
-
};
|
|
810
|
-
// Honour the advertised capability, not what the agent happens to route: some adapters (e.g.
|
|
811
|
-
// codex-acp routing `session/fork`) implement more than they declare in `agentCapabilities`.
|
|
812
|
-
// Reading `capabilities` above off `initResponse` — never probing the agent for what it accepts
|
|
813
|
-
// — is what keeps that distinction intact.
|
|
814
|
-
const advertisedAuthMethods = initResponse.authMethods ?? [];
|
|
815
|
-
const authMethods = advertisedAuthMethods
|
|
816
|
-
.map(toAuthMethodSnapshot)
|
|
817
|
-
.filter((method) => method !== null);
|
|
818
|
-
const terminalLogins = new Map();
|
|
819
|
-
for (const method of advertisedAuthMethods) {
|
|
820
|
-
const spec = terminalLoginSpecOf(method);
|
|
821
|
-
if (spec && typeof method.id === 'string')
|
|
822
|
-
terminalLogins.set(method.id, spec);
|
|
823
|
-
}
|
|
824
|
-
if (authMethods.length !== advertisedAuthMethods.length) {
|
|
467
|
+
// Capabilities, auth methods and steering are read in `initializeResponse.ts`, the one reading the
|
|
468
|
+
// deterministic processor shares — see that file for the rules.
|
|
469
|
+
const { capabilities, authMethods, droppedAuthMethods } = reading;
|
|
470
|
+
const terminalLogins = new Map(reading.terminalLogins);
|
|
471
|
+
if (droppedAuthMethods > 0) {
|
|
825
472
|
// Named and counted rather than dropped in silence: the user loses a sign-in method they can
|
|
826
473
|
// see advertised in the agent's own docs, and the only honest answer to "why" is which agent
|
|
827
474
|
// sent what. `agentInfo.name` first because most agents launch through `npx`/`uvx` and
|
|
828
475
|
// `spec.command` would then name the launcher, which identifies none of the ~40 registry
|
|
829
476
|
// entries. See `toAuthMethodSnapshot` for what accepting these costs instead.
|
|
830
477
|
console.warn(`[acp] agent '${initResponse.agentInfo?.name ?? spec.command}' advertised ` +
|
|
831
|
-
`${
|
|
478
|
+
`${droppedAuthMethods} auth method(s) without the required ` +
|
|
832
479
|
'id/name; ignoring them');
|
|
833
480
|
}
|
|
834
481
|
return {
|
|
835
|
-
get runtimeDiagnostics() { return diagnostics; },
|
|
836
|
-
providerTaskAdapterSelected: options.providerTaskAdapterSelected === true,
|
|
837
|
-
publishCallbacks(snapshot) {
|
|
838
|
-
if (snapshot.rootSessionId !== ownSessionId)
|
|
839
|
-
return;
|
|
840
|
-
diagnostics = { ...diagnostics, revision: diagnostics.revision + 1, callbacks: snapshot };
|
|
841
|
-
for (const listener of diagnosticListeners)
|
|
842
|
-
listener(diagnostics);
|
|
843
|
-
},
|
|
844
|
-
onRuntimeDiagnostics(cb) { diagnosticListeners.add(cb); },
|
|
845
|
-
onProviderTaskEvent(cb) { providerTaskListeners.add(cb); },
|
|
846
482
|
capabilities,
|
|
847
483
|
authMethods,
|
|
848
484
|
terminalLogins,
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
485
|
+
supportsAccountLimits: reading.supportsAccountLimits,
|
|
486
|
+
supportsSteering: reading.supportsSteering,
|
|
487
|
+
steeringTimings: reading.steeringTimings,
|
|
488
|
+
agentInfo: reading.agentInfo,
|
|
852
489
|
connection,
|
|
853
|
-
redactStderrSecret: (secret) => stderrTail.redactSecret(secret),
|
|
854
|
-
agentProcessAlive: () => child.exitCode === null && child.signalCode === null,
|
|
855
|
-
captureSessionUpdates(sink) {
|
|
856
|
-
if (updateCapture)
|
|
857
|
-
throw new Error('A session replay is already active on this connection');
|
|
858
|
-
updateCapture = sink;
|
|
859
|
-
return () => { if (updateCapture === sink)
|
|
860
|
-
updateCapture = null; };
|
|
861
|
-
},
|
|
862
|
-
bindDraftUpdates(sessionId, sink) {
|
|
863
|
-
ownSessionId = sessionId;
|
|
864
|
-
draftSink = sink;
|
|
865
|
-
const current = diagnostics.profile;
|
|
866
|
-
if (current?.rootSessionId && current.rootSessionId !== sessionId) {
|
|
867
|
-
diagnostics = { ...diagnostics, profile: { ...current, rootSessionId: undefined,
|
|
868
|
-
runtimeGeneration: undefined, rootConfirmed: false, initialized: false, observerHealthy: false } };
|
|
869
|
-
}
|
|
870
|
-
const pending = pendingProfiles.get(sessionId);
|
|
871
|
-
pendingProfiles.clear();
|
|
872
|
-
if (pending)
|
|
873
|
-
applyProfile(sessionId, pending.revision, pending.profile);
|
|
874
|
-
// p3-fix7 N9: replay what arrived before this moment. Our own session's updates go to the sink as `pre-bind`
|
|
875
|
-
// (they are ours, not a child's); anything else, or anything older than the window, is dropped with the
|
|
876
|
-
// warning it always had.
|
|
877
|
-
const now = Date.now();
|
|
878
|
-
for (const { update, at } of preBind.splice(0)) {
|
|
879
|
-
if (update.sessionId === sessionId && now - at <= PRE_BIND_MAX_AGE_MS) {
|
|
880
|
-
sink(update.reason === 'foreign-session' ? { ...update, reason: 'pre-bind' } : update);
|
|
881
|
-
}
|
|
882
|
-
else {
|
|
883
|
-
warnDroppedPreBind(update, 'no session is bound to this connection yet');
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
},
|
|
887
|
-
bindActivity(sink) {
|
|
888
|
-
activitySink = sink;
|
|
889
|
-
},
|
|
890
|
-
markSessionKnown(sessionId) {
|
|
891
|
-
knownSessionIds.add(sessionId);
|
|
892
|
-
return () => knownSessionIds.delete(sessionId);
|
|
893
|
-
},
|
|
894
490
|
// Child first, streams second — see `closeAgentTransport` for why that order is the fix and not
|
|
895
491
|
// an incidental detail.
|
|
896
492
|
close: () => closeAgentTransport(child, connection),
|
|
897
493
|
};
|
|
898
494
|
}
|
|
899
|
-
/** p3-fix7 N9: how many updates, and for how long, a connection keeps before its session is bound. */
|
|
900
|
-
const PRE_BIND_MAX_UPDATES = 32;
|
|
901
|
-
const PRE_BIND_MAX_AGE_MS = 5_000;
|
|
902
|
-
/** Says so once per update rather than dropping in silence; it should stay rare, and if it stops being rare it is worth seeing. */
|
|
903
|
-
function warnDroppedPreBind(update, why) {
|
|
904
|
-
console.warn(`[acp] dropped a ${update.reason} session/update for '${update.sessionId}': ${why}`);
|
|
905
|
-
}
|