@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
@@ -113,6 +113,12 @@
113
113
  // // with this message instead of succeeding — lets a
114
114
  // // test prove a real agent-side rejection surfaces as
115
115
  // // a rejected promise
116
+ // authenticateNotifications?: Array<{method: string, params: unknown}>,
117
+ // // sent as client notifications at the top of the
118
+ // // `authenticate` handler, before anything else —
119
+ // // models an agent that pushes a login URL as an
120
+ // // extension notification (`_codebuddy.ai/authUrl`)
121
+ // // rather than eliciting or printing it
116
122
  // hangOnAuthenticate?: boolean, // if true, `authenticate` never resolves — the shape
117
123
  // // measured on a real agent that opens a browser and
118
124
  // // waits for its callback (spec §6.2: antigravity's
@@ -348,6 +354,13 @@ async function main() {
348
354
  writeFileSync(config.authenticateSink, JSON.stringify(ctx.params));
349
355
  if (config.authenticateRejectsWith)
350
356
  throw new Error(config.authenticateRejectsWith);
357
+ // Extension notifications an agent pushes while answering `authenticate` (measured on the real
358
+ // CodeBuddy adapter: `_codebuddy.ai/authUrl` with `{authUrl, provider}` — a URL channel that
359
+ // is neither `elicitation/create` nor stderr). Sent before the reply, so a client that only
360
+ // listens from the reply onward still misses them, exactly like the real one.
361
+ for (const { method, params } of config.authenticateNotifications ?? []) {
362
+ await ctx.client.notify(method, params);
363
+ }
351
364
  if (config.createElicitationDuringAuthenticate) {
352
365
  // `requestId` is filled in here rather than in the config: a login elicitation is
353
366
  // request-scoped (ACP's `ElicitationRequestScope` — "tied to a specific JSON-RPC request
@@ -0,0 +1,295 @@
1
+ /**
2
+ * The shared world of the keeper-acp end-to-end tests (Task 21's adoption tests, Task 23's fault-injection gate): a temp config dir
3
+ * with a real keeper-acp (the built bundle in `dist/keepers`), a real adapter process (`scriptAgent.ts`, driven by marker files), and
4
+ * the server's half played by {@link FakeServer} (frames applied once per seq — the server's de-duplication — plus the answers it
5
+ * recorded). A daemon life is either in-process ({@link KeeperWorld.life}: its own keeper connection, AcpAgents, manager and
6
+ * TaskRegistry; "killing" it drops the connection without any goodbye) or a real process ({@link KeeperWorld.daemonProcess}:
7
+ * `daemonLife.ts` under vite-node, killed with SIGKILL). {@link KeeperWorld.dispose} kills every keeper, adapter and daemon process it
8
+ * started, by pid.
9
+ */
10
+ import { spawn } from 'node:child_process';
11
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import { rm } from 'node:fs/promises';
13
+ import path, { join } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { expect, vi } from 'vitest';
16
+ import { killKeepersUnder, tempDir } from '../../../test/keeperKit.js';
17
+ import { AcpKeeperClient } from '../../keepers/acp/client.js';
18
+ import { computeBuildVersion } from '../../keepers/kit/buildVersion.js';
19
+ import { readKeeperManifest } from '../../keepers/kit/bundle.js';
20
+ import { keeperGen } from '../../keepers/kit/identity.js';
21
+ import { KeeperLauncher } from '../../keepers/kit/launcher.js';
22
+ import { acquireStartupLock, releaseStartupLock } from '../../keepers/kit/lock.js';
23
+ import { AgentSessionManager } from '../../agentSessions.js';
24
+ import { AGENT_WIRE_VERSION } from '../../protocol/agent.js';
25
+ import { TaskRegistry } from '../../update/taskRegistry.js';
26
+ import { AcpAgents } from '../acpAgents.js';
27
+ import { keeperGenerations } from '../processor/transport/generations.js';
28
+ import { KeeperTransport } from '../processor/transport/keeper.js';
29
+ import { LocalTransport } from '../processor/transport/local.js';
30
+ const here = path.dirname(fileURLToPath(import.meta.url));
31
+ export const packageRoot = path.join(here, '..', '..', '..', '..');
32
+ export const realDist = path.join(packageRoot, 'dist', 'keepers');
33
+ export const SCRIPT_AGENT = join(here, 'scriptAgent.ts');
34
+ export const DAEMON_LIFE = join(here, 'daemonLife.ts');
35
+ export const BOOTSTRAP = "eval(require('node:fs').readFileSync(process.argv[1], 'utf8'))";
36
+ export const touch = (dir, name) => writeFileSync(join(dir, name), '');
37
+ export const agentPid = (dir) => Number(readFileSync(join(dir, 'pid'), 'utf8'));
38
+ export const stdinLines = (dir) => existsSync(join(dir, 'stdin.jsonl')) ? readFileSync(join(dir, 'stdin.jsonl'), 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l)) : [];
39
+ export const downMethods = (records) => records.filter((r) => r.kind === 'down').map((r) => JSON.parse(r.line).method);
40
+ /** The server's half: applies each seq once, in order (bridge_seq), per ref; remembers the answers it gave (spec §5.5.4). */
41
+ export class FakeServer {
42
+ applied = new Map();
43
+ lastSeen = new Map();
44
+ received = [];
45
+ /** ref → requestId → outcome: what the user answered (the card's `answer`). */
46
+ answers = new Map();
47
+ apply = (frame) => {
48
+ this.received.push(frame);
49
+ if ((frame.t === 'agent_event' || frame.t === 'agent_turn_done') && typeof frame.seq === 'number') {
50
+ const last = this.lastSeen.get(frame.sessionRef) ?? 0;
51
+ if (frame.seq <= last)
52
+ return; // the server's de-duplication
53
+ this.lastSeen.set(frame.sessionRef, frame.seq);
54
+ const list = this.applied.get(frame.sessionRef) ?? [];
55
+ list.push(frame);
56
+ this.applied.set(frame.sessionRef, list);
57
+ }
58
+ };
59
+ timeline(ref) { return this.applied.get(ref) ?? []; }
60
+ seqs(ref) { return this.timeline(ref).map((frame) => frame.seq); }
61
+ turnDones(ref) { return this.timeline(ref).filter((frame) => frame.t === 'agent_turn_done'); }
62
+ texts(ref) {
63
+ return this.timeline(ref).filter((f) => f.t === 'agent_event' && f.eventName === 'agent_message_chunk')
64
+ .map((f) => (f.payload.content?.text ?? ''));
65
+ }
66
+ cards(ref) {
67
+ return this.timeline(ref).filter((f) => f.eventName === 'permission_request').map((f) => f.payload.requestId);
68
+ }
69
+ /** The user answers a card: recorded first (the server's item), then sent if a daemon is there. */
70
+ answer(ref, requestId, outcome) {
71
+ const answered = this.answers.get(ref) ?? new Map();
72
+ answered.set(requestId, outcome);
73
+ this.answers.set(ref, answered);
74
+ return { t: 'agent_answer', sessionRef: ref, requestId, outcome };
75
+ }
76
+ /** Task 22's resend: the answers this side recorded for cards a durable reopen still reports pending. */
77
+ resends(ref, opened) {
78
+ const answered = this.answers.get(ref);
79
+ return (opened.pendingRequestIds ?? []).filter((id) => answered?.has(id))
80
+ .map((id) => ({ t: 'agent_answer', sessionRef: ref, requestId: id, outcome: answered.get(id) }));
81
+ }
82
+ }
83
+ /** What the server's timeline says, without the ref: compared between two runs. */
84
+ export const comparable = (frames) => frames.map(({ sessionRef: _ref, ...rest }) => rest);
85
+ /** Seqs 1..n, no duplicate, no gap. */
86
+ export function expectContiguous(seqs) {
87
+ expect(seqs).toEqual(Array.from({ length: seqs.length }, (_, k) => k + 1));
88
+ }
89
+ export class KeeperWorld {
90
+ configDir;
91
+ agentDir;
92
+ launcher;
93
+ lives = [];
94
+ processes = [];
95
+ extraDists = [];
96
+ agentDirs = [];
97
+ constructor(prefix) {
98
+ this.configDir = tempDir(prefix);
99
+ this.agentDir = this.newAgentDir(`${prefix}agent-`);
100
+ this.launcher = this.launcherFor(realDist);
101
+ }
102
+ /** These integrations require keeper hosting, rather than fallback after an unrelated lock-port collision. */
103
+ static async create(prefix, includeOlderGeneration = false) {
104
+ const current = keeperGen((await readKeeperManifest(join(realDist, 'acp'))).buildVersion);
105
+ for (let attempt = 0; attempt < 100; attempt++) {
106
+ const candidate = new this(prefix);
107
+ const locks = [];
108
+ try {
109
+ const generations = [current];
110
+ if (includeOlderGeneration)
111
+ generations.push(candidate.olderGeneration().oldGen);
112
+ for (const gen of new Set(generations)) {
113
+ locks.push(await acquireStartupLock(candidate.launcher.pathsFor('acp', gen).lockPort));
114
+ }
115
+ return candidate;
116
+ }
117
+ catch (error) {
118
+ await candidate.dispose();
119
+ if (error.code !== 'EADDRINUSE')
120
+ throw error;
121
+ }
122
+ finally {
123
+ await Promise.all(locks.map(releaseStartupLock));
124
+ }
125
+ }
126
+ throw new Error('No unoccupied keeper-acp integration fixture identity');
127
+ }
128
+ launcherFor(distDir) {
129
+ return new KeeperLauncher({ configDir: this.configDir, workstationId: 'ws-t21', server: 'http://test.invalid', distDir, useSystemd: false,
130
+ startTimeoutMs: 20_000, umask: 0o022 });
131
+ }
132
+ /** Another adapter's marker directory (its pid is killed on dispose). */
133
+ newAgentDir(prefix = 'axom-t23-agent-') {
134
+ const dir = tempDir(prefix);
135
+ this.agentDirs.push(dir);
136
+ return dir;
137
+ }
138
+ scriptArgs(config, dir = this.agentDir) {
139
+ return ['-e', BOOTSTRAP, SCRIPT_AGENT, JSON.stringify({ dir, ...config })];
140
+ }
141
+ /** One daemon life: its own keeper-acp connection, AcpAgents (with the generations), manager and TaskRegistry, bound to `server`. */
142
+ life(server, over = {}) {
143
+ const use = over.launcher ?? this.launcher;
144
+ const real = new KeeperTransport(new AcpKeeperClient({ launcher: use }));
145
+ const keeper = (over.wrapKeeper ? over.wrapKeeper(real) : real);
146
+ const generations = keeperGenerations({ launcher: use, current: keeper, log: () => { } });
147
+ const local = new LocalTransport({ configDir: this.configDir });
148
+ const acp = new AcpAgents({ cacheDir: this.configDir, registry: { load: async () => [] }, transports: { keeper, local }, keeperGenerations: generations,
149
+ sessionStartTimeoutMs: 15_000, ...over.acp });
150
+ const tasks = new TaskRegistry();
151
+ const sent = [];
152
+ const manager = new AgentSessionManager(acp, null, join(this.configDir, 'agents'), tasks);
153
+ manager.bindSend((frame) => { sent.push(frame); server.apply(frame); }, AGENT_WIRE_VERSION);
154
+ let killed = false;
155
+ const result = {
156
+ keeper, generations, acp, tasks, manager, sent,
157
+ // kill -9 as keeper-acp sees it: the connection drops, nothing is detached, nothing is said.
158
+ kill() { if (killed)
159
+ return; killed = true; keeper.close(); generations.close(); void local.dispose(0); },
160
+ };
161
+ this.lives.push(result);
162
+ return result;
163
+ }
164
+ openFrame(ref, config, over = {}) {
165
+ return {
166
+ t: 'agent_open', id: `open-${ref}-${Math.random().toString(36).slice(2, 8)}`, sessionRef: ref, agentKey: 'script', bridge: 'script-acp',
167
+ cwd: this.configDir, shell: '/bin/sh', command: process.execPath, args: this.scriptArgs(config), ...over,
168
+ };
169
+ }
170
+ /** Opens `ref` on life `l` and waits for the reply. */
171
+ async openOn(l, ref, config, over = {}) {
172
+ const frame = this.openFrame(ref, config, over);
173
+ await l.manager.handleFrame(frame);
174
+ const reply = opened(l, frame.id)[0];
175
+ expect(reply, `no agent_opened for ${ref}`).toBeDefined();
176
+ expect(reply.ok, reply.error ?? '').toBe(true);
177
+ return reply;
178
+ }
179
+ /** The server reopens `ref` on life `l` after a reconnect: `agent_open` with its watermark, then Task 22's answer resend. */
180
+ async reopenOn(l, server, ref, config, over = {}) {
181
+ const reply = await this.openOn(l, ref, config, { resumeFromSeq: server.lastSeen.get(ref) ?? 0, ...over });
182
+ for (const frame of server.resends(ref, reply))
183
+ await l.manager.handleFrame(frame);
184
+ return reply;
185
+ }
186
+ /** Every raw record of `ref`, read over a connection of its own. */
187
+ async rawOf(ref, launcher = this.launcher) {
188
+ const reader = new KeeperTransport(new AcpKeeperClient({ launcher }));
189
+ try {
190
+ await reader.attach(ref);
191
+ const records = [];
192
+ const summary = (await reader.list()).find((s) => s.sessionKey === ref);
193
+ for (let at = summary.raw.first;;) {
194
+ const page = await reader.readRaw(ref, at);
195
+ if (page.length === 0)
196
+ break;
197
+ records.push(...page);
198
+ at = page[page.length - 1].o + 1;
199
+ }
200
+ return records;
201
+ }
202
+ finally {
203
+ reader.close();
204
+ }
205
+ }
206
+ /** keeper-acp's listing of `ref`, over a connection of its own. */
207
+ async summaryOf(ref) {
208
+ const reader = new KeeperTransport(new AcpKeeperClient({ launcher: this.launcher }));
209
+ try {
210
+ return (await reader.list()).find((s) => s.sessionKey === ref);
211
+ }
212
+ finally {
213
+ reader.close();
214
+ }
215
+ }
216
+ /**
217
+ * A daemon life in a process of its own (`daemonLife.ts` under vite-node): it adopts what keeper-acp holds, then handles the frames
218
+ * it is given on stdin (one JSON per line; `{cmd:'forceBegin'}` runs a force update's `TaskRegistry.stopAll()`, `{cmd:'forceStop'}`
219
+ * also the restart path's detach, and exits). Every
220
+ * frame it sends lands in a file; its stderr is kept in `<configDir>/daemon-<n>.stderr` (the sentinel grep reads it).
221
+ */
222
+ daemonProcess(faults = {}) {
223
+ const n = this.processes.length + 1;
224
+ const framesOut = join(this.configDir, `frames-daemon-${n}.jsonl`);
225
+ const stderrFile = join(this.configDir, `daemon-${n}.stderr`);
226
+ const child = spawn(join(packageRoot, 'node_modules', '.bin', 'vite-node'), [DAEMON_LIFE,
227
+ JSON.stringify({ configDir: this.configDir, distDir: realDist, framesOut, faults })], { cwd: packageRoot, stdio: ['pipe', 'ignore', 'pipe'] });
228
+ let stderr = '';
229
+ child.stderr.on('data', (chunk) => { stderr += chunk.toString('utf8'); appendFileSync(stderrFile, chunk); });
230
+ const exited = new Promise((resolve) => child.once('exit', () => resolve()));
231
+ const result = {
232
+ child,
233
+ frames: () => existsSync(framesOut) ? readFileSync(framesOut, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line)) : [],
234
+ send: (frame) => { child.stdin.write(`${JSON.stringify(frame)}\n`); },
235
+ stderr: () => stderr,
236
+ kill: async () => { if (child.exitCode === null && child.signalCode === null)
237
+ child.kill('SIGKILL'); await exited; },
238
+ exited,
239
+ };
240
+ this.processes.push(result);
241
+ return result;
242
+ }
243
+ /** Opens `ref` in a daemon process and waits for its `agent_opened{ok}` (the server never prompts before it). */
244
+ async openIn(proc, frame) {
245
+ proc.send(frame);
246
+ let reply;
247
+ await vi.waitFor(() => {
248
+ expect(proc.child.exitCode, proc.stderr()).toBeNull();
249
+ reply = proc.frames().find((f) => f.t === 'agent_opened' && f.id === frame.id);
250
+ expect(reply).toBeDefined();
251
+ }, { timeout: 45_000, interval: 100 });
252
+ expect(reply.ok, reply.error ?? '').toBe(true);
253
+ return reply;
254
+ }
255
+ /** An "older build" of keeper-acp: the same bundle with one byte more (its own buildVersion, so its own generation). */
256
+ olderGeneration() {
257
+ const oldDist = tempDir('axom-t21-old-dist-');
258
+ this.extraDists.push(oldDist);
259
+ mkdirSync(join(oldDist, 'acp'), { recursive: true });
260
+ writeFileSync(join(oldDist, 'acp', 'keeper.mjs'), `${readFileSync(join(realDist, 'acp', 'keeper.mjs'), 'utf8')}\n// an older generation\n`);
261
+ const manifest = JSON.parse(readFileSync(join(realDist, 'acp', 'manifest.json'), 'utf8'));
262
+ const oldBuild = computeBuildVersion(join(oldDist, 'acp'));
263
+ writeFileSync(join(oldDist, 'acp', 'manifest.json'), JSON.stringify({ ...manifest, buildVersion: oldBuild }));
264
+ return { oldLauncher: this.launcherFor(oldDist), oldGen: keeperGen(oldBuild) };
265
+ }
266
+ /** Kills every daemon process, life, keeper-acp generation and adapter this world started (by pid); the directories stay. */
267
+ async stop() {
268
+ for (const proc of this.processes.splice(0))
269
+ await proc.kill().catch(() => { });
270
+ for (const life of this.lives.splice(0))
271
+ life.kill();
272
+ for (const gen of await this.launcher.liveGenerations('acp').catch(() => []))
273
+ await this.launcher.stop('acp', gen, 'force').catch(() => { });
274
+ for (const dir of this.agentDirs) {
275
+ const pidFile = join(dir, 'pid');
276
+ if (existsSync(pidFile)) {
277
+ try {
278
+ process.kill(Number(readFileSync(pidFile, 'utf8')), 'SIGKILL');
279
+ }
280
+ catch { /* gone */ }
281
+ }
282
+ }
283
+ }
284
+ /** {@link stop}, then removes every directory (killing any keeper whose pid manifest is still under the config dir). */
285
+ async dispose() {
286
+ await this.stop();
287
+ for (const dist of this.extraDists.splice(0))
288
+ await rm(dist, { recursive: true, force: true });
289
+ for (const dir of this.agentDirs.splice(0))
290
+ await rm(dir, { recursive: true, force: true });
291
+ killKeepersUnder(this.configDir);
292
+ }
293
+ }
294
+ export const opened = (l, id) => l.sent.filter((frame) => frame.t === 'agent_opened' && (id === undefined || frame.id === id));
295
+ export const liveSessionOf = (l, ref) => l.manager.sessions.get(ref)?.session;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Test stand-in for a transport's outbox stream (Task 20): what `acp.outbox.append` / `acp.commit` do to one session's outbox,
3
+ * in memory — strictly increasing seqs, offsets, truncation through an offset with its `committedTail` watermark, checkpoints and
4
+ * raw truncation recorded. Kept per sessionRef by the manager tests' fake agents so a "reopen" reads back what an earlier life
5
+ * left, as `ProcessorSession.start` does from the real stream.
6
+ */
7
+ import { SessionOutbox } from '../outbox.js';
8
+ export class MemoryOutboxStream {
9
+ records = [];
10
+ next = 0;
11
+ committed = -1;
12
+ committedTail = null;
13
+ checkpoints = [];
14
+ rawTruncations = [];
15
+ commits = [];
16
+ /** Non-null: the next appends fail with it (a transport that went away). */
17
+ failAppend = null;
18
+ async append(entries) {
19
+ if (this.failAppend)
20
+ throw this.failAppend;
21
+ let last = this.records.at(-1)?.seq ?? this.committedTail?.seq ?? -Infinity;
22
+ for (const entry of entries) {
23
+ if (!(entry.seq > last))
24
+ throw new Error(`Outbox seq ${entry.seq} does not follow ${last}`);
25
+ last = entry.seq;
26
+ }
27
+ const first = this.next;
28
+ for (const entry of entries)
29
+ this.records.push({ o: this.next++, t: 0, seq: entry.seq, src: { ...entry.src }, frame: { ...entry.frame } });
30
+ return { first, last: this.next - 1 };
31
+ }
32
+ async commit(params) {
33
+ this.commits.push(params);
34
+ if (params.checkpoint)
35
+ this.checkpoints.push({ offset: params.checkpoint.offset, blob: String(params.checkpoint.blob) });
36
+ if (params.outbox) {
37
+ if (params.outbox.through >= this.next)
38
+ throw new Error('Outbox commit is beyond the outbox head');
39
+ this.committed = Math.max(this.committed, params.outbox.through);
40
+ this.committedTail = params.outbox.tail;
41
+ this.records = this.records.filter((r) => r.o > this.committed);
42
+ }
43
+ if (params.rawThrough !== undefined) {
44
+ const latest = this.checkpoints.at(-1)?.offset ?? -1;
45
+ if (params.rawThrough > latest)
46
+ throw new Error('Raw may only be truncated through the latest checkpoint');
47
+ this.rawTruncations.push(params.rawThrough);
48
+ }
49
+ return {};
50
+ }
51
+ /** What a new life reads back: the records past the watermark. */
52
+ leftovers() { return this.records.filter((r) => r.o > this.committed); }
53
+ /** A new life's {@link SessionOutbox} over this stream, as `ProcessorSession.start` builds it. */
54
+ outbox() { return new SessionOutbox(this, { leftovers: this.leftovers(), committedTail: this.committedTail }); }
55
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Real-capture fixtures (Task 17d) and the transducer that turns one into a processor raw log.
3
+ *
4
+ * ## What a fixture is
5
+ *
6
+ * A sanitised capture (`sanitize.ts`) in the kit-stream spelling: the wire between an ACP adapter and
7
+ * the client that captured it — `scripts/acp-capture.mjs` or the legacy capture harness. The `down`
8
+ * lines are **that client's**, with its own JSON-RPC ids; nothing in a fixture depends on the
9
+ * processor's id scheme, so a fixture stays valid when the processor changes.
10
+ *
11
+ * ## The transducer
12
+ *
13
+ * {@link transduce} plays the daemon against a fixture: every client decision the capture shows
14
+ * becomes the note the daemon would write (`session/new` → `agent_session_open`, `session/prompt` →
15
+ * `agent_prompt`, `session/set_mode` → `agent_set_config_option`, a permission answer →
16
+ * `agent_answer`, `session/cancel` → `agent_interrupt`, anything else → `rpc.request`), the processor
17
+ * writes its own `down` lines through the real record loop (`processor/test/harness.ts` `run`), and the
18
+ * adapter's `up` lines follow in capture order — a response's id rewritten from the capture client's
19
+ * request to the processor's. The result is the raw log keeper-acp would have recorded for the same
20
+ * conversation: the input of the determinism, batching, checkpoint and equivalence tests.
21
+ */
22
+ import { readdirSync, readFileSync } from 'node:fs';
23
+ import { join } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+ import { permissionRequestId } from '../../permission.js';
26
+ import { MemoryRawLog } from '../../processor/memoryLog.js';
27
+ import { decodeStreamRecord } from '../../processor/record.js';
28
+ import { createProcessor } from '../../processor/step.js';
29
+ import { run } from '../../processor/test/harness.js';
30
+ export const RAW_DIR = fileURLToPath(new URL('.', import.meta.url));
31
+ /** The spawn id every transduced log uses (card ids are `perm:<SPAWN_ID>:<agent id>`). */
32
+ export const SPAWN_ID = 'spawn-fixture';
33
+ /** Every committed fixture, by discovery (never a hand-kept list). */
34
+ export function fixtureNames() {
35
+ return readdirSync(RAW_DIR).filter((name) => name.endsWith('.jsonl')).sort();
36
+ }
37
+ export function readFixtureText(name) {
38
+ return readFileSync(join(RAW_DIR, name), 'utf8');
39
+ }
40
+ /** A fixture's records, decoded through the processor's own stream decoder. */
41
+ export function loadFixture(name) {
42
+ return readFixtureText(name).split('\n').filter((line) => line.trim()).map((line) => decodeStreamRecord(JSON.parse(line)));
43
+ }
44
+ const isResponse = (m) => m.method === undefined && m.id !== undefined && ('result' in m || 'error' in m);
45
+ /** Fold a capture into the processor raw log keeper-acp would have recorded for the same conversation. */
46
+ export function transduce(capture, options = {}) {
47
+ let clock = 0;
48
+ const stdin = [];
49
+ const log = new MemoryRawLog(() => clock, (line) => stdin.push(line));
50
+ const session = { log, stdin, outbox: [], blocks: new Map() };
51
+ const processor = createProcessor();
52
+ const actions = [];
53
+ /** capture request id → processor request id (both as JSON). */
54
+ const ids = new Map();
55
+ /** capture request id → method, for the agent's requests (to classify the client's answers). */
56
+ const agentRequests = new Map();
57
+ let next = 0;
58
+ let turns = 0;
59
+ /** An interrupt was decided since the last prompt: the cards it cancelled are answered by the processor itself. */
60
+ let interrupted = false;
61
+ let configs = 0;
62
+ /** The capture id of a `session/load`: its response is where the daemon closes the history window. */
63
+ let loadRequest;
64
+ const fold = () => { next = run(processor, session, next); };
65
+ /** Append a note (and any entries that must precede its fold), fold, and return the requests the processor wrote for it. */
66
+ const decide = (entry, ...with_) => {
67
+ const before = stdin.length;
68
+ log.append(entry);
69
+ for (const extra of with_)
70
+ log.append(extra);
71
+ fold();
72
+ return stdin.slice(before).map((line) => JSON.parse(line)).filter((m) => m.method !== undefined && m.id !== undefined);
73
+ };
74
+ const map = (captured, written) => {
75
+ const own = written.find((m) => m.method === captured.method);
76
+ if (!own)
77
+ throw new Error(`transduce: the processor wrote no ${captured.method} for capture request ${JSON.stringify(captured.id)}`);
78
+ ids.set(JSON.stringify(captured.id), own.id);
79
+ };
80
+ for (const record of capture) {
81
+ clock = record.t;
82
+ if (record.kind === 'proc') {
83
+ if (record.proc.type === 'spawn') {
84
+ decide({ kind: 'proc', proc: { ...record.proc, spawnId: SPAWN_ID } });
85
+ if (options.launch !== undefined)
86
+ decide({ kind: 'note', note: { type: 'agent_launch', launch: options.launch } });
87
+ }
88
+ else {
89
+ decide({ kind: 'proc', proc: record.proc });
90
+ }
91
+ }
92
+ else if (record.kind === 'err') {
93
+ decide({ kind: 'err', line: record.line });
94
+ }
95
+ else if (record.kind === 'note') {
96
+ decide({ kind: 'note', note: record.note });
97
+ }
98
+ else if (record.kind === 'up') {
99
+ let line = record.line;
100
+ let message;
101
+ try {
102
+ message = JSON.parse(line);
103
+ }
104
+ catch { /* a parse error is the processor's business */ }
105
+ if (message && isResponse(message)) {
106
+ const own = ids.get(JSON.stringify(message.id));
107
+ if (own !== undefined)
108
+ line = JSON.stringify({ ...message, id: own });
109
+ }
110
+ else if (message && message.method !== undefined && message.id !== undefined) {
111
+ agentRequests.set(JSON.stringify(message.id), message.method);
112
+ }
113
+ decide({ kind: 'up', line });
114
+ // T19: the daemon writes `historyEnd` as soon as it has seen the load's result (the open result
115
+ // the processor reports on this record) — every update after it is live, never history.
116
+ if (message && isResponse(message) && loadRequest !== undefined && JSON.stringify(message.id) === loadRequest) {
117
+ loadRequest = undefined;
118
+ decide({ kind: 'note', note: { type: 'historyEnd' } });
119
+ }
120
+ }
121
+ else {
122
+ const message = JSON.parse(record.line);
123
+ const params = message.params ?? {};
124
+ if (message.method === 'initialize') {
125
+ const own = stdin.map((l) => JSON.parse(l)).filter((m) => m.method === 'initialize').at(-1);
126
+ if (!own)
127
+ throw new Error('transduce: no initialize was written at spawn');
128
+ ids.set(JSON.stringify(message.id), own.id);
129
+ }
130
+ else if (message.method === 'session/new' || message.method === 'session/load' || message.method === 'session/resume') {
131
+ const mode = message.method.slice('session/'.length);
132
+ const mcpServers = Array.isArray(params.mcpServers) ? params.mcpServers : [];
133
+ actions.push({ kind: 'open', mode: mode, cwd: String(params.cwd), mcpServers,
134
+ ...(mode !== 'new' ? { sessionId: String(params.sessionId) } : {}) });
135
+ if (mode === 'load')
136
+ loadRequest = JSON.stringify(message.id);
137
+ map(message, decide({ kind: 'note', note: {
138
+ type: 'agent_session_open', mode, cwd: params.cwd, mcpServers,
139
+ ...(mode !== 'new' ? { sessionId: params.sessionId } : {}),
140
+ } }, ...(mode === 'load' && options.afterLoadOpen ? options.afterLoadOpen(String(params.sessionId)) : [])));
141
+ }
142
+ else if (message.method === 'session/prompt') {
143
+ turns += 1;
144
+ const blocksRef = `blocks-${turns}`;
145
+ const blocks = params.prompt;
146
+ const platformTurnId = `turn-${turns}`;
147
+ session.blocks.set(blocksRef, blocks);
148
+ interrupted = false;
149
+ actions.push({ kind: 'prompt', blocks, platformTurnId });
150
+ map(message, decide({ kind: 'note', note: { type: 'agent_prompt', blocksRef, platformTurnId } }));
151
+ }
152
+ else if (message.method === 'session/set_mode' || message.method === 'session/set_config_option') {
153
+ configs += 1;
154
+ const configId = message.method === 'session/set_mode' ? 'acp:modes' : String(params.configId);
155
+ const value = message.method === 'session/set_mode' ? params.modeId : params.value;
156
+ actions.push({ kind: 'config', configId, value });
157
+ map(message, decide({ kind: 'note', note: { type: 'agent_set_config_option', id: `cfg-${configs}`, configId, value } }));
158
+ }
159
+ else if (message.method === 'session/cancel') {
160
+ actions.push({ kind: 'interrupt', o: record.o });
161
+ interrupted = true;
162
+ decide({ kind: 'note', note: { type: 'agent_interrupt' } });
163
+ }
164
+ else if (message.method !== undefined && message.id !== undefined) {
165
+ actions.push({ kind: 'rpc', method: message.method });
166
+ map(message, decide({ kind: 'note', note: { type: 'rpc.request', method: message.method, ...(message.params !== undefined ? { params } : {}) } }));
167
+ }
168
+ else if (isResponse(message)) {
169
+ const method = agentRequests.get(JSON.stringify(message.id));
170
+ if (method === 'session/request_permission') {
171
+ const outcome = message.result?.outcome;
172
+ const optionId = outcome?.outcome === 'selected' && typeof outcome.optionId === 'string' ? outcome.optionId : null;
173
+ // A `cancelled` answer after the client's interrupt is the interrupt's own doing: the processor
174
+ // answered that card on the `agent_interrupt` note, as `AcpSession.interrupt` does.
175
+ const byInterrupt = optionId === null && interrupted;
176
+ actions.push({ kind: 'answer', agentRequestId: message.id, optionId, byInterrupt });
177
+ if (!byInterrupt) {
178
+ decide({ kind: 'note', note: {
179
+ type: 'agent_answer', requestId: permissionRequestId(SPAWN_ID, message.id), outcome: optionId ?? '__cancelled__',
180
+ } });
181
+ }
182
+ }
183
+ // Any other answer (the capture client refusing fs/terminal with -32601) the processor
184
+ // already gave itself on the request's own record.
185
+ }
186
+ else {
187
+ throw new Error(`transduce: no daemon decision for the capture's down line ${record.line.slice(0, 200)}`);
188
+ }
189
+ }
190
+ const extra = options.after?.get(record.o);
191
+ if (extra)
192
+ for (const entry of extra())
193
+ decide(entry);
194
+ }
195
+ return { session, records: log.read(0), frames: session.outbox, processor, actions };
196
+ }