@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.
Files changed (320) hide show
  1. package/README.md +18 -5
  2. package/dist/axom-patch/cli/README.md +119 -9
  3. package/dist/axom-patch/cli/account-limits/README.md +27 -0
  4. package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
  5. package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
  6. package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
  7. package/dist/axom-patch/cli/account-limits/generate.py +123 -0
  8. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +81 -0
  9. package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
  10. package/dist/axom-patch/cli/claude-account-limits.json +37 -0
  11. package/dist/axom-patch/cli/codex-account-limits.json +37 -0
  12. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  13. package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
  14. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  15. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  16. package/dist/axom-patch/cli/nightly-task.md +194 -0
  17. package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
  18. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +5 -1
  19. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  20. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  21. package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
  22. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
  23. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
  24. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
  25. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -5
  26. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -7
  27. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  28. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  29. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  30. package/dist/axom-patch/cli/runtime.json +39 -15
  31. package/dist/axom-patch/cli/zcode-account-limits.json +30 -0
  32. package/dist/axom-patch/cli/zcode-models.json +37 -0
  33. package/dist/axom-patch/cli/zcode-subagent-subwork.json +72 -0
  34. package/dist/cli.js +4 -9
  35. package/dist/commands/knowledge.js +47 -0
  36. package/dist/commands/update.js +65 -3
  37. package/dist/commands/workstation.js +73 -7
  38. package/dist/install/claude/commands.js +13 -13
  39. package/dist/keepers/acp/keeper.mjs +3083 -0
  40. package/dist/keepers/acp/manifest.json +9 -0
  41. package/dist/keepers/terminal/keeper.mjs +8469 -0
  42. package/dist/keepers/terminal/manifest.json +9 -0
  43. package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  44. package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  45. package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
  46. package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
  47. package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
  48. package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
  49. package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
  50. package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
  51. package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
  52. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
  53. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
  54. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
  55. package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  56. package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
  57. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  58. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  59. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  60. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  61. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  62. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  63. package/dist/mcp.js +1 -1
  64. package/dist/native/darwin-arm64/axom-quic +0 -0
  65. package/dist/test/keeperKit.js +67 -0
  66. package/dist/workstation/acp/acpAgents.js +2192 -636
  67. package/dist/workstation/acp/acpClient.js +84 -495
  68. package/dist/workstation/acp/adoption.js +10 -396
  69. package/dist/workstation/acp/binaryInstall.js +100 -27
  70. package/dist/workstation/acp/builds/gc.js +245 -0
  71. package/dist/workstation/acp/builds/install/binary.js +28 -0
  72. package/dist/workstation/acp/builds/install/npm.js +324 -0
  73. package/dist/workstation/acp/builds/install/uv.js +254 -0
  74. package/dist/workstation/acp/builds/migrate.js +195 -0
  75. package/dist/workstation/acp/builds/model.js +128 -0
  76. package/dist/workstation/acp/builds/nodeGate.js +100 -0
  77. package/dist/workstation/acp/builds/store.js +352 -0
  78. package/dist/workstation/acp/commandTableProbe.js +68 -0
  79. package/dist/workstation/acp/configOptions.js +103 -1
  80. package/dist/workstation/acp/envSecrets.js +27 -0
  81. package/dist/workstation/acp/initializeResponse.js +290 -0
  82. package/dist/workstation/acp/installErrors.js +12 -2
  83. package/dist/workstation/acp/installProbe.js +1 -1
  84. package/dist/workstation/acp/managedPatches.js +239 -159
  85. package/dist/workstation/acp/managedRuntime.js +314 -0
  86. package/dist/workstation/acp/outbox.js +330 -0
  87. package/dist/workstation/acp/patchAssets.js +60 -3
  88. package/dist/workstation/acp/permission.js +42 -10
  89. package/dist/workstation/acp/processor/checkpoint.js +78 -0
  90. package/dist/workstation/acp/processor/commands.js +35 -0
  91. package/dist/workstation/acp/processor/control.js +282 -0
  92. package/dist/workstation/acp/processor/emit.js +26 -0
  93. package/dist/workstation/acp/processor/jsonrpc.js +172 -0
  94. package/dist/workstation/acp/processor/memoryLog.js +49 -0
  95. package/dist/workstation/acp/processor/origins.js +48 -0
  96. package/dist/workstation/acp/processor/output.js +67 -0
  97. package/dist/workstation/acp/processor/projection.js +42 -0
  98. package/dist/workstation/acp/processor/record.js +128 -0
  99. package/dist/workstation/acp/processor/runtime.js +105 -0
  100. package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
  101. package/dist/workstation/acp/processor/session.js +496 -0
  102. package/dist/workstation/acp/processor/state.js +67 -0
  103. package/dist/workstation/acp/processor/step.js +442 -0
  104. package/dist/workstation/acp/processor/test/fixtureLog.js +58 -0
  105. package/dist/workstation/acp/processor/test/harness.js +120 -0
  106. package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
  107. package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
  108. package/dist/workstation/acp/processor/transport/generations.js +76 -0
  109. package/dist/workstation/acp/processor/transport/keeper.js +130 -0
  110. package/dist/workstation/acp/processor/transport/local.js +269 -0
  111. package/dist/workstation/acp/processor/transport/types.js +78 -0
  112. package/dist/workstation/acp/processor/updates.js +390 -0
  113. package/dist/workstation/acp/processorSession.js +1348 -0
  114. package/dist/workstation/acp/registry/catalog.js +189 -41
  115. package/dist/workstation/acp/registry/launch.js +76 -119
  116. package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
  117. package/dist/workstation/acp/steering.js +58 -0
  118. package/dist/workstation/acp/switching.js +111 -0
  119. package/dist/workstation/acp/terminalLogin.js +16 -42
  120. package/dist/workstation/acp/terminalLoginSpec.js +41 -0
  121. package/dist/workstation/acp/test/adapterFixtures.js +256 -0
  122. package/dist/workstation/acp/test/daemonLife.js +106 -0
  123. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  124. package/dist/workstation/acp/test/keeperWorld.js +295 -0
  125. package/dist/workstation/acp/test/memoryOutbox.js +55 -0
  126. package/dist/workstation/acp/test/raw/fixtures.js +196 -0
  127. package/dist/workstation/acp/test/raw/sanitize.js +344 -0
  128. package/dist/workstation/acp/test/raw/sources.js +135 -0
  129. package/dist/workstation/acp/test/realAdapter.js +331 -0
  130. package/dist/workstation/acp/test/scriptAgent.js +214 -0
  131. package/dist/workstation/acp/test/sessionFrames.js +42 -0
  132. package/dist/workstation/acp/timedCommand.js +4 -2
  133. package/dist/workstation/acp/uvxInstall.js +72 -122
  134. package/dist/workstation/agentFlow.js +103 -0
  135. package/dist/workstation/agentSessions.js +1289 -544
  136. package/dist/workstation/channels/agent.js +7 -5
  137. package/dist/workstation/channels/computer.js +69 -21
  138. package/dist/workstation/channels/computerExecs.js +200 -0
  139. package/dist/workstation/channels/management.js +172 -8
  140. package/dist/workstation/channels/url.js +1 -1
  141. package/dist/workstation/config.js +16 -4
  142. package/dist/workstation/connection/encryptedChannel.js +4 -1
  143. package/dist/workstation/control/bindingStore.js +200 -0
  144. package/dist/workstation/control/commands.js +124 -0
  145. package/dist/workstation/control/daemonIdentity.js +53 -0
  146. package/dist/workstation/control/enrollment.js +106 -0
  147. package/dist/workstation/control/inspect.js +71 -0
  148. package/dist/workstation/control/locks.js +63 -0
  149. package/dist/workstation/control/protocol.js +40 -0
  150. package/dist/workstation/control/restartGuard.js +46 -0
  151. package/dist/workstation/control/runtimeClient.js +97 -0
  152. package/dist/workstation/control/runtimeHost.js +148 -0
  153. package/dist/workstation/control/serverIdentity.js +35 -0
  154. package/dist/workstation/daemon.js +393 -12
  155. package/dist/workstation/daemonctl.js +103 -14
  156. package/dist/workstation/desktopControl/audio.js +99 -0
  157. package/dist/workstation/desktopControl/capabilities.js +92 -0
  158. package/dist/workstation/desktopControl/captureModes.js +9 -0
  159. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  160. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  161. package/dist/workstation/desktopControl/helperProcess.js +353 -0
  162. package/dist/workstation/desktopControl/lobby.js +129 -0
  163. package/dist/workstation/desktopControl/macProvider.js +155 -0
  164. package/dist/workstation/desktopControl/media.js +331 -0
  165. package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
  166. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  167. package/dist/workstation/desktopControl/overview.js +58 -0
  168. package/dist/workstation/desktopControl/protocol.js +302 -0
  169. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  170. package/dist/workstation/desktopControl/rateController.js +226 -0
  171. package/dist/workstation/desktopControl/supervisor.js +1681 -0
  172. package/dist/workstation/desktopControl/transport.js +27 -0
  173. package/dist/workstation/desktopControl/wallpaper.js +78 -0
  174. package/dist/workstation/exec/attach.js +126 -0
  175. package/dist/workstation/exec/command.js +40 -0
  176. package/dist/workstation/exec/durable.js +517 -0
  177. package/dist/workstation/exec/fallback.js +106 -0
  178. package/dist/workstation/exec/policy.js +837 -0
  179. package/dist/workstation/exec/shaping.js +211 -0
  180. package/dist/workstation/exec.js +3 -112
  181. package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
  182. package/dist/workstation/files/adapters/toolRequests.js +121 -0
  183. package/dist/workstation/files/client.js +455 -0
  184. package/dist/workstation/files/credentials.js +142 -0
  185. package/dist/workstation/files/errors.js +28 -0
  186. package/dist/workstation/files/fence.js +350 -0
  187. package/dist/workstation/files/find/fileIndex.js +153 -0
  188. package/dist/workstation/files/find/fuzzy.js +155 -0
  189. package/dist/workstation/files/host.js +122 -0
  190. package/dist/workstation/files/hostProtocol.js +2 -0
  191. package/dist/workstation/files/media/cache.js +620 -0
  192. package/dist/workstation/files/media/encoding.js +308 -0
  193. package/dist/workstation/files/media/estimator.js +100 -0
  194. package/dist/workstation/files/media/media.js +325 -0
  195. package/dist/workstation/files/media/packedAudio.js +23 -0
  196. package/dist/workstation/files/media/scheduler.js +551 -0
  197. package/dist/workstation/files/media/types.js +5 -0
  198. package/dist/workstation/files/scope.js +45 -0
  199. package/dist/workstation/files/structured.js +284 -0
  200. package/dist/workstation/{fileOps.js → files/text.js} +152 -39
  201. package/dist/workstation/files/uploads.js +351 -0
  202. package/dist/workstation/journal.js +65 -236
  203. package/dist/workstation/keepers/acp/client.js +101 -0
  204. package/dist/workstation/keepers/acp/envelope.js +62 -0
  205. package/dist/workstation/keepers/acp/host.js +419 -0
  206. package/dist/workstation/keepers/acp/main.js +18 -0
  207. package/dist/workstation/keepers/acp/memoryStream.js +121 -0
  208. package/dist/workstation/keepers/acp/params.js +137 -0
  209. package/dist/workstation/keepers/acp/protocol.js +66 -0
  210. package/dist/workstation/keepers/acp/recover.js +120 -0
  211. package/dist/workstation/keepers/acp/session.js +584 -0
  212. package/dist/workstation/keepers/acp/stderr.js +154 -0
  213. package/dist/workstation/keepers/kit/activity.js +103 -0
  214. package/dist/workstation/keepers/kit/bootstrap.js +164 -0
  215. package/dist/workstation/keepers/kit/buildVersion.js +51 -0
  216. package/dist/workstation/keepers/kit/bundle.js +124 -0
  217. package/dist/workstation/keepers/kit/client.js +432 -0
  218. package/dist/workstation/keepers/kit/identity.js +21 -0
  219. package/dist/workstation/keepers/kit/ipc.js +112 -0
  220. package/dist/workstation/keepers/kit/launchConfig.js +8 -0
  221. package/dist/workstation/keepers/kit/launcher.js +289 -0
  222. package/dist/workstation/keepers/kit/lock.js +123 -0
  223. package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
  224. package/dist/workstation/keepers/kit/paths.js +76 -0
  225. package/dist/workstation/keepers/kit/protocol.js +43 -0
  226. package/dist/workstation/keepers/kit/server.js +518 -0
  227. package/dist/workstation/keepers/kit/streamLog.js +697 -0
  228. package/dist/workstation/keepers/legacy.js +296 -0
  229. package/dist/workstation/keepers/policy.js +576 -0
  230. package/dist/workstation/keepers/stop.js +34 -0
  231. package/dist/workstation/keepers/terminal/client.js +213 -0
  232. package/dist/workstation/keepers/terminal/exec.js +981 -0
  233. package/dist/workstation/keepers/terminal/generations.js +129 -0
  234. package/dist/workstation/keepers/terminal/keeper.js +62 -0
  235. package/dist/workstation/keepers/terminal/main.js +43 -0
  236. package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
  237. package/dist/workstation/keepers/terminal/params.js +27 -0
  238. package/dist/workstation/keepers/terminal/protocol.js +56 -0
  239. package/dist/workstation/keepers/terminal/pty.js +330 -0
  240. package/dist/workstation/managedInstall.js +57 -0
  241. package/dist/workstation/protocol/accountLimits.js +47 -0
  242. package/dist/workstation/protocol/agent.js +50 -19
  243. package/dist/workstation/protocol/common.js +30 -0
  244. package/dist/workstation/protocol/computer.js +15 -4
  245. package/dist/workstation/protocol/management.js +141 -4
  246. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  247. package/dist/workstation/rd/artifact.js +119 -0
  248. package/dist/workstation/rd/control.js +119 -0
  249. package/dist/workstation/rd/display.js +29 -0
  250. package/dist/workstation/rd/provider.js +222 -0
  251. package/dist/workstation/rd/sessions.js +391 -0
  252. package/dist/workstation/rd/state.js +45 -0
  253. package/dist/workstation/rd/update.js +171 -0
  254. package/dist/workstation/rd/upkeep.js +38 -0
  255. package/dist/workstation/register.js +27 -37
  256. package/dist/workstation/service/installer.js +104 -13
  257. package/dist/workstation/service/launchd.js +1 -0
  258. package/dist/workstation/storageMetrics.js +118 -0
  259. package/dist/workstation/systemMetrics.js +154 -93
  260. package/dist/workstation/toolchain.js +94 -0
  261. package/dist/workstation/tools/browser.js +28 -4
  262. package/dist/workstation/tools/client.js +9 -1
  263. package/dist/workstation/tools/info.js +50 -0
  264. package/dist/workstation/tools/keeperProcess.js +15 -5
  265. package/dist/workstation/tools/router.js +55 -0
  266. package/dist/workstation/tools/terminalRouter.js +222 -0
  267. package/dist/workstation/tools/validation.js +37 -1
  268. package/dist/workstation/update/artifact.js +2 -1
  269. package/dist/workstation/update/coordinator.js +3 -0
  270. package/dist/workstation/update/globalInstall.js +116 -0
  271. package/dist/workstation/update/npmInstaller.js +49 -23
  272. package/dist/workstation/update/ptyPreflight.js +21 -0
  273. package/dist/workstation/update/rdRollback.js +145 -0
  274. package/dist/workstation/update/runUpdate.js +3 -2
  275. package/dist/workstation/update/taskRegistry.js +105 -11
  276. package/dist/workstation/update/watchdog.js +16790 -334
  277. package/package.json +11 -8
  278. package/skills/axom/SKILL.md +11 -11
  279. package/skills/axom-definition/SKILL.md +3 -3
  280. package/skills/axom-platform/SKILL.md +3 -3
  281. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  282. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -49
  283. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  284. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  285. package/dist/commands/guide.js +0 -38
  286. package/dist/workstation/acp/acpSession.js +0 -1475
  287. package/dist/workstation/acp/contract.js +0 -499
  288. package/dist/workstation/acp/delegationMcp.js +0 -24
  289. package/dist/workstation/acp/draftInterceptor.js +0 -163
  290. package/dist/workstation/acp/qoderStartup.js +0 -45
  291. package/dist/workstation/acp/sessionScope.js +0 -19
  292. package/dist/workstation/filePaths.js +0 -174
  293. package/dist/workstation/fileWorker.js +0 -81
  294. package/dist/workstation/fileWorkerClient.js +0 -121
  295. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -106
  296. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -262
  297. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  298. package/dist/workstation/qoderAdapter/contract.js +0 -78
  299. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -30
  300. package/dist/workstation/qoderAdapter/history.js +0 -87
  301. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  302. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  303. package/dist/workstation/qoderAdapter/main.js +0 -93
  304. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  305. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  306. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  307. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  308. package/dist/workstation/qoderAdapter/profile.js +0 -93
  309. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -49
  310. package/dist/workstation/qoderAdapter/session.js +0 -319
  311. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  312. package/dist/workstation/qoderAdapter/subwork.js +0 -77
  313. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  314. package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
  315. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  316. package/dist/workstation/qoderAdapter/usage.js +0 -37
  317. package/dist/workstation/tools/core.js +0 -50
  318. package/dist/workstation/tools/files.js +0 -525
  319. package/dist/workstation/tools/terminal.js +0 -214
  320. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
