@platless/axom-cli 0.5.9 → 0.6.52

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.
Files changed (258) hide show
  1. package/README.md +25 -6
  2. package/dist/axom-patch/cli/README.md +129 -12
  3. package/dist/axom-patch/cli/account-limits/generate.py +15 -12
  4. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
  5. package/dist/axom-patch/cli/claude-account-limits.json +9 -9
  6. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  7. package/dist/axom-patch/cli/codex-account-limits.json +11 -11
  8. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  9. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  10. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  11. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  12. package/dist/axom-patch/cli/nightly-task.md +55 -13
  13. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  14. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  15. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  16. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  17. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  18. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
  19. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
  20. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  21. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  22. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  23. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  24. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  25. package/dist/axom-patch/cli/runtime.json +19 -19
  26. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  27. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  28. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  29. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  30. package/dist/axom-patch/cli/zcode-models.json +9 -10
  31. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
  32. package/dist/cli.js +11 -45
  33. package/dist/commands/auth.js +4 -1
  34. package/dist/commands/knowledge.js +47 -0
  35. package/dist/commands/update.js +126 -3
  36. package/dist/commands/workstation.js +101 -164
  37. package/dist/commands/workstationRegistrations.js +116 -0
  38. package/dist/credentialBroker.js +13 -2
  39. package/dist/credentials.js +16 -0
  40. package/dist/install/claude/commands.js +15 -15
  41. package/dist/keepers/terminal/keeper.mjs +1089 -673
  42. package/dist/keepers/terminal/manifest.json +4 -4
  43. package/dist/mcp.js +1 -1
  44. package/dist/native/darwin-x64/axom-quic +0 -0
  45. package/dist/workstation/acp/acpAgents.js +113 -102
  46. package/dist/workstation/acp/acpClient.js +26 -0
  47. package/dist/workstation/acp/builds/install/npm.js +99 -10
  48. package/dist/workstation/acp/initializeResponse.js +6 -1
  49. package/dist/workstation/acp/managedPatches.js +189 -25
  50. package/dist/workstation/acp/managedRuntime.js +314 -0
  51. package/dist/workstation/acp/outbox.js +12 -5
  52. package/dist/workstation/acp/patchAssets.js +40 -0
  53. package/dist/workstation/acp/permission.js +2 -2
  54. package/dist/workstation/acp/processor/checkpoint.js +26 -34
  55. package/dist/workstation/acp/processor/control.js +7 -80
  56. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  57. package/dist/workstation/acp/processor/runtime.js +5 -81
  58. package/dist/workstation/acp/processor/session.js +54 -82
  59. package/dist/workstation/acp/processor/state.js +7 -13
  60. package/dist/workstation/acp/processor/step.js +3 -20
  61. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  62. package/dist/workstation/acp/processor/transport/local.js +19 -3
  63. package/dist/workstation/acp/processor/updates.js +156 -105
  64. package/dist/workstation/acp/processorSession.js +20 -173
  65. package/dist/workstation/acp/registry/catalog.js +65 -39
  66. package/dist/workstation/acp/registry/launch.js +16 -1
  67. package/dist/workstation/acp/switching.js +0 -3
  68. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  69. package/dist/workstation/acp/test/keeperWorld.js +32 -3
  70. package/dist/workstation/acp/test/raw/sources.js +16 -130
  71. package/dist/workstation/acp/test/realAdapter.js +1 -47
  72. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  73. package/dist/workstation/agentFlow.js +1 -1
  74. package/dist/workstation/agentSessions.js +114 -166
  75. package/dist/workstation/channels/agent.js +9 -1
  76. package/dist/workstation/channels/computer.js +27 -4
  77. package/dist/workstation/channels/management.js +130 -15
  78. package/dist/workstation/channels/url.js +1 -1
  79. package/dist/workstation/config.js +33 -9
  80. package/dist/workstation/connection/encryptedChannel.js +4 -1
  81. package/dist/workstation/control/bindingStore.js +204 -0
  82. package/dist/workstation/control/commands.js +125 -0
  83. package/dist/workstation/control/daemonIdentity.js +143 -0
  84. package/dist/workstation/control/deviceCommands.js +191 -0
  85. package/dist/workstation/control/deviceProtocol.js +68 -0
  86. package/dist/workstation/control/enrollment.js +106 -0
  87. package/dist/workstation/control/inspect.js +71 -0
  88. package/dist/workstation/control/locks.js +110 -0
  89. package/dist/workstation/control/protocol.js +40 -0
  90. package/dist/workstation/control/restartGuard.js +46 -0
  91. package/dist/workstation/control/rootFormat.js +16 -0
  92. package/dist/workstation/control/runtimeClient.js +152 -0
  93. package/dist/workstation/control/runtimeHost.js +235 -0
  94. package/dist/workstation/control/serverIdentity.js +35 -0
  95. package/dist/workstation/control/updateTransition.js +91 -0
  96. package/dist/workstation/daemon.js +266 -264
  97. package/dist/workstation/daemonctl.js +226 -25
  98. package/dist/workstation/desktopControl/audio.js +99 -0
  99. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  100. package/dist/workstation/desktopControl/automationJson.js +74 -0
  101. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  102. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  103. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  104. package/dist/workstation/desktopControl/automationSession.js +81 -0
  105. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  106. package/dist/workstation/desktopControl/capabilities.js +93 -0
  107. package/dist/workstation/desktopControl/captureModes.js +9 -0
  108. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  109. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  110. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  111. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  112. package/dist/workstation/desktopControl/helperProcess.js +602 -0
  113. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  114. package/dist/workstation/desktopControl/lobby.js +131 -0
  115. package/dist/workstation/desktopControl/macProvider.js +156 -0
  116. package/dist/workstation/desktopControl/media.js +331 -0
  117. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  118. package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
  119. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  120. package/dist/workstation/desktopControl/overview.js +58 -0
  121. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  122. package/dist/workstation/desktopControl/protocol.js +311 -0
  123. package/dist/workstation/desktopControl/provider.js +1 -0
  124. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  125. package/dist/workstation/desktopControl/rateController.js +226 -0
  126. package/dist/workstation/desktopControl/supervisor.js +1774 -0
  127. package/dist/workstation/desktopControl/transport.js +27 -0
  128. package/dist/workstation/desktopControl/wallpaper.js +98 -0
  129. package/dist/workstation/exec/policy.js +5 -0
  130. package/dist/workstation/files/client.js +10 -1
  131. package/dist/workstation/files/credentials.js +142 -0
  132. package/dist/workstation/files/host.js +9 -2
  133. package/dist/workstation/files/text.js +42 -8
  134. package/dist/workstation/journal.js +5 -2
  135. package/dist/workstation/keepers/acp/client.js +38 -2
  136. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  137. package/dist/workstation/keepers/kit/bundle.js +21 -17
  138. package/dist/workstation/keepers/kit/client.js +2 -2
  139. package/dist/workstation/keepers/kit/launcher.js +123 -7
  140. package/dist/workstation/keepers/kit/paths.js +2 -1
  141. package/dist/workstation/keepers/legacy.js +16 -2
  142. package/dist/workstation/keepers/policy.js +5 -34
  143. package/dist/workstation/keepers/terminal/client.js +115 -23
  144. package/dist/workstation/keepers/terminal/exec.js +6 -3
  145. package/dist/workstation/keepers/terminal/frames.js +73 -0
  146. package/dist/workstation/keepers/terminal/generations.js +5 -8
  147. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  148. package/dist/workstation/keepers/terminal/input.js +146 -0
  149. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  150. package/dist/workstation/keepers/terminal/main.js +1 -1
  151. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  152. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  153. package/dist/workstation/keepers/terminal/pty.js +1 -330
  154. package/dist/workstation/keepers/terminal/screen.js +246 -0
  155. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  156. package/dist/workstation/managedInstall.js +57 -0
  157. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  158. package/dist/workstation/protocol/agent.js +2 -15
  159. package/dist/workstation/protocol/management.js +113 -5
  160. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  161. package/dist/workstation/rd/artifact.js +120 -0
  162. package/dist/workstation/rd/control.js +117 -0
  163. package/dist/workstation/rd/display.js +29 -0
  164. package/dist/workstation/rd/host.js +213 -0
  165. package/dist/workstation/rd/migrationReadiness.js +46 -0
  166. package/dist/workstation/rd/provider.js +256 -0
  167. package/dist/workstation/rd/sessions.js +391 -0
  168. package/dist/workstation/rd/state.js +57 -0
  169. package/dist/workstation/rd/update.js +168 -0
  170. package/dist/workstation/rd/upkeep.js +80 -0
  171. package/dist/workstation/register.js +27 -37
  172. package/dist/workstation/registrations/enrollment.js +250 -0
  173. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  174. package/dist/workstation/registrations/migration.js +356 -0
  175. package/dist/workstation/registrations/model.js +34 -0
  176. package/dist/workstation/registrations/removal.js +222 -0
  177. package/dist/workstation/registrations/store.js +737 -0
  178. package/dist/workstation/runtime/desktopBinding.js +20 -0
  179. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  180. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  181. package/dist/workstation/runtime/deviceServices.js +117 -0
  182. package/dist/workstation/runtime/events.js +20 -0
  183. package/dist/workstation/runtime/ownership.js +100 -0
  184. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  185. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  186. package/dist/workstation/runtime/removalActivity.js +184 -0
  187. package/dist/workstation/service/installer.js +197 -14
  188. package/dist/workstation/service/launchd.js +2 -0
  189. package/dist/workstation/service/systemd.js +1 -0
  190. package/dist/workstation/storageMetrics.js +118 -0
  191. package/dist/workstation/systemMetrics.js +47 -92
  192. package/dist/workstation/toolchain.js +94 -0
  193. package/dist/workstation/tools/browser.js +28 -4
  194. package/dist/workstation/tools/client.js +8 -27
  195. package/dist/workstation/tools/info.js +6 -13
  196. package/dist/workstation/tools/terminalRouter.js +17 -172
  197. package/dist/workstation/tools/validation.js +55 -1
  198. package/dist/workstation/update/archiveEvidence.js +206 -0
  199. package/dist/workstation/update/artifact.js +2 -1
  200. package/dist/workstation/update/coordinator.js +34 -13
  201. package/dist/workstation/update/deviceCompatibility.js +119 -0
  202. package/dist/workstation/update/deviceCoordinator.js +369 -0
  203. package/dist/workstation/update/deviceHealth.js +104 -0
  204. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  205. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  206. package/dist/workstation/update/deviceRollback.js +78 -0
  207. package/dist/workstation/update/globalInstall.js +186 -0
  208. package/dist/workstation/update/legacyBridge.js +414 -0
  209. package/dist/workstation/update/legacyOwner.js +39 -0
  210. package/dist/workstation/update/npmInstaller.js +51 -25
  211. package/dist/workstation/update/privateDurability.js +61 -0
  212. package/dist/workstation/update/rdRollback.js +145 -0
  213. package/dist/workstation/update/recoveryPlan.js +124 -0
  214. package/dist/workstation/update/runUpdate.js +10 -3
  215. package/dist/workstation/update/taskRegistry.js +161 -3
  216. package/dist/workstation/update/watchdog.js +29806 -333
  217. package/dist/workstation/update/watchdogProcess.js +72 -0
  218. package/package.json +11 -7
  219. package/skills/axom/SKILL.md +11 -11
  220. package/skills/axom-definition/SKILL.md +3 -3
  221. package/skills/axom-platform/SKILL.md +3 -3
  222. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  223. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  224. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  225. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  226. package/dist/commands/guide.js +0 -38
  227. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  228. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  229. package/dist/workstation/acp/delegationMcp.js +0 -24
  230. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  231. package/dist/workstation/acp/qoderStartup.js +0 -45
  232. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  233. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  234. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  235. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  236. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  237. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  238. package/dist/workstation/qoderAdapter/contract.js +0 -78
  239. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  240. package/dist/workstation/qoderAdapter/history.js +0 -85
  241. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  242. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  243. package/dist/workstation/qoderAdapter/main.js +0 -95
  244. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  245. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  246. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  247. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  248. package/dist/workstation/qoderAdapter/profile.js +0 -93
  249. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  250. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  251. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  252. package/dist/workstation/qoderAdapter/session.js +0 -319
  253. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  254. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  255. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  256. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  257. package/dist/workstation/qoderAdapter/usage.js +0 -37
  258. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "kind": "terminal",
