@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
@@ -0,0 +1,331 @@
1
+ import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import path, { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { AcpKeeperClient } from '../../keepers/acp/client.js';
6
+ import { readKeeperManifest } from '../../keepers/kit/bundle.js';
7
+ import { keeperGen } from '../../keepers/kit/identity.js';
8
+ import { KeeperLauncher } from '../../keepers/kit/launcher.js';
9
+ import { killKeepersUnder } from '../../../test/keeperKit.js';
10
+ import { AcpAgents } from '../acpAgents.js';
11
+ import { KeeperTransport } from '../processor/transport/keeper.js';
12
+ import { LocalTransport } from '../processor/transport/local.js';
13
+ import { RawTruncatedError } from '../processor/transport/types.js';
14
+ import { startRawAgent } from '../processor/test/rawAgent.js';
15
+ import { watch } from './sessionFrames.js';
16
+ const here = path.dirname(fileURLToPath(import.meta.url));
17
+ export const PACKAGE_ROOT = path.join(here, '..', '..', '..', '..');
18
+ export const REAL_KEEPER_DIST = path.join(PACKAGE_ROOT, 'dist', 'keepers');
19
+ const list = (value) => (value ?? '').split(',').map((part) => part.trim()).filter(Boolean);
20
+ export function realAdapterConfig(env = process.env) {
21
+ const transports = list(env.AXOM_REAL_ADAPTER_TRANSPORTS);
22
+ for (const name of transports)
23
+ if (name !== 'local' && name !== 'keeper')
24
+ throw new Error(`AXOM_REAL_ADAPTER_TRANSPORTS: unknown transport '${name}'`);
25
+ const args = env.AXOM_ACP_CONTRACT_ARGS ? JSON.parse(env.AXOM_ACP_CONTRACT_ARGS) : ['--acp'];
26
+ if (!Array.isArray(args) || !args.every((arg) => typeof arg === 'string'))
27
+ throw new Error('AXOM_ACP_CONTRACT_ARGS must be a JSON array of strings');
28
+ return {
29
+ enabled: env.AXOM_REAL_ADAPTER_HARNESS === '1',
30
+ prompts: env.AXOM_REAL_ADAPTER_PROMPTS === '1',
31
+ agents: list(env.AXOM_REAL_ADAPTER_AGENTS),
32
+ ...(env.AXOM_ACP_CONTRACT_COMMAND ? { command: env.AXOM_ACP_CONTRACT_COMMAND } : {}),
33
+ args,
34
+ ...(env.AXOM_REAL_ADAPTER_AUTH_HOME ? { authHome: env.AXOM_REAL_ADAPTER_AUTH_HOME } : {}),
35
+ envPass: list(env.AXOM_REAL_ADAPTER_ENV_PASS),
36
+ transports: transports.length > 0 ? transports : ['local', 'keeper'],
37
+ };
38
+ }
39
+ /** Why a tier does not run (undefined: it runs). Shown as the skipped test's name. */
40
+ export function skipReason(config, tier) {
41
+ if (!config.enabled)
42
+ return 'set AXOM_REAL_ADAPTER_HARNESS=1 (see acp/test/realAdapter.ts)';
43
+ if (tier === 'no-prompt') {
44
+ return config.command || config.agents.length > 0 ? undefined : 'set AXOM_ACP_CONTRACT_COMMAND or AXOM_REAL_ADAPTER_AGENTS';
45
+ }
46
+ if (!config.prompts)
47
+ return 'sends prompts (model usage): set AXOM_REAL_ADAPTER_PROMPTS=1';
48
+ if (!config.authHome)
49
+ return 'needs a logged-in adapter: set AXOM_REAL_ADAPTER_AUTH_HOME';
50
+ if (tier === 'qoder')
51
+ return config.command ? undefined : 'set AXOM_ACP_CONTRACT_COMMAND to qodercli';
52
+ return undefined;
53
+ }
54
+ /**
55
+ * A provider history failure fails certification but never prevents local teardown; each local step is guarded on its own, so one
56
+ * failure cannot strand the resources after it. Every failure is reported together.
57
+ */
58
+ export async function cleanupOwnedFixture(actions) {
59
+ const failures = [];
60
+ try {
61
+ await actions.deleteSession();
62
+ }
63
+ catch (error) {
64
+ failures.push(error);
65
+ }
66
+ finally {
67
+ for (const release of [actions.closeTransport, actions.killOwnedGroup, actions.removeWorkspace]) {
68
+ try {
69
+ await release();
70
+ }
71
+ catch (error) {
72
+ failures.push(error);
73
+ }
74
+ }
75
+ }
76
+ if (failures.length)
77
+ throw new AggregateError(failures, `Qoder cleanup incomplete: ${failures.map(String).join('; ')}`);
78
+ }
79
+ export async function bounded(promise, label, ms) {
80
+ let timer;
81
+ try {
82
+ return await Promise.race([promise, new Promise((_, reject) => {
83
+ timer = setTimeout(() => reject(new Error(`UNCERTIFIED: ${label} exceeded ${ms}ms`)), ms);
84
+ })]);
85
+ }
86
+ finally {
87
+ clearTimeout(timer);
88
+ }
89
+ }
90
+ // ---- launcher and environment -------------------------------------------------------------------------------------------------
91
+ /**
92
+ * `launch.cjs <envFile> <pidFile> <command> ...args`: spawns the adapter detached (its own process group), with exactly the
93
+ * environment in `envFile`, stdio inherited (the pipes keeper-acp or the local transport gave the launcher), and records its pid.
94
+ * It forwards SIGTERM/SIGINT to the group and exits with the adapter.
95
+ */
96
+ const LAUNCHER = `const {spawn}=require('node:child_process');const fs=require('node:fs');
97
+ const [envFile,pidFile,command,...args]=process.argv.slice(2);
98
+ const env=JSON.parse(fs.readFileSync(envFile,'utf8'));
99
+ const child=spawn(command,args,{detached:true,stdio:'inherit',env});
100
+ fs.writeFileSync(pidFile,String(child.pid));
101
+ for(const signal of ['SIGTERM','SIGINT'])process.on(signal,()=>{try{process.kill(-child.pid,signal)}catch{}});
102
+ child.on('exit',(code,signal)=>process.exit(code??(signal?1:0)));
103
+ child.on('error',(error)=>{process.stderr.write(String(error)+'\\n');process.exit(127)});`;
104
+ export async function writeLauncher(dir) {
105
+ const file = join(dir, 'launch.cjs');
106
+ await writeFile(file, LAUNCHER);
107
+ return file;
108
+ }
109
+ /** The adapter's whole environment (see the file comment). `home` is created when it is a temp one. */
110
+ export async function isolatedEnv(home, config, launchEnv = {}) {
111
+ const env = {
112
+ PATH: launchEnv.PATH ?? process.env.PATH ?? '/usr/bin:/bin', HOME: home,
113
+ CLAUDE_CONFIG_DIR: join(home, '.claude'), CODEX_HOME: join(home, '.codex'), XDG_CONFIG_HOME: join(home, '.config'),
114
+ };
115
+ for (const name of config.envPass) {
116
+ const value = process.env[name];
117
+ if (value !== undefined)
118
+ env[name] = value;
119
+ }
120
+ Object.assign(env, launchEnv);
121
+ for (const dir of [env.CLAUDE_CONFIG_DIR, env.CODEX_HOME, env.XDG_CONFIG_HOME])
122
+ await mkdir(dir, { recursive: true });
123
+ return env;
124
+ }
125
+ export async function startRun(transport, prefix) {
126
+ const configDir = await mkdtemp(join(tmpdir(), `${prefix}-${transport}-cfg-`));
127
+ const runDir = await mkdtemp(join(tmpdir(), `${prefix}-${transport}-run-`));
128
+ if (transport === 'local') {
129
+ const local = new LocalTransport({ configDir });
130
+ const acp = new AcpAgents({ cacheDir: configDir, transports: { local }, sessionStartTimeoutMs: 60_000 });
131
+ return { transport, configDir, runDir, acp, reader: local, async dispose() {
132
+ await local.dispose(0);
133
+ for (const dir of [configDir, runDir])
134
+ await rm(dir, { recursive: true, force: true });
135
+ } };
136
+ }
137
+ const launcher = new KeeperLauncher({ configDir, workstationId: 'ws-t30a', server: 'http://test.invalid', distDir: REAL_KEEPER_DIST,
138
+ useSystemd: false, startTimeoutMs: 20_000, umask: 0o022 });
139
+ const keeper = new KeeperTransport(new AcpKeeperClient({ launcher }));
140
+ const acp = new AcpAgents({ cacheDir: configDir, transports: { keeper }, sessionStartTimeoutMs: 60_000 });
141
+ return { transport, configDir, runDir, acp, reader: keeper, async dispose() {
142
+ keeper.close();
143
+ const gen = keeperGen((await readKeeperManifest(join(REAL_KEEPER_DIST, 'acp'))).buildVersion);
144
+ await launcher.stop('acp', gen, 'force').catch(() => { });
145
+ killKeepersUnder(configDir);
146
+ for (const dir of [configDir, runDir])
147
+ await rm(dir, { recursive: true, force: true });
148
+ } };
149
+ }
150
+ /**
151
+ * Every retained raw record of `sessionKey`. A turn's end cuts a checkpoint and the transport may drop the records before it, so a
152
+ * case that needs the open's lines reads them before its first turn.
153
+ */
154
+ export async function readAllRaw(run, sessionKey) {
155
+ const records = [];
156
+ for (let at = 0;;) {
157
+ let page;
158
+ try {
159
+ page = await run.reader.readRaw(sessionKey, at);
160
+ }
161
+ catch (error) {
162
+ if (!(error instanceof RawTruncatedError) || at !== 0)
163
+ throw error;
164
+ at = error.first;
165
+ continue;
166
+ }
167
+ if (page.length === 0)
168
+ break;
169
+ records.push(...page);
170
+ at = page[page.length - 1].o + 1;
171
+ }
172
+ return records;
173
+ }
174
+ export const downMethodsOf = (records) => records.filter((r) => r.kind === 'down')
175
+ .map((r) => JSON.parse(r.line).method).filter((m) => m !== undefined);
176
+ let refCounter = 0;
177
+ /** The launcher argv (and its 0600 env file) for one adapter process of `ref`. */
178
+ async function launcherArgs(run, ref, launch, options) {
179
+ const envFile = join(run.runDir, `${ref}.env.json`);
180
+ const pidFile = join(run.runDir, `${ref}.pid`);
181
+ await writeFile(envFile, JSON.stringify(await isolatedEnv(options.home, options.config, launch.launchEnv)), { mode: 0o600 });
182
+ await chmod(envFile, 0o600);
183
+ const launcher = await writeLauncher(run.runDir);
184
+ return { args: [launcher, envFile, pidFile, launch.command, ...launch.args], pidFile };
185
+ }
186
+ export async function openReal(run, launch, options) {
187
+ refCounter += 1;
188
+ const ref = `real-${refCounter}`;
189
+ const { args, pidFile } = await launcherArgs(run, ref, launch, options);
190
+ let opened;
191
+ try {
192
+ opened = await run.acp.open({ agentId: launch.agentId, cwd: options.cwd, env: { HOME: options.home }, agentEnv: {},
193
+ command: process.execPath, args, sessionRef: ref, agentKey: 'real-harness', ...options.over });
194
+ }
195
+ catch (error) {
196
+ // The ref, so a case can read what the refused open wrote (and kill what it started).
197
+ throw Object.assign(error instanceof Error ? error : new Error(String(error)), { harnessRef: ref, pidFile });
198
+ }
199
+ const onEvent = options.onEvent;
200
+ const watched = watch(opened.session, onEvent ? (event) => onEvent(event, opened.session) : undefined);
201
+ return { ref, session: opened.session, nativeSessionId: opened.nativeSessionId, watched, pidFile,
202
+ keeperHosted: run.acp.placementOf(opened.session)?.keeper === true };
203
+ }
204
+ /** A JSON-RPC error answer as an outcome: `-32601` (method not found) is the adapter rejecting the method itself. */
205
+ const errorOutcome = (error) => error.code === -32601 ? 'unsupported' : `failed: ${error.code ?? '?'} ${error.message ?? ''}`.trimEnd();
206
+ /**
207
+ * `session/delete` on a still-open session's own connection (what the deleted harness did, before closing its transport): an
208
+ * `rpc.request` note, the processor writes the request, and the answer is read back from the raw log. `not-sent` when the session is
209
+ * no longer connected (the processor drops the note) or nothing came back in time.
210
+ */
211
+ export async function deleteOnLive(run, opened, id, timeoutMs = 15_000) {
212
+ if (!opened.session.liveness.agentAlive)
213
+ return 'not-sent';
214
+ await opened.session.note({ type: 'rpc.request', method: 'session/delete', params: { sessionId: id } });
215
+ const deadline = Date.now() + timeoutMs;
216
+ while (Date.now() < deadline) {
217
+ const lines = (await readAllRaw(run, opened.ref)).filter((r) => r.kind === 'up' || r.kind === 'down')
218
+ .map((r) => ({ kind: r.kind, message: JSON.parse(r.line) }));
219
+ const request = lines.filter((l) => l.kind === 'down' && l.message.method === 'session/delete' && l.message.params?.sessionId === id).at(-1);
220
+ const answer = request && lines.find((l) => l.kind === 'up' && l.message.method === undefined && l.message.id === request.message.id);
221
+ if (answer)
222
+ return answer.message.error ? errorOutcome(answer.message.error) : 'deleted';
223
+ await new Promise((resolve) => setTimeout(resolve, 200));
224
+ }
225
+ return 'not-sent';
226
+ }
227
+ /**
228
+ * `session/delete` straight over a fresh adapter process (raw ndjson, our own `initialize`), whatever the adapter advertised — the
229
+ * deleted harness sent it unconditionally. Same launcher and environment as the sessions.
230
+ */
231
+ async function deleteDirect(run, launch, options, ids) {
232
+ refCounter += 1;
233
+ const { args, pidFile } = await launcherArgs(run, `delete-direct-${refCounter}`, launch, options);
234
+ const agent = startRawAgent({ command: process.execPath, args });
235
+ const outcomes = {};
236
+ try {
237
+ agent.send(JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: 1, clientCapabilities: {} } }));
238
+ await agent.nextLine((m) => m.id === 1 && m.method === undefined, 30_000);
239
+ for (const [index, id] of ids.entries()) {
240
+ const rpcId = 2 + index;
241
+ agent.send(JSON.stringify({ jsonrpc: '2.0', id: rpcId, method: 'session/delete', params: { sessionId: id } }));
242
+ const answer = JSON.parse(await agent.nextLine((m) => m.id === rpcId && m.method === undefined, 30_000));
243
+ outcomes[id] = answer.error ? errorOutcome(answer.error) : 'deleted';
244
+ }
245
+ }
246
+ finally {
247
+ await agent.close();
248
+ await killOwnedGroup(pidFile);
249
+ }
250
+ return outcomes;
251
+ }
252
+ /**
253
+ * Deletes the test-owned native sessions; throws unless every one was deleted. Order (the ruling of T30a fix round 1):
254
+ * 1. on the session's own live connection, before its transport closes (the deleted harness's order — an adapter may refuse a
255
+ * delete from another process while the session is open);
256
+ * 2. what is left, over a throwaway connection through the production `deleteNativeSessions`;
257
+ * 3. if that answered `unsupported` only because the adapter does not advertise `sessionCapabilities.delete`, the request is sent
258
+ * directly anyway (logged `advertised:false`). `unsupported` is reported only when the adapter itself rejects the method.
259
+ * Which order a real Qoder needs is not known without one; both run, and the log line says which one deleted each id (T30b reads it).
260
+ */
261
+ export async function deleteOwnedSessions(run, launch, options, sessions) {
262
+ const ids = [...new Set(sessions.map((s) => s.nativeSessionId).filter((id) => !!id))];
263
+ const outcomes = {};
264
+ for (const id of ids) {
265
+ const live = [...sessions].reverse().find((s) => s.nativeSessionId === id && s.session.liveness.agentAlive);
266
+ if (live)
267
+ outcomes[id] = { via: 'live', outcome: await deleteOnLive(run, live, id) };
268
+ }
269
+ const rest = ids.filter((id) => outcomes[id]?.outcome !== 'deleted');
270
+ if (rest.length > 0) {
271
+ refCounter += 1;
272
+ const { args, pidFile } = await launcherArgs(run, `delete-${refCounter}`, launch, options);
273
+ let results;
274
+ try {
275
+ results = await run.acp.deleteNativeSessions({ agentId: launch.agentId, cwd: options.cwd, env: { HOME: options.home }, agentEnv: {},
276
+ command: process.execPath, args }, rest);
277
+ }
278
+ finally {
279
+ await killOwnedGroup(pidFile);
280
+ }
281
+ for (const result of results)
282
+ outcomes[result.nativeSessionId] = { via: 'deleteNativeSessions', outcome: result.outcome === 'failed'
283
+ ? `failed: ${result.error ?? ''}` : result.outcome };
284
+ const unadvertised = rest.filter((id) => outcomes[id]?.outcome === 'unsupported');
285
+ if (unadvertised.length > 0) {
286
+ console.info('Qoder owned-session delete', JSON.stringify({ advertised: false, ids: unadvertised }));
287
+ for (const [id, outcome] of Object.entries(await deleteDirect(run, launch, options, unadvertised)))
288
+ outcomes[id] = { via: 'direct', outcome };
289
+ }
290
+ }
291
+ console.info('Qoder owned-session delete', JSON.stringify(outcomes));
292
+ const failed = ids.filter((id) => outcomes[id]?.outcome !== 'deleted');
293
+ if (failed.length > 0)
294
+ throw new Error(`owned session/delete failed: ${JSON.stringify(Object.fromEntries(failed.map((id) => [id, outcomes[id]])))}`);
295
+ }
296
+ /** The ref and pid file of an open that failed ({@link openReal} attaches them to the error). */
297
+ export const failedOpen = (error) => (error ?? {});
298
+ /** An adapter refused `session/new` / `session/load` for want of credentials (ACP `auth_required`, -32000). */
299
+ export const isAuthRequired = (error) => error.errorCode === -32000 || /authentication required|auth_required/i.test(String(error));
300
+ /** Kills the adapter's own process group (from its pid file); an exited group or a launch that never wrote one is fine. */
301
+ export async function killOwnedGroup(pidFile) {
302
+ try {
303
+ const pid = Number(await readFile(pidFile, 'utf8'));
304
+ if (!Number.isInteger(pid) || pid <= 1)
305
+ throw new Error('Invalid owned process group ID');
306
+ process.kill(-pid, 'SIGKILL');
307
+ }
308
+ catch (error) {
309
+ const code = error.code;
310
+ if (code !== 'ENOENT' && code !== 'ESRCH')
311
+ throw error;
312
+ }
313
+ }
314
+ /** Sends one prompt and waits for its `agent_turn_done`; returns the turn and the events it produced. */
315
+ export async function turn(opened, text, timeoutMs) {
316
+ const start = opened.watched.events.length;
317
+ const n = opened.watched.turns.length + 1;
318
+ const done = opened.watched.turn(n);
319
+ opened.session.prompt([{ type: 'text', text }], `turn-${refCounter}-${n}`);
320
+ const result = await bounded(done, 'prompt', timeoutMs);
321
+ return { done: result, events: opened.watched.events.slice(start) };
322
+ }
323
+ /** Drains a load's backfill (the history the processor folded inside the load's window). */
324
+ export async function historyOf(opened) {
325
+ const history = [];
326
+ const backfill = opened.session.backfill;
327
+ if (backfill)
328
+ for await (const event of backfill())
329
+ history.push(event);
330
+ return history;
331
+ }
@@ -0,0 +1,214 @@
1
+ // @ts-nocheck
2
+ // A scripted ACP agent in plain JavaScript over raw ndjson (no SDK), for the Task 21 adoption tests: its turns are driven by
3
+ // marker files, so a test can let a turn run on while the daemon is gone, and prove from files what reached the agent. Run like
4
+ // `fakeAgent.ts`: `node -e "eval(readFileSync(argv[1]))" <this file> <config JSON>` (see `scriptAgentLaunch` in the test helpers).
5
+ //
6
+ // Config (all optional except `dir`):
7
+ // dir: string every marker file lives here
8
+ // sessionId?: string session/new's id (default 'S1'); session/load answers {} for any id
9
+ // chunksBefore?: string[] agent_message_chunk texts sent when a prompt arrives
10
+ // permission?: { id } then: session/request_permission with this JSON-RPC id; waits for its response
11
+ // cancelPermissionOn?: string while waiting: once this file exists, send `$/cancel_request{requestId}` (keep waiting)
12
+ // waitFor?: string then: wait for this file
13
+ // crashOn?: string while a prompt runs: once this file exists, the process exits 3 mid-turn (no end_turn)
14
+ // chunksAfter?: string[] then: more chunks; then the prompt answers {stopReason:'end_turn'}
15
+ // commandsAfterOpen?: object[] an available_commands_update right after session/new or session/load answers
16
+ // chunksAfterOpen?: string[] agent_message_chunk texts right after session/new answers (output outside any turn)
17
+ // mcpHttp?: boolean initialize advertises mcpCapabilities.http (a delegation MCP lease can be opened on it)
18
+ // stderrEnv?: string[] at start: for each env var, its value on stderr (raw, and JSON-escaped) — Task 23's sentinel
19
+ // stderrMcpHeaders?: boolean on session/new and session/load: every MCP header value received, on stderr (raw and JSON)
20
+ // version?: string initialize's agentInfo.version (default '1.0.0') (Task 24)
21
+ // noLoad?: boolean initialize does not advertise loadSession
22
+ // failInitializeOn?: string initialize answers an error once this marker exists
23
+ // failLoad?: boolean session/load answers an error
24
+ // failLoadOn?: string session/load answers an error once this marker exists
25
+ // loadWaitFor?: string session/load waits for this file before it answers
26
+ // historyOnLoad?: string[] agent_message_chunk texts replayed (as history) before session/load answers
27
+ // configOptions?: object[] session/new and session/load answer these configOptions (load: as configured — the agent keeps
28
+ // no choice); session/set_config_option sets currentValue and answers the new list
29
+ // envOut?: string[] at start: these env vars' values into `env.json`
30
+ // initializeWaitFor?: string initialize waits for this file before it answers (Task 24 fix round 1)
31
+ // emitOn?: { file, text } once the session is open: when this file appears, one agent_message_chunk outside any turn
32
+ // chunkOnTerm?: string on SIGTERM: one agent_message_chunk (outside any turn), then exit (fix round 2)
33
+ // stderrOn?: { file, text } once the session is open: when this file appears, one stderr line
34
+ // exitAfterOpenOn?: string once the session is open (session/new or session/load answered): if this file exists, the
35
+ // process exits 3 shortly after — an adapter that dies on every respawn
36
+ //
37
+ // Files it writes: `stdin.jsonl` (every line received), `prompt-<n>` (prompt n arrived), `turn-<n>-ended` (its end_turn was written),
38
+ // `permission-response.json` (the raw
39
+ // response to its permission request), `cancel-received` (a session/cancel notification), `pid`.
40
+ const { appendFileSync, existsSync, writeFileSync } = require('node:fs');
41
+ const path = require('node:path');
42
+ const config = JSON.parse(process.argv[2] || '{}');
43
+ const at = (name) => path.join(config.dir, name);
44
+ writeFileSync(at('pid'), String(process.pid));
45
+ if (config.envOut)
46
+ writeFileSync(at('env.json'), JSON.stringify(Object.fromEntries(config.envOut.map((name) => [name, process.env[name] ?? null]))));
47
+ let configOptions = JSON.parse(JSON.stringify(config.configOptions ?? []));
48
+ let openSessionId;
49
+ if (config.chunkOnTerm) {
50
+ process.on('SIGTERM', () => {
51
+ if (openSessionId)
52
+ notify('session/update', { sessionId: openSessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: config.chunkOnTerm } } });
53
+ setTimeout(() => process.exit(0), 50);
54
+ });
55
+ }
56
+ for (const name of config.stderrEnv ?? []) {
57
+ const value = process.env[name] ?? '';
58
+ process.stderr.write(`env ${name}=${value}\n`);
59
+ process.stderr.write(`env-json ${JSON.stringify({ [name]: value })}\n`);
60
+ }
61
+ const send = (message) => process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', ...message })}\n`);
62
+ const notify = (method, params) => send({ method, params });
63
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
64
+ const waitForFile = async (name) => { while (!existsSync(at(name)))
65
+ await sleep(20); };
66
+ const pending = new Map();
67
+ let prompts = 0;
68
+ async function prompt(id, params) {
69
+ prompts += 1;
70
+ writeFileSync(at(`prompt-${prompts}`), JSON.stringify(params));
71
+ const chunk = (text) => notify('session/update', { sessionId: params.sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } } });
72
+ for (const text of config.chunksBefore ?? []) {
73
+ chunk(text);
74
+ await sleep(5);
75
+ }
76
+ if (config.crashOn)
77
+ void waitForFile(config.crashOn).then(() => process.exit(3));
78
+ if (config.permission) {
79
+ const answered = new Promise((resolve) => pending.set(config.permission.id, resolve));
80
+ send({ id: config.permission.id, method: 'session/request_permission', params: {
81
+ sessionId: params.sessionId,
82
+ toolCall: { toolCallId: 'tool-1', title: 'Run the thing', kind: 'execute', status: 'pending' },
83
+ options: [{ optionId: 'allow', name: 'Allow', kind: 'allow_once' }, { optionId: 'deny', name: 'Deny', kind: 'reject_once' }],
84
+ } });
85
+ if (config.cancelPermissionOn) {
86
+ void (async () => {
87
+ await waitForFile(config.cancelPermissionOn);
88
+ if (pending.has(config.permission.id))
89
+ notify('$/cancel_request', { requestId: config.permission.id });
90
+ })();
91
+ }
92
+ const response = await answered;
93
+ writeFileSync(at('permission-response.json'), JSON.stringify(response));
94
+ }
95
+ if (config.waitFor)
96
+ await waitForFile(config.waitFor);
97
+ for (const text of config.chunksAfter ?? []) {
98
+ chunk(text);
99
+ await sleep(5);
100
+ }
101
+ send({ id, result: { stopReason: 'end_turn' } });
102
+ writeFileSync(at(`turn-${prompts}-ended`), '');
103
+ }
104
+ function onLine(line) {
105
+ appendFileSync(at('stdin.jsonl'), `${line}\n`);
106
+ let message;
107
+ try {
108
+ message = JSON.parse(line);
109
+ }
110
+ catch {
111
+ return;
112
+ }
113
+ if (message.method === undefined && message.id !== undefined) {
114
+ const resolve = pending.get(message.id);
115
+ if (resolve) {
116
+ pending.delete(message.id);
117
+ resolve(message);
118
+ }
119
+ return;
120
+ }
121
+ switch (message.method) {
122
+ case 'initialize': {
123
+ const answer = () => {
124
+ if (config.failInitializeOn && existsSync(at(config.failInitializeOn))) {
125
+ send({ id: message.id, error: { code: -32002, message: 'Initialize refused: old build unavailable' } });
126
+ return;
127
+ }
128
+ send({ id: message.id, result: { protocolVersion: 1, agentCapabilities: { loadSession: !config.noLoad, ...(config.mcpHttp ? { mcpCapabilities: { http: true } } : {}) }, authMethods: [],
129
+ agentInfo: { name: 'script-agent', version: config.version ?? '1.0.0' } } });
130
+ };
131
+ if (config.initializeWaitFor)
132
+ void waitForFile(config.initializeWaitFor).then(answer);
133
+ else
134
+ answer();
135
+ return;
136
+ }
137
+ case 'session/set_config_option': {
138
+ configOptions = configOptions.map((option) => (option.id === message.params.configId ? { ...option, currentValue: message.params.value } : option));
139
+ send({ id: message.id, result: { configOptions } });
140
+ return;
141
+ }
142
+ case 'session/load':
143
+ void (async () => {
144
+ if (config.loadWaitFor)
145
+ await waitForFile(config.loadWaitFor);
146
+ if (config.failLoad || (config.failLoadOn && existsSync(at(config.failLoadOn)))) {
147
+ send({ id: message.id, error: { code: -32002, message: 'Resource not found: session' } });
148
+ return;
149
+ }
150
+ for (const text of config.historyOnLoad ?? []) {
151
+ notify('session/update', { sessionId: message.params.sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } } });
152
+ }
153
+ openSession(message);
154
+ })();
155
+ return;
156
+ case 'session/new':
157
+ openSession(message);
158
+ return;
159
+ case 'session/prompt':
160
+ void prompt(message.id, message.params);
161
+ return;
162
+ case 'session/cancel':
163
+ writeFileSync(at('cancel-received'), line);
164
+ return;
165
+ default:
166
+ if (message.id !== undefined)
167
+ send({ id: message.id, error: { code: -32601, message: 'Method not found' } });
168
+ }
169
+ }
170
+ function openSession(message) {
171
+ const sessionId = message.method === 'session/new' ? (config.sessionId ?? 'S1') : message.params.sessionId;
172
+ if (config.stderrMcpHeaders) {
173
+ for (const server of message.params.mcpServers ?? []) {
174
+ for (const header of server.headers ?? []) {
175
+ process.stderr.write(`mcp header ${header.name}: ${header.value}\n`);
176
+ process.stderr.write(`mcp header json ${JSON.stringify(header)}\n`);
177
+ }
178
+ }
179
+ }
180
+ const opened = config.configOptions ? { configOptions: JSON.parse(JSON.stringify(config.configOptions)) } : {};
181
+ if (config.configOptions)
182
+ configOptions = JSON.parse(JSON.stringify(config.configOptions));
183
+ send({ id: message.id, result: message.method === 'session/new' ? { sessionId, ...opened } : opened });
184
+ if (message.method === 'session/new') {
185
+ for (const text of config.chunksAfterOpen ?? []) {
186
+ notify('session/update', { sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } } });
187
+ }
188
+ }
189
+ openSessionId = sessionId;
190
+ if (config.exitAfterOpenOn && existsSync(at(config.exitAfterOpenOn)))
191
+ setTimeout(() => process.exit(3), 50);
192
+ if (config.stderrOn)
193
+ void waitForFile(config.stderrOn.file).then(() => process.stderr.write(`${config.stderrOn.text}\n`));
194
+ if (config.emitOn) {
195
+ void waitForFile(config.emitOn.file).then(() => {
196
+ notify('session/update', { sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: config.emitOn.text } } });
197
+ });
198
+ }
199
+ if (config.commandsAfterOpen) {
200
+ notify('session/update', { sessionId, update: { sessionUpdate: 'available_commands_update', availableCommands: config.commandsAfterOpen } });
201
+ }
202
+ }
203
+ let buffer = '';
204
+ process.stdin.on('data', (chunk) => {
205
+ buffer += chunk.toString('utf8');
206
+ for (let newline = buffer.indexOf('\n'); newline >= 0; newline = buffer.indexOf('\n')) {
207
+ const line = buffer.slice(0, newline);
208
+ buffer = buffer.slice(newline + 1);
209
+ if (line.trim())
210
+ onLine(line);
211
+ }
212
+ });
213
+ process.stdin.on('end', () => process.exit(0));
214
+ export {};
@@ -0,0 +1,42 @@
1
+ export const eventOf = (frame) => ({
2
+ eventName: frame.eventName,
3
+ payload: frame.payload,
4
+ ...(frame.acpSessionId !== undefined ? { acpSessionId: frame.acpSessionId } : {}),
5
+ });
6
+ /** `onEvent` (optional) sees each `agent_event` as it arrives — how a test answers cards the moment they are raised. */
7
+ export function watch(session, onEvent) {
8
+ const watched = { frames: [], events: [], turns: [],
9
+ activity: [], diagnostics: [] };
10
+ const waiters = [];
11
+ session.onFrames((frames) => {
12
+ for (const output of frames) {
13
+ watched.frames.push(output);
14
+ const frame = output.frame;
15
+ if (frame.t === 'agent_event') {
16
+ const event = eventOf(frame);
17
+ watched.events.push(event);
18
+ onEvent?.(event);
19
+ }
20
+ else if (frame.t === 'agent_session_state')
21
+ watched.activity.push(frame.raw);
22
+ else if (frame.t === 'agent_runtime_profile')
23
+ watched.diagnostics.push(frame.diagnostics);
24
+ else if (frame.t === 'agent_turn_done') {
25
+ watched.turns.push(frame);
26
+ for (const waiter of waiters.filter((w) => w.n <= watched.turns.length)) {
27
+ waiters.splice(waiters.indexOf(waiter), 1);
28
+ waiter.resolve(watched.turns[waiter.n - 1]);
29
+ }
30
+ }
31
+ }
32
+ });
33
+ return {
34
+ ...watched,
35
+ turn: (n = 1) => new Promise((resolve) => {
36
+ if (watched.turns.length >= n)
37
+ resolve(watched.turns[n - 1]);
38
+ else
39
+ waiters.push({ n, resolve });
40
+ }),
41
+ };
42
+ }
@@ -25,9 +25,11 @@ import { execFile } from 'node:child_process';
25
25
  * `binaryInstall.ts`'s zip-extraction fallback — keep working unchanged; it is `undefined` on a
26
26
  * timeout, which is never `ENOENT`.
27
27
  */
28
- export function runTimedCommand(command, args, timeoutMs) {
28
+ export function runTimedCommand(command, args, timeoutMs, options = {}) {
29
29
  return new Promise((resolve) => {
30
- execFile(command, args, { encoding: 'utf8', timeout: timeoutMs, windowsHide: true }, (error, stdout) => {
30
+ execFile(command, args, {
31
+ encoding: 'utf8', timeout: timeoutMs, windowsHide: true, maxBuffer: 16 * 1024 * 1024, ...(options.env ? { env: options.env } : {}),
32
+ }, (error, stdout) => {
31
33
  if (!error) {
32
34
  resolve({ ok: true, stdout: stdout.toString() });
33
35
  return;