@@ -1,499 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { AgentSpawnError } from './acpClient.js';
3
- import { AGENT_FRAME_SHAPES, decodeAgentFrame, encodeAgentFrame } from '../protocol/agent.js';
4
- import { validateWireEvent } from './validate.js';
5
- /**
6
- * Default wait for output, deliberately under vitest's 5s default test timeout: whichever fires
7
- * first writes the failure message, and "timed out waiting for <what>" is far more useful than
8
- * vitest's generic test timeout.
9
- */
10
- export const DEFAULT_CONTRACT_TIMEOUT_MS = 4_000;
11
- /** Multiplier on the measured turn duration used as the quiet window in the buffering check. */
12
- const BUFFERING_QUIET_FACTOR = 3;
13
- /** Floor for that window, so a synchronous sample (turn duration ≈ 0) still gets a real one. */
14
- const BUFFERING_QUIET_FLOOR_MS = 25;
15
- const TURN_STOP_REASONS = new Set([
16
- 'end_turn', 'max_tokens', 'max_turns', 'refusal', 'interrupted', 'superseded', 'expired', 'orphaned',
17
- ]);
18
- function turnResultViolation(result) {
19
- if (!['completed', 'interrupted', 'failed'].includes(result.status)) {
20
- return `turn result has invalid status '${String(result.status)}'`;
21
- }
22
- if (result.stopReason !== undefined && !TURN_STOP_REASONS.has(result.stopReason)) {
23
- return `turn result has invalid stopReason '${String(result.stopReason)}'`;
24
- }
25
- return null;
26
- }
27
- /** Registers both sinks the way `agentSessions.ts` does: `onEvent` first, then `onTurnDone`. */
28
- function record(session) {
29
- const events = [];
30
- const turns = [];
31
- session.onEvent((e) => events.push(e));
32
- session.onTurnDone((r) => turns.push(r));
33
- return { events, turns };
34
- }
35
- function timeoutOf(hooks) {
36
- return hooks.timeoutMs ?? DEFAULT_CONTRACT_TIMEOUT_MS;
37
- }
38
- function sleep(ms) {
39
- return new Promise((resolve) => setTimeout(resolve, ms));
40
- }
41
- async function waitFor(hooks, what, predicate) {
42
- const deadline = Date.now() + timeoutOf(hooks);
43
- while (!predicate()) {
44
- if (Date.now() >= deadline)
45
- throw new Error(`timed out waiting for ${what}`);
46
- await sleep(5);
47
- }
48
- }
49
- /**
50
- * Opens a session, runs the body, and always disposes.
51
- *
52
- * `session.close()` is not called here: it is clause 6's own subject, and a session that cannot
53
- * close should fail that check alone rather than smear an error across every other one. `dispose()`
54
- * is what actually reclaims the subprocess, and its failures are swallowed for the same reason.
55
- */
56
- async function withSession(hooks, open, body) {
57
- const target = await open();
58
- try {
59
- await body(target.session);
60
- }
61
- finally {
62
- try {
63
- await target.dispose();
64
- }
65
- catch {
66
- // teardown is not any clause's subject
67
- }
68
- }
69
- }
70
- function requireHook(hooks, name) {
71
- const hook = hooks[name];
72
- // Only reachable if a caller invoked a gated check directly, or the skip branch in
73
- // runAcpContract was removed. Throwing keeps that from reading as a pass.
74
- if (!hook)
75
- throw new Error(`the '${name}' hook is required for this check`);
76
- return hook;
77
- }
78
- function isPermissionCard(e) {
79
- return e.eventName === 'permission_request';
80
- }
81
- /** Names an event for a failure message without trusting it to be well-formed. */
82
- function nameOf(e) {
83
- try {
84
- return String(e?.eventName);
85
- }
86
- catch {
87
- return '<uninspectable>';
88
- }
89
- }
90
- function describeRejection(what, e) {
91
- const rejection = validateWireEvent(e);
92
- if (!rejection)
93
- return null;
94
- return `${what} '${nameOf(e)}' violates the wire contract: ${rejection.reason}${rejection.detail ? ` (${rejection.detail})` : ''}`;
95
- }
96
- // --- the checks ---------------------------------------------------------------------------------
97
- /**
98
- * Every clause, in the deleted kit's own order so the two can be read side by side.
99
- *
100
- * Two of that kit's ten checks are gone, and one is here with a different assertion:
101
- *
102
- * - `probe-total` (clause 1, "`probe()` reports availability instead of throwing", plus
103
- * `AgentBridge.type` being a non-empty string) is **dropped**. Neither subject exists: ACP has no
104
- * `probe`, and an agent's identity is now a registry id the client is handed rather than a string
105
- * a bridge declares about itself. More than that, `AcpAgents.verify` deliberately *does* throw
106
- * when the agent cannot be launched — the clause is not merely unimplemented, it is inverted on
107
- * purpose (see `acpAgents.ts`'s "Two outcomes, and neither of them is 'not authenticated'").
108
- * - `probe-reports-unavailable` (clause 1) survives as {@link unavailableAgentIsDiagnosable}, with a
109
- * **new assertion**. The clause it encoded — an absent binary must stay distinguishable from a
110
- * transient failure, because nothing above this layer can tell them apart — still holds exactly.
111
- * The mechanism inverted: `{available: false}` became a typed `AgentSpawnError`, and it is that
112
- * typing, not a boolean, that keeps "not installed" from collapsing into a generic failure.
113
- */
114
- export const acpContractChecks = [
115
- {
116
- id: 'unavailable-agent-is-diagnosable',
117
- clause: 1,
118
- title: 'an agent that is not installed fails with a typed, actionable error',
119
- requires: 'connectUnavailableAgent',
120
- run: async (hooks) => {
121
- const connect = requireHook(hooks, 'connectUnavailableAgent');
122
- let outcome;
123
- try {
124
- outcome = { ok: true, value: await connect() };
125
- }
126
- catch (error) {
127
- outcome = { ok: false, error };
128
- }
129
- // Resolving is the failure mode this catches, and it is the subtle one: a client that reports
130
- // a missing agent as connected sends every later failure to the wrong layer, and the user gets
131
- // "did not open a session" for an agent that was never on the machine.
132
- expect(outcome.ok, 'connecting to an agent that is not installed must fail, not resolve').toBe(false);
133
- const error = outcome.error;
134
- // Typed, not merely thrown. `AcpAgents.verify` catches around `AcpSession.open` and turns
135
- // failures into an inconclusive `sessionError`; only the launch failure being its own class
136
- // keeps "not installed" from being answered as "installed, but something went wrong".
137
- expect(error instanceof AgentSpawnError, `a not-installed agent must reject with AgentSpawnError, got ${error instanceof Error ? error.constructor.name : typeof error}: ${String(error)}`).toBe(true);
138
- expect(String(error.message).length, 'the rejection must carry a message a user can act on').toBeGreaterThan(0);
139
- },
140
- },
141
- {
142
- id: 'pre-registration-buffering',
143
- clause: 2,
144
- title: 'events produced before onEvent/onTurnDone are buffered and flushed on registration',
145
- requires: null,
146
- run: async (hooks) => {
147
- // `agentSessions.ts` registers its sinks immediately *after* the session resolves, so anything
148
- // produced during or right after open lands with no callback attached.
149
- //
150
- // Driving-then-registering is not on its own enough to prove buffering: `driveOneTurn`
151
- // resolves long before the agent says anything, so registration wins the race and the events
152
- // arrive live. The check would then pass for a client that buffers nothing at all.
153
- //
154
- // So: measure how long a turn takes when observed live, then drive a second turn with no sink
155
- // attached and wait past that duration before registering. By then the turn is over. Anything
156
- // that still arrives was held; an implementation that drops has nothing left to deliver and
157
- // times out.
158
- let turnMs = 0;
159
- await withSession(hooks, hooks.openSession, async (session) => {
160
- const seen = record(session);
161
- const started = Date.now();
162
- await hooks.driveOneTurn(session);
163
- await waitFor(hooks, 'a baseline turn to produce an event (with the sinks already registered)', () => seen.events.length > 0);
164
- await waitFor(hooks, 'a baseline turn to finish (with the sinks already registered)', () => seen.turns.length > 0);
165
- turnMs = Date.now() - started;
166
- });
167
- const quietMs = Math.max(BUFFERING_QUIET_FLOOR_MS, turnMs * BUFFERING_QUIET_FACTOR);
168
- if (quietMs > timeoutOf(hooks)) {
169
- // Deliberately loud rather than clamped to the timeout. Clamping still "works" — the
170
- // baseline finished inside timeoutMs, so the window would stay longer than a turn — but it
171
- // shrinks the margin towards 1x precisely when turns are slow, and says nothing while doing
172
- // it. That is a silent degradation of the check itself, which is what this refuses.
173
- throw new Error(`a baseline turn took ${turnMs}ms, so this check needs a ${quietMs}ms quiet window, but ` +
174
- `timeoutMs is ${timeoutOf(hooks)}ms. Raise timeoutMs above ${quietMs} (and vitest's own ` +
175
- `testTimeout past it) so the buffering check has a real window rather than one barely ` +
176
- `longer than a turn.`);
177
- }
178
- const dropped = (what) => `${what} to be flushed on registration — the turn was driven and left to run for ${quietMs}ms ` +
179
- `(a live turn took ${turnMs}ms) with no sink attached, so an implementation that drops instead ` +
180
- `of buffering has already discarded it`;
181
- await withSession(hooks, hooks.openSession, async (session) => {
182
- await hooks.driveOneTurn(session);
183
- await sleep(quietMs);
184
- const seen = record(session);
185
- await waitFor(hooks, dropped('events produced before registration'), () => seen.events.length > 0);
186
- await waitFor(hooks, dropped('the turn result produced before registration'), () => seen.turns.length > 0);
187
- });
188
- },
189
- },
190
- {
191
- id: 'events-valid',
192
- clause: 3,
193
- title: 'every emitted event passes validateWireEvent',
194
- requires: null,
195
- run: async (hooks) => {
196
- await withSession(hooks, hooks.openSession, async (session) => {
197
- const seen = record(session);
198
- await hooks.driveOneTurn(session);
199
- await waitFor(hooks, 'the driven turn to produce at least one event', () => seen.events.length > 0);
200
- await waitFor(hooks, 'the driven turn to finish', () => seen.turns.length > 0);
201
- for (const e of seen.events) {
202
- // Encoded as a string so the assertion message names the offending event and reason;
203
- // a bare `toBeNull()` on the rejection object reads as "expected {…} to be null".
204
- expect(describeRejection('event', e)).toBeNull();
205
- }
206
- for (const turn of seen.turns) {
207
- expect(turnResultViolation(turn)).toBeNull();
208
- }
209
- });
210
- },
211
- },
212
- {
213
- id: 'no-unknown-request-events',
214
- clause: 3,
215
- title: 'no request-shaped event other than permission_request is emitted',
216
- requires: null,
217
- run: async (hooks) => {
218
- // Split out of `events-valid` on purpose, even though validateWireEvent rejects these too:
219
- // this is the clause an implementer is most likely to trip while doing something reasonable.
220
- // Under ACP the temptation is concrete and sitting in the code already — `permission.ts`
221
- // declines every `elicitation/create`, and "just emit an `elicitation_request`" is the
222
- // obvious-looking next step. Since Plan B it is *more* tempting, not less: the platform now
223
- // owns that event name for its own agents' questions. It is still unanswerable from a bridge —
224
- // `TimelineEventWire.decode` has no branch for it, so the server drops the event and advances
225
- // its watermark past it, it can never even be replayed, and the agent waits forever. A named
226
- // check says that in the failure message instead of leaving it inside a generic one.
227
- await withSession(hooks, hooks.openSession, async (session) => {
228
- const seen = record(session);
229
- await hooks.driveOneTurn(session);
230
- await waitFor(hooks, 'the driven turn to produce at least one event', () => seen.events.length > 0);
231
- await waitFor(hooks, 'the driven turn to finish', () => seen.turns.length > 0);
232
- const offenders = seen.events
233
- .map(nameOf)
234
- .filter((name) => (name.endsWith('_request') || name.endsWith('_requested')) && name !== 'permission_request');
235
- expect(offenders, 'only permission_request may carry a request suffix — the platform has no way to answer ' +
236
- 'any other request-shaped event, and the server drops it. `_requested` is the retired ' +
237
- 'spelling and is caught here too, so an unupgraded bridge fails loudly rather than ' +
238
- 'having its approval cards silently land as generic items').toEqual([]);
239
- });
240
- },
241
- },
242
- {
243
- id: 'permission-card-answerable',
244
- clause: 3,
245
- title: 'permission_request cards are well-formed and answerable',
246
- requires: 'drivePermission',
247
- run: async (hooks) => {
248
- const drivePermission = requireHook(hooks, 'drivePermission');
249
- await withSession(hooks, hooks.openPermissionSession ?? hooks.openSession, async (session) => {
250
- const seen = record(session);
251
- await drivePermission(session);
252
- await waitFor(hooks, 'a permission_request event', () => seen.events.some(isPermissionCard));
253
- // An option-less card is the one that hurts: the session parks on a prompt the user has no
254
- // button to answer, and Stop becomes the only way out. Under ACP the options are the
255
- // *agent's* own (`permission.ts` passes them through rather than inventing a vocabulary), so
256
- // this clause is now a statement about what this client refuses to relay.
257
- for (const card of seen.events.filter(isPermissionCard)) {
258
- expect(describeRejection('permission card', card)).toBeNull();
259
- }
260
- });
261
- },
262
- },
263
- {
264
- id: 'stale-answer-tolerated',
265
- clause: 4,
266
- title: 'answer() tolerates an unknown requestId',
267
- requires: null,
268
- run: async (hooks) => {
269
- await withSession(hooks, hooks.openSession, async (session) => {
270
- const seen = record(session);
271
- // The server synthesizes a deny for the outstanding card when the user sends a plain
272
- // message instead of answering it — by then the client may have forgotten the id entirely.
273
- expect(() => session.answer('contract-never-issued-request-id', 'deny'), 'answer() with an unknown requestId must not throw').not.toThrow();
274
- // …and must not have wedged the session either.
275
- await hooks.driveOneTurn(session);
276
- await waitFor(hooks, 'the session to keep working after a stale answer', () => seen.turns.length > 0);
277
- });
278
- },
279
- },
280
- {
281
- id: 'superseded-answer-tolerated',
282
- clause: 4,
283
- title: 'answer() tolerates a requestId that was already answered',
284
- requires: 'drivePermission',
285
- run: async (hooks) => {
286
- const drivePermission = requireHook(hooks, 'drivePermission');
287
- await withSession(hooks, hooks.openPermissionSession ?? hooks.openSession, async (session) => {
288
- const seen = record(session);
289
- await drivePermission(session);
290
- await waitFor(hooks, 'a permission_request event', () => seen.events.some(isPermissionCard));
291
- const card = seen.events.find(isPermissionCard);
292
- const requestId = String(card?.payload?.requestId ?? '');
293
- expect(requestId, 'permission_request must carry a requestId').not.toBe('');
294
- const options = Array.isArray(card?.payload?.options) ? card.payload.options : [];
295
- const outcome = typeof options[0]?.optionId === 'string' ? String(options[0].optionId) : 'deny';
296
- expect(() => session.answer(requestId, outcome), 'answering an outstanding card must not throw').not.toThrow();
297
- // The same id a second time: the card is settled and the client has dropped it, but the
298
- // server can still send a synthesized deny for it.
299
- expect(() => session.answer(requestId, 'deny'), 'answer() for an already-settled requestId must not throw').not.toThrow();
300
- });
301
- },
302
- },
303
- {
304
- id: 'interrupt-ends-turn',
305
- clause: 5,
306
- title: "interrupt() ends the turn with status 'interrupted'",
307
- // The deleted per-bridge suite's equivalent clause ran unconditionally, against every bridge. It
308
- // now runs only when a hook can force a real in-flight turn, and `realAdapterHooks()` (this
309
- // file's real-adapter describe block, below) deliberately doesn't provide `openHangingSession` —
310
- // see its comment — so against a real ACP adapter this clause's coverage is not reduced, it is
311
- // zero (review finding, task-11 fix round 1, finding 3).
312
- requires: 'openHangingSession',
313
- run: async (hooks) => {
314
- const openHanging = requireHook(hooks, 'openHangingSession');
315
- await withSession(hooks, openHanging, async (session) => {
316
- const seen = record(session);
317
- // Driven with prompt() against a session whose turn will not finish on its own: a turn that
318
- // already completed cannot be interrupted, so against an ordinary session the check would
319
- // prove nothing. The assertion is on the *last* result, so an early 'completed' is a failure
320
- // rather than something the check quietly accepts.
321
- session.prompt([{ type: 'text', text: 'contract: start a turn that will be interrupted' }]);
322
- await session.interrupt();
323
- await waitFor(hooks, 'a turn result after interrupt()', () => seen.turns.length > 0);
324
- const last = seen.turns[seen.turns.length - 1];
325
- expect(last?.status, 'the turn after interrupt() must end as interrupted').toBe('interrupted');
326
- });
327
- },
328
- },
329
- {
330
- id: 'close-idempotent',
331
- clause: 6,
332
- title: 'close() is idempotent',
333
- requires: null,
334
- run: async (hooks) => {
335
- const target = await hooks.openSession();
336
- try {
337
- await target.session.close();
338
- // `agentSessions.ts` can close a session a transport drop already tore down; a second close
339
- // must be a no-op, not a rejection that surfaces as a spurious daemon error.
340
- await target.session.close();
341
- }
342
- finally {
343
- await target.dispose().catch(() => undefined);
344
- }
345
- },
346
- },
347
- {
348
- id: 'every-prompt-gets-a-turn-result',
349
- clause: 7,
350
- title: 'two overlapping prompts each report their own terminal turn result',
351
- requires: null,
352
- run: async (hooks) => {
353
- // New in ACP, and pinning a real defect rather than a hypothetical one.
354
- //
355
- // `AcpSession.prompt`'s own doc comment lists an overlapping call as an expected case, and
356
- // ACP itself allows it — the SDK's `ActiveSession.prompt` enqueues a `stop` message per call,
357
- // so two prompts really do produce two completions on the read queue. Everything downstream
358
- // is built on one result per prompt: each `prompt()` call pushes its own entry onto
359
- // `AcpSession.inFlightTurns`, `finishTurn` pops exactly one entry per completion it sees (not
360
- // "the" entry, "an" entry — see its own doc), and `agentSessions.ts`'s `onAgentTurnDone`
361
- // fires once per `finishTurn` call and emits one `agent_turn_done` frame each time (2026-09-21
362
- // rewrite; there is no longer a `tasks.agentTurnStarted`/`agentTurnFinished` bracket in the
363
- // middle of that path, but the one-result-per-prompt pairing itself is unchanged — it was
364
- // always `inFlightTurns`' push/pop symmetry, not the bracket, that guaranteed it). The
365
- // server's run stays parked until a `turn_done` arrives, and `agent_message` — the event that
366
- // flushes the server's delta buffer and attaches the turn's usage figures — is synthesized
367
- // only as a turn ends. A dropped second result therefore does not merely lose a status line:
368
- // it strands the second turn's text and parks the run indefinitely.
369
- //
370
- // Driven through `driveOneTurn` twice concurrently rather than two bare `prompt()` calls so a
371
- // real adapter's hook (which may need to say something specific to get output) is what decides
372
- // how a turn is started.
373
- await withSession(hooks, hooks.openSession, async (session) => {
374
- const seen = record(session);
375
- await Promise.all([hooks.driveOneTurn(session), hooks.driveOneTurn(session)]);
376
- await waitFor(hooks, 'both overlapping turns to report a terminal result — a second turn whose result never ' +
377
- 'arrives leaves the platform parked on a run that has already finished', () => seen.turns.length >= 2);
378
- for (const turn of seen.turns) {
379
- expect(turnResultViolation(turn)).toBeNull();
380
- }
381
- });
382
- },
383
- },
384
- ];
385
- /**
386
- * Which checks {@link runAcpContract} will run and which it will skip, for these hooks.
387
- *
388
- * Extracted so "skipped, never silently passed" is assertable. Left inline in the `describe` body,
389
- * the decision lives only in a branch whose failure mode — a case that runs an empty body and
390
- * reports green — no assertion can see.
391
- */
392
- export function acpContractRunPlan(hooks) {
393
- return acpContractChecks.map((check) => ({
394
- id: check.id,
395
- skipped: check.requires !== null && hooks[check.requires] === undefined,
396
- }));
397
- }
398
- /**
399
- * Clause 8 — every field the agent channel treats as required must actually be enforced by the
400
- * decoder, for every frame type.
401
- *
402
- * ### Why this is one general clause rather than an assertion per field
403
- *
404
- * Task 10a shipped `agent_authenticate` with no guard on `methodId`. The frame decoded cleanly, and
405
- * `AcpAuth.authenticate(undefined)` ran all the way to the agent. The follow-up review then
406
- * enumerated every field of the two new frames by hand and found nothing else — a real answer with
407
- * a one-release shelf life, because the next frame added gets no such treatment.
408
- *
409
- * ### Where "required" comes from, and why it must not come from the guard table
410
- *
411
- * The obvious implementation — read the field names off `AGENT_FRAME_SHAPES` and check each one is
412
- * enforced — is circular, and measurably so: deleting `methodId` from that table leaves such a check
413
- * green, because the check stops looking for exactly the field the guard stopped guarding. That is
414
- * the original bug reproduced, not caught.
415
- *
416
- * So required-ness is taken from a **second, independent declaration**: the `AgentFrame` union.
417
- * `samples` is typed as `AgentFrame`, so TypeScript will not accept an entry missing a non-optional
418
- * field, and the samples are required to carry *only* those fields. A sample's own keys are therefore
419
- * the union's mandatory field list, arrived at without consulting the guard table at all. This
420
- * function then asserts the two agree — set equality, so a guard the table forgot and a field a
421
- * sample invented are both failures — before asserting the decoder actually rejects each one.
422
- *
423
- * Hook-free, unlike every clause above, which is why it is its own exported function rather than an
424
- * entry in {@link acpContractChecks}: it has no session to drive. The samples come from the caller
425
- * because a valid instance of each frame is test data, not something a type can synthesize.
426
- */
427
- export function assertRequiredFieldsEnforced(samples,
428
- /**
429
- * The decoder under test. Defaults to the real one; parameterized only so `contract.test.ts` can
430
- * hand in a deliberately lenient decoder and assert this function rejects it. Without that, "the
431
- * clause holds" and "the clause is unfalsifiable" look identical from the outside.
432
- */
433
- decode = decodeAgentFrame) {
434
- // Coverage first: without this, a frame type with no sample contributes no assertions at all and
435
- // the whole clause passes vacuously for it.
436
- expect(Object.keys(samples).sort(), 'every agent frame type needs a valid sample here, or its required fields go unchecked').toEqual(Object.keys(AGENT_FRAME_SHAPES).sort());
437
- // Every sample is vetted before any of them is mutilated, rather than vetting and mutilating one
438
- // frame at a time. Interleaved, a rotten sample late in the map would only be reported *after*
439
- // some earlier frame's deletion had already failed for an unrelated reason, and the message a
440
- // reader gets would name the wrong frame.
441
- const requiredByType = new Map();
442
- for (const [type, sample] of Object.entries(samples)) {
443
- expect(sample.t, `the sample filed under '${type}' must be a ${type} frame`).toBe(type);
444
- // The sample itself has to decode, or every deletion below "fails" for the wrong reason and the
445
- // clause would pass while proving nothing.
446
- expect(() => decode(encodeAgentFrame(sample)), `the sample ${type} frame must itself decode`).not.toThrow();
447
- // The union's mandatory fields, read off an instance the type checker vouched for. `t` is the
448
- // discriminator rather than a payload field, and `decodeChannelFrame` rejects an unknown one
449
- // before any guard runs, so it is not this clause's subject.
450
- const fromUnion = Object.keys(sample).filter((key) => key !== 't').sort();
451
- expect(fromUnion.length, `the sample ${type} frame carries no fields, so it checks nothing`).toBeGreaterThan(0);
452
- expect(
453
- // Cast, not a narrowed lookup: `type` is `samples`'s own `string` key, but the coverage
454
- // assertion just above (line 608-610) already proved it is one of `AGENT_FRAME_SHAPES`'s keys
455
- // before this loop ever runs.
456
- Object.keys(AGENT_FRAME_SHAPES[type].required).sort(), `${type}'s guard table and its AgentFrame union member disagree about which fields are ` +
457
- 'required. Either the table is missing a guard for a field the type says is mandatory ' +
458
- '(the shape `agent_authenticate.methodId` shipped in), or this sample carries an optional ' +
459
- 'field it should not — samples here must carry required fields and nothing else').toEqual(fromUnion);
460
- requiredByType.set(type, fromUnion);
461
- }
462
- for (const [type, sample] of Object.entries(samples)) {
463
- for (const field of requiredByType.get(type)) {
464
- const mutilated = { ...sample };
465
- delete mutilated[field];
466
- expect(() => decode(JSON.stringify(mutilated)), `${type} must be rejected when required field '${field}' is missing — the Kotlin twin cannot ` +
467
- 'decode it, and a frame that passes here and fails there is a silent drop on the far side').toThrow();
468
- }
469
- }
470
- }
471
- /**
472
- * Run the whole contract suite. Call at the top level of a test file:
473
- *
474
- * ```ts
475
- * runAcpContract('ACP client over the in-repo fake agent', { openSession: …, driveOneTurn: … });
476
- * ```
477
- *
478
- * Cases needing an optional hook are skipped — visibly, as skipped — when it is absent.
479
- */
480
- export function runAcpContract(name, hooks) {
481
- const plan = new Map(acpContractRunPlan(hooks).map((entry) => [entry.id, entry.skipped]));
482
- describe(name, () => {
483
- for (const check of acpContractChecks) {
484
- const label = `clause ${check.clause}: ${check.title}`;
485
- if (plan.get(check.id)) {
486
- // The real body, not an empty one. Never executed while skipped — but the instant `.skip`
487
- // comes off, the check runs, hits its missing hook and goes red. An empty body would go
488
- // green and leave the run plan still reporting it as skipped, so nothing would notice.
489
- it.skip(label, async () => {
490
- await check.run(hooks);
491
- }, hooks.vitestTimeoutMs);
492
- continue;
493
- }
494
- it(label, async () => {
495
- await check.run(hooks);
496
- }, hooks.vitestTimeoutMs);
497
- }
498
- });
499
- }
@@ -1,24 +0,0 @@
1
- export function buildSessionMcpServers(userServers = [], lease) {
2
- if (!lease)
3
- return [...userServers];
4
- const fail = () => { throw new Error('Delegation MCP configuration is unavailable'); };
5
- let url;
6
- try {
7
- url = new URL(lease.url);
8
- }
9
- catch {
10
- return fail();
11
- }
12
- if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash ||
13
- url.pathname !== '/mcp/delegation' || !Number.isSafeInteger(lease.expiresAtEpochMs) ||
14
- lease.expiresAtEpochMs <= Date.now() || userServers.some((server) => server.name === 'axom-delegation'))
15
- return fail();
16
- const headers = Object.entries(lease.headers);
17
- if (headers.length !== 1 || headers[0][0].toLowerCase() !== 'authorization' ||
18
- !/^Bearer [A-Za-z0-9_-]+$/.test(headers[0][1]))
19
- return fail();
20
- return [...userServers, {
21
- type: 'http', name: 'axom-delegation', url: url.toString(),
22
- headers: headers.map(([name, value]) => ({ name, value })),
23
- }];
24
- }