3
- "protocolVersion": 1,
4
- "buildVersion": "c0f319e5906ddf108ac7f65222a35db8423b49702609c2ea74aca69937ad1a05",
3
+ "protocolVersion": 2,
4
+ "buildVersion": "80542c28e2e2ce561c3acc8dab28422a0df482b778665e982272e923261c775c",
5
5
  "daemonProtocol": [
6
- 1,
7
- 1
6
+ 2,
7
+ 2
8
8
  ]
9
9
  }
package/dist/mcp.js CHANGED
@@ -98,7 +98,7 @@ export function isInsufficientScope(error) {
98
98
  return /\b403\b/.test(message);
99
99
  }
100
100
  /** Abilities every credential has (Ability registry: BASELINE with the MCP surface). */
101
- const BASELINE_TOOL_SCOPES = new Set(['system', 'guide', 'app_struct']);
101
+ const BASELINE_TOOL_SCOPES = new Set(['system', 'knowledge', 'app_struct']);
102
102
  const TOOL_SCOPE = /^axom__([a-z][a-z0-9]*(?:_[a-z0-9]+)*)__[a-z]/;
103
103
  const ADMIN_NOTE = ' Scopes marked "Requires an administrator account" on the consent page also need an administrator user.';
104
104
  /** An external tool's middle segment IS the OAuth scope that grants it (spec 2026-09-26 §3.5). */
Binary file
@@ -1,4 +1,4 @@
1
- import { homedir } from 'node:os';
1
+ import { assertRuntimeRequest, loadRuntimeRelease, managedRuntimeFor, verifyManagedRuntime } from './managedRuntime.js';
2
2
  /**
3
3
  * The daemon's single entry point into ACP — what `agentSessions.ts` holds where it used to hold a
4
4
  * `Map<string, AgentBridge>`.
@@ -32,7 +32,6 @@ import { homedir } from 'node:os';
32
32
  * reaching the network or running `npx`.
33
33
  */
34
34
  import { RequestError } from '@agentclientprotocol/sdk';
35
- import { certifiedRuntime } from '../qoderAdapter/foregroundOutput.js';
36
35
  import { existsSync } from 'node:fs';
37
36
  import { readdir } from 'node:fs/promises';
38
37
  import path from 'node:path';
@@ -45,7 +44,7 @@ import { deleteNativeSessions, listNativeSessions } from './adoption.js';
45
44
  import { AcpAuth } from './auth.js';
46
45
  import { binaryTargetFor, installBinary, platformTriple } from './binaryInstall.js';
47
46
  import { binaryBuild } from './builds/install/binary.js';
48
- import { defaultNpmCommand, ensurePristine, npmBuild } from './builds/install/npm.js';
47
+ import { defaultNpmCommand, ensurePristine, installedDependencies, npmBuild } from './builds/install/npm.js';
49
48
  import { installUvBuild } from './builds/install/uv.js';
50
49
  import { adoptNpxCopy, findNpxCopy, legacyBinaryVersions, moveLegacyBinary, RESERVED_ROOT_NAMES } from './builds/migrate.js';
51
50
  import { AdapterNotInstalledError, AdapterStillInstallingError, binaryBuildDir, npmPristineDir, inside } from './builds/model.js';
@@ -57,14 +56,12 @@ import { BuildStore } from './builds/store.js';
57
56
  import { selectPackagePatches, patchDigest } from './managedPatches.js';
58
57
  import { nativeExtensionFor, nativeAccountLimitsVersionFor } from './patchAssets.js';
59
58
  import { ACCOUNT_LIMITS_READ_TIMEOUT_MS } from '../protocol/accountLimits.js';
60
- import { buildSessionMcpServers } from './delegationMcp.js';
61
59
  import { overlayEnvSecrets } from './envSecrets.js';
62
- import { AdoptionRefusedError, ProcessorSession } from './processorSession.js';
60
+ import { AdoptionRefusedError, ProcessorSession, readOutboxTail } from './processorSession.js';
63
61
  import { hostsRunningAdapter } from './processor/transport/generations.js';
64
62
  import { isOutboxTail } from './outbox.js';
65
63
  import { LocalTransport } from './processor/transport/local.js';
66
64
  import { AcpKeeperUnavailableError } from '../keepers/acp/client.js';
67
- import { prepareQoderStartup, QODER_MANAGED_VERSION } from './qoderStartup.js';
68
65
  import { friendlyOrdinaryInstallError, friendlyPinnedVersionUnavailableAndFallbackFailed, friendlyUvMissingError, logInstallFailureDetail, } from './installErrors.js';
69
66
  import { ADAPTER_INSTALL_TIMEOUT_MS, npmCacheDir, npmPackageName, npmPackageVersion, pickInstalledVersion, findOnPath, probeEnvironment, runDependencyInstall, } from './installProbe.js';
70
67
  import { createDeferredPermissionSink, registerPermissionHandlers } from './permission.js';
@@ -498,11 +495,14 @@ export class AcpAgents {
498
495
  quotaReaders = new Map();
499
496
  /** The `uv` uv builds are installed with; see {@link AcpAgentsOptions.runUv}. */
500
497
  runUv;
498
+ serverOrigin;
501
499
  /** Injectable `npm` for adapter builds; see {@link AcpAgentsOptions.npmCommand}. */
502
500
  npmCommand;
503
501
  nodePath;
504
502
  binaryFetch;
505
503
  patchRecipes;
504
+ managedRuntimePolicies;
505
+ patchAssetRoot;
506
506
  /** `builds.json` and the per-agent install/switch/GC lock (spec §6.1). */
507
507
  builds;
508
508
  /** The registry with catalog supplements but without overrides applied — {@link effectiveEntries} applies them, keeping launchEnv apart. */
@@ -510,6 +510,7 @@ export class AcpAgents {
510
510
  /** {@link OPEN_INSTALL_BUDGET_MS}, overridable for tests. */
511
511
  openInstallBudgetMs;
512
512
  cacheDir;
513
+ ownership;
513
514
  transports;
514
515
  generations;
515
516
  /** The once-per-daemon adoption's discovery ({@link adoptKeeperSessions}). */
@@ -575,14 +576,18 @@ export class AcpAgents {
575
576
  this.runCommand = options.runCommand ?? runDependencyInstall;
576
577
  this.runUv = options.runUv ?? defaultRunUv;
577
578
  this.nodePath = options.nodePath ?? process.execPath;
579
+ this.serverOrigin = options.serverOrigin;
578
580
  this.npmCommand = options.npmCommand ?? defaultNpmCommand(this.nodePath, this.platform);
579
581
  this.binaryFetch = options.binaryFetch;
580
582
  this.patchRecipes = options.patchRecipes;
583
+ this.managedRuntimePolicies = options.managedRuntimePolicies;
584
+ this.patchAssetRoot = options.patchAssetRoot;
581
585
  this.commandRetryFirstBroadcastMs = options.commandRetryFirstBroadcastMs ?? COMMAND_RETRY_FIRST_BROADCAST_MS;
582
586
  this.openInstallBudgetMs = options.openInstallBudgetMs ?? OPEN_INSTALL_BUDGET_MS;
583
- this.adapterRoot = path.join(options.cacheDir, 'acp-adapters');
587
+ this.adapterRoot = path.join(options.adapterCacheDirectory ?? options.cacheDir, 'acp-adapters');
584
588
  this.builds = new BuildStore(this.adapterRoot);
585
589
  this.cacheDir = options.cacheDir;
590
+ this.ownership = options.ownership;
586
591
  this.transports = { ...options.transports };
587
592
  this.generations = options.keeperGenerations;
588
593
  this.initializeTimeoutMs = options.initializeTimeoutMs ?? DEFAULT_SESSION_INITIALIZE_TIMEOUT_MS;
@@ -956,7 +961,7 @@ export class AcpAgents {
956
961
  spec.onInstallOutcome?.(outcome);
957
962
  }));
958
963
  }
959
- let resolved = await phase('prepare-launch', () => this.resolveLaunchFor(spec.agentId, spec.command, spec.args));
964
+ let resolved = await phase('prepare-launch', () => this.resolveLaunchFor(spec.agentId, spec.command, spec.args, spec.recordedBuildId));
960
965
  // Spec §5.5.6 (Task 24): a session the adoption could not continue is switched by this open — its process is closed and a new
961
966
  // one loads the native session, never falling back to `session/new`.
962
967
  if (this.transports.keeper && spec.sessionRef !== undefined && spec.nativeSessionId) {
@@ -966,20 +971,9 @@ export class AcpAgents {
966
971
  }
967
972
  // GC never takes the build between resolving it and the spawn (whose keeper-acp label references it from then on).
968
973
  const releaseLease = this.leaseBuild(spec.agentId, resolved.launch.label?.buildId);
969
- const startup = spec.agentId === 'qoder' && spec.delegationMcp
970
- ? await phase('prepare-qoder', () => prepareQoderStartup(resolved.launch, spec.delegationMcp, spec.cwd, spec.env, spec.agentEnv))
971
- : undefined;
972
- if (startup)
973
- resolved = { launch: startup.launch, source: resolved.source };
974
974
  let session;
975
975
  try {
976
- session = await phase('initialize', () => this.startProcessorSession(resolved, spec, startup, installed));
977
- }
978
- catch (error) {
979
- await startup?.dispose();
980
- if (startup)
981
- throw new Error('Managed Qoder startup could not initialize');
982
- throw error;
976
+ session = await phase('initialize', () => this.startProcessorSession(resolved, spec, installed));
983
977
  }
984
978
  finally {
985
979
  releaseLease();
@@ -987,14 +981,8 @@ export class AcpAgents {
987
981
  const facts = session.initializeFacts;
988
982
  this.observe(spec.agentId, observationOfFacts(facts));
989
983
  try {
990
- if (startup && (session.agentInfo?.name !== 'qoder-cli' || session.agentInfo.version !== QODER_MANAGED_VERSION)) {
991
- throw new Error('Managed Qoder runtime identity is not certified');
992
- }
993
- // Qoder 1.1.61 accepts HTTP MCP at startup, but rejects that entry in ACP new/load.
994
- const sessionSpec = { ...spec, managedHistoryResumeRequired: !!spec.delegationMcp || spec.managedHistoryResumeRequired,
995
- ...(startup ? { delegationMcp: undefined, requireSessionLoad: spec.requireSessionLoad || !!spec.nativeSessionId } : {}) };
996
984
  const operation = spec.nativeSessionId && facts.capabilities.loadSession ? 'session/load' : 'session/new';
997
- const started = await phase(operation, () => withDeadline(this.startSession(session, facts, sessionSpec), this.sessionStartTimeoutMs, `Agent '${spec.agentId}' ${operation}`, AgentSessionStartTimeoutError));
985
+ const started = await phase(operation, () => withDeadline(this.startSession(session, facts, spec), this.sessionStartTimeoutMs, `Agent '${spec.agentId}' ${operation}`, AgentSessionStartTimeoutError));
998
986
  // Only here, never before: the agent accepted a session, which is the one piece of evidence
999
987
  // ACP offers that it is not waiting on credentials. Recording it up at `initialize` time
1000
988
  // would claim authentication for an agent about to reject `session/new` with auth_required.
@@ -1002,32 +990,31 @@ export class AcpAgents {
1002
990
  return { session, nativeSessionId: started.nativeSessionId };
1003
991
  }
1004
992
  catch (error) {
1005
- await session.close('auto').catch(() => undefined);
1006
- await startup?.dispose();
993
+ // A recorded-build reopen is the only path allowed to retain an empty keeper record after
994
+ // bootstrap failure: the old native session still belongs to that package, and releasing an
995
+ // acknowledged transport would make the next retry silently select the mutable current build.
996
+ await session.close(spec.recordedBuildId !== undefined ? 'keep' : 'auto').catch(() => undefined);
1007
997
  // Marked, not merely rethrown: everything above this `try` failed before the agent said
1008
998
  // anything, and only a failure from here on can be an `auth_required`. See
1009
999
  // {@link AgentRefusedSessionError} — `agentSessions.ts` is the reader.
1010
- throw new AgentRefusedSessionError(error, startup ? 'Managed Qoder session could not open' : undefined);
1000
+ throw new AgentRefusedSessionError(error, undefined);
1011
1001
  }
1012
1002
  }
1013
1003
  /**
1014
1004
  * Spawn the adapter behind a {@link ProcessorSession} and wait for its `initialize` (spec §5.3: keeper-acp, or the local
1015
1005
  * fallback when keeper-acp cannot be reached — §5.7). The spawn spec carries the **complete** environment (keeper-acp never
1016
- * reads its own), the daemon's working directory (where `connectAgent` started adapters), a managed startup's secrets for
1017
- * stderr redaction and its temporary files to remove on exit; the label is opaque to the keeper.
1006
+ * reads its own), the daemon's working directory (where `connectAgent` started adapters); the label is opaque to the keeper.
1018
1007
  */
1019
- async startProcessorSession(resolved, spec, startup, installOutcome) {
1020
- const { spawn, label: baseLabel, launch } = this.spawnSpecOf(resolved, spec, startup);
1008
+ async startProcessorSession(resolved, spec, installOutcome) {
1009
+ const { spawn, label: baseLabel, launch } = this.spawnSpecOf(resolved, spec);
1021
1010
  const ref = spec.sessionRef;
1022
1011
  const sessionKey = ref ?? `open-${randomUUID()}`;
1023
- // A ref whose earlier life ran in the daemon (the local fallback, or a migrated legacy journal) and left frames the server has
1024
- // not applied continues there for this life, so they are resent from where they are; a drained one is released, its watermark
1025
- // kept as the floor. Either way the processor numbers after everything that ref ever held (spec §5.5).
1026
1012
  const resumeFrom = spec.seqBase?.() ?? 0;
1027
1013
  const local = await this.localLeftover(sessionKey);
1028
1014
  let seqBase = Math.max(resumeFrom, local?.lastSeq ?? 0);
1029
- const stayLocal = local !== undefined && local.lastSeq > resumeFrom && local.unacknowledged;
1030
- if (local && !stayLocal)
1015
+ const pendingLocal = local !== undefined && local.lastSeq > resumeFrom && local.unacknowledged;
1016
+ const stayLocal = pendingLocal && !this.transports.keeper;
1017
+ if (local && !pendingLocal)
1031
1018
  await this.localTransport().release(sessionKey).catch(() => undefined);
1032
1019
  // Sessions an earlier daemon left in keeper-acp are discovered (and adopted) before keeper-acp spawns anything for this one.
1033
1020
  if (this.transports.keeper)
@@ -1056,7 +1043,6 @@ export class AcpAgents {
1056
1043
  spawn, seqBase, backfill: spec.backfill === true,
1057
1044
  ...(installOutcome ? { installOutcome } : {}),
1058
1045
  launch,
1059
- ...(startup ? { onClosed: () => startup.dispose() } : {}),
1060
1046
  };
1061
1047
  let session;
1062
1048
  if (this.transports.keeper && !stayLocal) {
@@ -1079,10 +1065,15 @@ export class AcpAgents {
1079
1065
  else
1080
1066
  this.heldOnOlder.delete(keeperKey); // a reopen hold on an older generation that is gone now
1081
1067
  try {
1068
+ const imports = pendingLocal ? await readOutboxTail(this.localTransport(), sessionKey, local.outbox) : undefined;
1069
+ if (imports && local && imports.length !== local.outbox.next - Math.max(local.outbox.first, local.outbox.committed + 1)) {
1070
+ throw new Error('The local session outbox could not be read completely');
1071
+ }
1082
1072
  session = await ProcessorSession.start({ ...options, sessionKey: keeperKey, transport: target.transport,
1073
+ ...(imports ? { imports } : {}),
1083
1074
  label: { ...baseLabel, ...(keyGen > 0 ? { keyGen } : {}) },
1084
1075
  onDegrade: this.recordDegraded(keeperKey, target.gen ?? 'current'),
1085
- ...(target.gen !== undefined ? { onClosed: () => this.olderSessionClosed(keeperKey, options.onClosed) } : {}) });
1076
+ ...(target.gen !== undefined ? { onClosed: () => this.olderSessionClosed(keeperKey) } : {}) });
1086
1077
  }
1087
1078
  catch (error) {
1088
1079
  this.heldOnOlder.delete(keeperKey);
@@ -1113,22 +1104,26 @@ export class AcpAgents {
1113
1104
  await session.initialized(this.initializeTimeoutMs);
1114
1105
  }
1115
1106
  catch (error) {
1116
- await session.close('auto').catch(() => undefined);
1107
+ await session.close(spec.recordedBuildId !== undefined ? 'keep' : 'auto').catch(() => undefined);
1117
1108
  throw error;
1118
1109
  }
1110
+ if (pendingLocal && session.transportMode === 'keeper') {
1111
+ await this.localTransport().release(sessionKey).catch((error) => {
1112
+ console.warn(`[acp] releasing the migrated local outbox for ${sessionKey} failed: ${errorMessage(error)}`);
1113
+ });
1114
+ }
1119
1115
  return session;
1120
1116
  }
1121
1117
  /**
1122
1118
  * The spawn spec, label and launch diagnostics of a launch (spec §5.3): the **complete** environment (keeper-acp never reads its
1123
- * own), the daemon's working directory (where `connectAgent` started adapters), a managed startup's secrets for stderr redaction and
1124
- * its temporary files to remove on exit; the label is opaque to the keeper.
1119
+ * own), the daemon's working directory (where `connectAgent` started adapters), the label is opaque to the keeper.
1125
1120
  */
1126
- spawnSpecOf(resolved, spec, startup) {
1121
+ spawnSpecOf(resolved, spec) {
1127
1122
  const { launch } = resolved;
1128
1123
  const env = completeEnvironment(withPathPrefix({ ...process.env, ...launch.env, ...spec.env, ...spec.agentEnv }, launch.pathPrefix));
1129
1124
  const cwd = process.cwd();
1130
- // keeper-acp takes at most ACP_STDERR_SECRETS_MAX; a managed startup's own secrets come first.
1131
- const allSecrets = [...(startup?.secrets ?? []), ...overlayEnvSecrets(spec)];
1125
+ // keeper-acp takes at most ACP_STDERR_SECRETS_MAX.
1126
+ const allSecrets = [...overlayEnvSecrets(spec)];
1132
1127
  if (allSecrets.length > ACP_STDERR_SECRETS_MAX) {
1133
1128
  console.warn(`[acp] agent '${spec.agentId}': ${allSecrets.length - ACP_STDERR_SECRETS_MAX} of ${allSecrets.length} env values ` +
1134
1129
  `are not redacted from its stderr (keeper-acp takes at most ${ACP_STDERR_SECRETS_MAX})`);
@@ -1137,7 +1132,6 @@ export class AcpAgents {
1137
1132
  const spawn = {
1138
1133
  command: launch.command, args: [...launch.args], env, cwd,
1139
1134
  ...(stderrSecrets.length > 0 ? { stderrSecrets } : {}),
1140
- ...(startup ? { removeOnExit: [startup.dir] } : {}),
1141
1135
  };
1142
1136
  const label = {
1143
1137
  sessionRef: spec.sessionRef ?? null, agentId: spec.agentId, agentKey: spec.agentKey ?? null,
@@ -1450,9 +1444,6 @@ export class AcpAgents {
1450
1444
  else if (!nativeSessionId) {
1451
1445
  outcome = refuse('the session has no native session to load');
1452
1446
  }
1453
- else if (spec.delegationMcp || from.delegation) {
1454
- outcome = refuse('a managed delegation session is not moved');
1455
- }
1456
1447
  else {
1457
1448
  outcome = await this.builds.withAgentLock(spec.agentId, () => this.switchLocked(from, { ...spec, sessionRef: ref, nativeSessionId }, request))
1458
1449
  .catch((error) => refuse(`the switch failed before anything was stopped: ${errorMessage(error)}`, false));
@@ -1731,6 +1722,7 @@ export class AcpAgents {
1731
1722
  source: 'registry',
1732
1723
  launch: await launchForBuild(build, this.adapterRoot, {
1733
1724
  nodePath: this.nodePath, launchEnv: catalogLaunchEnv(agentId, build.registryVersion), basePath: this.probe.path,
1725
+ ...this.runtimeAssets(agentId), allowLegacy: true,
1734
1726
  }),
1735
1727
  };
1736
1728
  }
@@ -1853,6 +1845,8 @@ export class AcpAgents {
1853
1845
  * be listed. One at a time; never rejects (failures are logged).
1854
1846
  */
1855
1847
  collectBuilds(options = {}) {
1848
+ if (this.ownership)
1849
+ return Promise.resolve({ removed: [], discarded: [], kept: [], legacyRemoved: [], skipped: 'Device-wide build references are incomplete' });
1856
1850
  const previous = this.collecting;
1857
1851
  let run;
1858
1852
  run = (async () => {
@@ -1892,12 +1886,11 @@ export class AcpAgents {
1892
1886
  return this.unlistedGens.has(gen) || [...this.heldOnOlder.values()].includes(gen);
1893
1887
  }
1894
1888
  /** A session of this daemon on an older generation ended: that generation may be idle now. */
1895
- olderSessionClosed(sessionKey, also) {
1889
+ olderSessionClosed(sessionKey) {
1896
1890
  // A reopen's retire keeps the hold: the respawn goes into the same generation right behind it (fix round 2).
1897
1891
  if (!this.reopenHolds.has(sessionKey))
1898
1892
  this.heldOnOlder.delete(sessionKey);
1899
1893
  void this.retireIdleGenerations();
1900
- return also?.();
1901
1894
  }
1902
1895
  /**
1903
1896
  * A keeper-hosted session degraded (keeper-acp gone, or poisoned): its outbox head is remembered, so a later open of the ref that
@@ -1979,6 +1972,7 @@ export class AcpAgents {
1979
1972
  const tail = summary.outbox.committedTail;
1980
1973
  const tailSeq = typeof tail?.seq === 'number' ? tail.seq : 0;
1981
1974
  return {
1975
+ outbox: summary.outbox,
1982
1976
  lastSeq: Math.max(summary.outbox.lastSeq ?? 0, tailSeq),
1983
1977
  unacknowledged: summary.outbox.next > Math.max(summary.outbox.first, summary.outbox.committed + 1),
1984
1978
  };
@@ -2075,15 +2069,18 @@ export class AcpAgents {
2075
2069
  }
2076
2070
  /** The fallback transport (spec §5.7), made on first use. */
2077
2071
  localTransport() {
2078
- this.transports.local ??= new LocalTransport({ configDir: this.cacheDir });
2072
+ this.transports.local ??= new LocalTransport({ configDir: this.cacheDir, ownership: this.ownership });
2079
2073
  return this.transports.local;
2080
2074
  }
2081
2075
  /** Daemon shutdown: keeper-acp's connection is dropped (its adapters keep running); local adapters are stopped. */
2082
- closeTransports() {
2076
+ async closeTransports() {
2083
2077
  this.clearQuotaReaders();
2084
2078
  this.transports.keeper?.close();
2085
2079
  this.generations?.close();
2086
- this.transports.local?.close();
2080
+ if (this.transports.local instanceof LocalTransport)
2081
+ await this.transports.local.dispose();
2082
+ else
2083
+ this.transports.local?.close();
2087
2084
  }
2088
2085
  clearQuotaReaders(agentId) {
2089
2086
  for (const [key, reader] of this.quotaReaders)
@@ -2113,60 +2110,35 @@ export class AcpAgents {
2113
2110
  async startSession(session, facts, spec) {
2114
2111
  const nativeSessionId = spec.nativeSessionId;
2115
2112
  const capabilities = facts.capabilities;
2116
- if (nativeSessionId && spec.managedHistoryResumeRequired) {
2117
- throw new Error('Managed native history replay has no certified boundary');
2118
- }
2119
- if (spec.delegationMcp && !capabilities.mcpHttp) {
2120
- throw new Error(`Agent '${spec.agentId}' does not advertise HTTP MCP support`);
2121
- }
2122
2113
  if (spec.requireSessionLoad && !nativeSessionId) {
2123
2114
  throw new Error('Refreshing settings requires an existing native session ID');
2124
2115
  }
2125
- const historyHome = spec.agentEnv.HOME ?? spec.env.HOME ?? homedir();
2126
- // The finished MCP list (the lease checked here, against the clock, before any note is written); the bearer is a secretRef
2127
- // in the note and the down record, its value only in this daemon's memory and the adapter's stdin (spec §13).
2128
- const servers = buildSessionMcpServers(spec.mcpServers, spec.delegationMcp);
2116
+ const mcpServers = [...(spec.mcpServers ?? [])];
2129
2117
  const redact = [];
2130
- const mcpServers = servers.map((server) => {
2131
- if (!('headers' in server) || !spec.delegationMcp || server.name !== 'axom-delegation')
2132
- return server;
2133
- return { ...server, headers: server.headers.map((header) => {
2134
- const ref = session.secret(header.value);
2135
- redact.push(ref);
2136
- return { ...header, value: ref };
2137
- }) };
2138
- });
2139
- const delegation = spec.delegationMcp && spec.delegation ? {
2140
- expiresAtEpochMs: spec.delegationMcp.expiresAtEpochMs, leaseId: spec.delegation.leaseId,
2141
- ...(spec.delegation.bindingId !== undefined && spec.delegation.instanceId !== undefined
2142
- ? { bindingId: spec.delegation.bindingId, instanceId: spec.delegation.instanceId } : {}),
2143
- } : undefined;
2144
- const request = { cwd: spec.cwd, mcpServers, redact, ...(delegation ? { delegation } : {}) };
2118
+ const request = { cwd: spec.cwd, mcpServers, redact };
2145
2119
  const refused = (result) => (result.code !== undefined ? new RequestError(result.code, result.error) : new Error(result.error));
2146
- // Qoder's load may emit old chunks after its response. Cached reconnects need context, not
2147
- // another history import. Actual adoption/backfill and explicit settings loads still use load.
2148
- if (nativeSessionId && certifiedRuntime(facts.agentInfo) && capabilities.resume &&
2149
- !spec.backfill && !spec.requireSessionLoad && !spec.delegationMcp) {
2120
+ // The adapter declares cached reconnect semantics in initialize; explicit history/settings loads still use load.
2121
+ if (nativeSessionId && facts.cachedReconnect === 'resume' && capabilities.resume &&
2122
+ !spec.backfill && !spec.requireSessionLoad) {
2150
2123
  const result = await session.openSession({ ...request, mode: 'resume', sessionId: nativeSessionId });
2151
2124
  if (!result.ok)
2152
2125
  throw refused(result); // Never silently replace this cached conversation with a fresh native root.
2153
- await session.enableRepair(nativeSessionId, spec.cwd, historyHome, true);
2154
2126
  return { nativeSessionId };
2155
2127
  }
2156
2128
  if (nativeSessionId) {
2157
2129
  if (!capabilities.loadSession) {
2158
- if (spec.requireSessionLoad || spec.delegationMcp)
2130
+ if (spec.requireSessionLoad)
2159
2131
  throw new Error(`Agent '${spec.agentId}' does not support session/load`);
2160
2132
  console.warn(`[acp] agent '${spec.agentId}' does not support session/load; starting a new session ` +
2161
2133
  `instead of continuing '${nativeSessionId}'`);
2162
2134
  }
2163
2135
  else {
2164
- const result = await session.openSession({ ...request, mode: 'load', sessionId: nativeSessionId, repair: { historyHome } });
2136
+ const result = await session.openSession({ ...request, mode: 'load', sessionId: nativeSessionId });
2165
2137
  if (result.ok) {
2166
2138
  await session.endHistory();
2167
2139
  return { nativeSessionId };
2168
2140
  }
2169
- if (spec.requireSessionLoad || spec.delegationMcp)
2141
+ if (spec.requireSessionLoad)
2170
2142
  throw refused(result);
2171
2143
  console.warn(`[acp] session/load for '${nativeSessionId}' on agent '${spec.agentId}' failed ` +
2172
2144
  `(${result.error}); starting a new session instead`);
@@ -2175,8 +2147,6 @@ export class AcpAgents {
2175
2147
  const result = await session.openSession({ ...request, mode: 'new' });
2176
2148
  if (!result.ok)
2177
2149
  throw refused(result);
2178
- if (result.nativeSessionId)
2179
- await session.enableRepair(result.nativeSessionId, spec.cwd, historyHome);
2180
2150
  return { nativeSessionId: result.nativeSessionId };
2181
2151
  }
2182
2152
  /**
@@ -2570,6 +2540,9 @@ export class AcpAgents {
2570
2540
  ...(outcome.pinnedVersionUnavailable ? { pinnedVersionUnavailable: outcome.pinnedVersionUnavailable } : {}),
2571
2541
  };
2572
2542
  }
2543
+ runtimeAssets(agentId) {
2544
+ return { policy: managedRuntimeFor(agentId, this.managedRuntimePolicies), assetRoot: this.patchAssetRoot, recipes: this.patchRecipes };
2545
+ }
2573
2546
  /** The registry as Axom installs it (catalog supplements, pins, launchEnv kept apart) — see `registry/catalog.ts`. */
2574
2547
  async effective() {
2575
2548
  return effectiveEntries(await this.baseRegistry.load());
@@ -2590,18 +2563,21 @@ export class AcpAgents {
2590
2563
  currentBuildId) {
2591
2564
  const agent = entry.agent;
2592
2565
  const { npx, uvx, binary } = agent.distribution;
2566
+ const policy = this.runtimeAssets(agent.id).policy;
2567
+ if (policy && (!npx || agent.version !== policy.version))
2568
+ return { ok: false, error: 'Managed runtime Catalog/distribution differs from bundled release policy' };
2593
2569
  const source = entry.pinnedBy ?? 'registry';
2594
2570
  const commit = (build) => this.builds.commit(build, { makeCurrent: build.buildId !== currentBuildId, setBy });
2595
2571
  if (npx) {
2596
- const pristine = await this.installPristineWithFallback(agent, npx.package, timeoutMs);
2572
+ const pristine = await this.installPristineWithFallback(agent, timeoutMs);
2597
2573
  if (!pristine.ok)
2598
2574
  return pristine;
2599
2575
  try {
2600
2576
  const build = await npmBuild({
2601
- store: this.builds, agentId: agent.id, name: npmPackageName(npx.package), version: pristine.version,
2577
+ store: this.builds, agentId: agent.id, name: npx.tarball ? npx.package : npmPackageName(npx.package), version: pristine.version,
2602
2578
  args: npx.args ?? [], env: npx.env ?? {}, registryVersion: entry.registryVersion,
2603
2579
  source: pristine.pinnedVersionUnavailable ? 'unpinned-fallback' : source, installedBy: setBy,
2604
- nodePath: this.nodePath,
2580
+ nodePath: this.nodePath, ...this.runtimeAssets(agent.id),
2605
2581
  ...(this.patchRecipes ? { recipes: this.patchRecipes } : {}),
2606
2582
  });
2607
2583
  const recorded = pristine.pinnedVersionUnavailable
@@ -2693,18 +2669,24 @@ export class AcpAgents {
2693
2669
  * have), one retry unpinned, recording the version that actually landed. This is the **only** place
2694
2670
  * that fallback exists now: a launch never resolves a version (spec §6.2 "ETARGET 回退移到安装期").
2695
2671
  */
2696
- async installPristineWithFallback(agent, pinnedSpec, timeoutMs) {
2697
- const name = npmPackageName(pinnedSpec);
2698
- const version = npmPackageVersion(pinnedSpec);
2672
+ async installPristineWithFallback(agent, timeoutMs) {
2673
+ const distribution = agent.distribution.npx;
2674
+ const pinnedSpec = distribution.package;
2675
+ const name = distribution.tarball ? distribution.package : npmPackageName(pinnedSpec);
2676
+ const version = distribution.tarball ? agent.version : npmPackageVersion(pinnedSpec);
2677
+ if (distribution.tarball && !this.serverOrigin)
2678
+ return { ok: false, error: 'Adapter is served by the Axom server; this workstation has no server configured' };
2679
+ const tarball = distribution.tarball ? { url: new URL(distribution.tarball.path, this.serverOrigin).href, sha512: distribution.tarball.sha512 } : undefined;
2680
+ const runtime = this.runtimeAssets(agent.id);
2699
2681
  const budget = timeoutMs ?? ADAPTER_INSTALL_TIMEOUT_MS;
2700
2682
  const install = (pinned) => ensurePristine({
2701
- store: this.builds, name, ...(pinned !== undefined ? { version: pinned } : {}), npmCommand: this.npmCommand, timeoutMs: budget,
2683
+ store: this.builds, name, ...runtime, ...(pinned !== undefined ? { version: pinned } : {}), npmCommand: this.npmCommand, timeoutMs: budget, ...(tarball ? { tarball } : {}),
2702
2684
  }).catch((error) => ({ ok: false, error: errorMessage(error), stderr: '' }));
2703
2685
  const outcome = await install(version);
2704
2686
  if (outcome.ok)
2705
2687
  return outcome;
2706
2688
  const existingVersion = (await this.builds.current(agent.id))?.version;
2707
- if (version === undefined || !stderrShowsMissingPackageVersion(outcome.stderr)) {
2689
+ if (runtime.policy || tarball || version === undefined || !stderrShowsMissingPackageVersion(outcome.stderr)) {
2708
2690
  logInstallFailureDetail(agent.id, 'npx', pinnedSpec, outcome.stderr || outcome.error);
2709
2691
  return { ok: false, error: friendlyOrdinaryInstallError(agent.id, outcome.stderr || outcome.error, existingVersion) };
2710
2692
  }
@@ -2779,6 +2761,24 @@ export class AcpAgents {
2779
2761
  const current = await this.builds.current(agentId);
2780
2762
  if (!current)
2781
2763
  return undefined;
2764
+ const runtime = this.runtimeAssets(agentId);
2765
+ if (runtime.policy) {
2766
+ const npx = entry.agent.distribution.npx;
2767
+ if (!npx || entry.agent.version !== runtime.policy.version)
2768
+ throw new Error('Managed runtime Catalog/distribution differs from bundled policy');
2769
+ assertRuntimeRequest(runtime.policy, npx.tarball ? npx.package : npmPackageName(npx.package), npx.tarball ? entry.agent.version : npmPackageVersion(npx.package));
2770
+ const release = await loadRuntimeRelease(runtime.policy, runtime);
2771
+ if (current.runtimeReleaseId === release.id || (!current.runtimeReleaseId && current.package === runtime.policy.package && current.version === runtime.policy.version)) {
2772
+ await verifyManagedRuntime({ ...runtime, policy: runtime.policy, build: current, storeRoot: this.adapterRoot, nodePath: this.nodePath, initialize: false });
2773
+ return undefined;
2774
+ }
2775
+ const outcome = await this.installAdapter(entry, 'axom', timeoutMs, current.buildId);
2776
+ if (!outcome.ok)
2777
+ throw new Error(outcome.error);
2778
+ if (!outcome.build || outcome.build.buildId === current.buildId)
2779
+ return undefined;
2780
+ return { agentId, from: current.buildId, to: outcome.build.buildId, reason: 'certified-pin' };
2781
+ }
2782
2782
  // The pin this CLI certifies was already tried and npm did not have it: the current build is the
2783
2783
  // install-time fallback for exactly this pin. Trying again on every daemon start would only repeat
2784
2784
  // the same network round trip and land on the same build.
@@ -2796,7 +2796,7 @@ export class AcpAgents {
2796
2796
  if ((current.kind !== 'npm' && current.kind !== 'npm-patched') || !current.package)
2797
2797
  return undefined;
2798
2798
  const pristine = inside(this.adapterRoot, npmPristineDir(current.package, current.version));
2799
- const recipes = await selectPackagePatches(current.package, current.version, inside(path.join(pristine, 'node_modules'), current.package), this.patchRecipes);
2799
+ const recipes = await selectPackagePatches(current.package, current.version, inside(path.join(pristine, 'node_modules'), current.package), this.patchRecipes, await installedDependencies(pristine));
2800
2800
  const wanted = recipes.length ? patchDigest(recipes) : undefined;
2801
2801
  if (wanted === current.patchDigest)
2802
2802
  return undefined;
@@ -2890,6 +2890,8 @@ export class AcpAgents {
2890
2890
  if (!entry)
2891
2891
  return undefined;
2892
2892
  const { npx, uvx, binary } = entry.agent.distribution;
2893
+ if (this.runtimeAssets(agentId).policy && !npx)
2894
+ throw new Error('Managed runtime migration requires its certified npm distribution');
2893
2895
  if (npx)
2894
2896
  return this.migrateFromNpx(entry, npx.package, npmCache);
2895
2897
  // uv: the user's own uv tool dir is not ours to take over; the first open installs a build.
@@ -2903,6 +2905,12 @@ export class AcpAgents {
2903
2905
  const name = npmPackageName(spec);
2904
2906
  // Exactly what the pre-build launch ran: `npx <name>@<effective version>`.
2905
2907
  const version = requestedVersionOf(entry);
2908
+ const runtime = this.runtimeAssets(agentId);
2909
+ if (runtime.policy) {
2910
+ if (entry.agent.version !== runtime.policy.version)
2911
+ throw new Error('Managed runtime Catalog version mismatch');
2912
+ assertRuntimeRequest(runtime.policy, name, version);
2913
+ }
2906
2914
  const copy = await findNpxCopy(npmCache, name, version);
2907
2915
  if (!copy)
2908
2916
  return undefined;
@@ -2912,7 +2920,7 @@ export class AcpAgents {
2912
2920
  ...await npmBuild({
2913
2921
  store: this.builds, agentId, name, version, args: npx.args ?? [], env: npx.env ?? {},
2914
2922
  registryVersion: entry.registryVersion, source: entry.pinnedBy ?? 'registry', installedBy: 'axom',
2915
- nodePath: this.nodePath, ...(this.patchRecipes ? { recipes: this.patchRecipes } : {}),
2923
+ nodePath: this.nodePath, ...runtime, ...(this.patchRecipes ? { recipes: this.patchRecipes } : {}),
2916
2924
  }),
2917
2925
  migratedFrom: 'npx-cache',
2918
2926
  };
@@ -3064,6 +3072,7 @@ export class AcpAgents {
3064
3072
  return launchForBuild(current, this.adapterRoot, {
3065
3073
  nodePath: this.nodePath,
3066
3074
  launchEnv: catalogLaunchEnv(agentId, current.registryVersion),
3075
+ ...this.runtimeAssets(agentId),
3067
3076
  basePath: this.probe.path,
3068
3077
  });
3069
3078
  }
@@ -3075,9 +3084,11 @@ export class AcpAgents {
3075
3084
  throw new AdapterNotInstalledError(agentId);
3076
3085
  }
3077
3086
  /** A config-supplied `command` is the whole launch spec (ruling R3); otherwise the current build decides. */
3078
- async resolveLaunchFor(agentId, command, args) {
3087
+ async resolveLaunchFor(agentId, command, args, recordedBuildId) {
3079
3088
  if (command)
3080
3089
  return { launch: { command, args, env: {} }, source: 'command-override' };
3090
+ if (recordedBuildId !== undefined)
3091
+ return this.launchOfBuild(agentId, recordedBuildId);
3081
3092
  return { source: 'registry', launch: await this.launchSpecFor(agentId) };
3082
3093
  }
3083
3094
  /**
@@ -359,6 +359,19 @@ export async function closeAgentTransport(child, connection, graceMs = CHILD_EXI
359
359
  await connection.closed.catch(() => { });
360
360
  }
361
361
  const DEFAULT_INITIALIZE_TIMEOUT_MS = 30_000;
362
+ /**
363
+ * 登录 URL 扩展通知的方法名与形状(见 {@link connectAgent} 内的注册点注释)。解析器**不抛**:
364
+ * 形状不对返回 `undefined`,处理器对 `undefined` 不做事——坏参数只少一张卡,不该伤到连接。
365
+ */
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 };
374
+ }
362
375
  /**
363
376
  * Spawns an ACP agent subprocess, connects to it over ndjson stdio, and negotiates capabilities
364
377
  * via `initialize`. `env` and `agentEnv` are overlay layers on top of the current process
@@ -393,6 +406,19 @@ export async function connectAgent(spec, env, agentEnv, options = {}) {
393
406
  // connection registers the permission handlers).
394
407
  const generation = randomUUID();
395
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
+ }
396
422
  const connection = app.connect(stream);
397
423
  const timeoutMs = options.initializeTimeoutMs ?? DEFAULT_INITIALIZE_TIMEOUT_MS;
398
424
  let timeoutHandle;