@platless/axom-cli 0.1.0 → 0.5.9

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 (281) hide show
  1. package/README.md +1 -1
  2. package/dist/authScopes.js +19 -0
  3. package/dist/axom-patch/cli/README.md +132 -0
  4. package/dist/axom-patch/cli/account-limits/README.md +27 -0
  5. package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
  6. package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
  7. package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
  8. package/dist/axom-patch/cli/account-limits/generate.py +120 -0
  9. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +64 -0
  10. package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
  11. package/dist/axom-patch/cli/claude-account-limits.json +37 -0
  12. package/dist/axom-patch/cli/codex-account-limits.json +37 -0
  13. package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
  14. package/dist/axom-patch/cli/nightly-task.md +152 -0
  15. package/dist/axom-patch/cli/pi-steering.json +37 -0
  16. package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
  17. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +38 -0
  18. package/dist/axom-patch/cli/probes/pi-steering.mjs +80 -0
  19. package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
  20. package/dist/axom-patch/cli/probes/qoder-steering.mjs +89 -0
  21. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +61 -0
  22. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
  23. package/dist/axom-patch/cli/qoder-background-terminal.md +82 -0
  24. package/dist/axom-patch/cli/qoder-foreground-output.md +42 -0
  25. package/dist/axom-patch/cli/qoder-steering.json +52 -0
  26. package/dist/axom-patch/cli/qoder-steering.md +30 -0
  27. package/dist/axom-patch/cli/qoder-subagent-origin.json +73 -0
  28. package/dist/axom-patch/cli/qoder-subagent-subwork.md +73 -0
  29. package/dist/axom-patch/cli/runtime.json +63 -0
  30. package/dist/axom-patch/cli/steering/claude-steering-js.patch +89 -0
  31. package/dist/axom-patch/cli/steering/claude-steering-types.patch +28 -0
  32. package/dist/axom-patch/cli/steering/codex-steering.patch +57 -0
  33. package/dist/axom-patch/cli/steering/pi-steering.js +79 -0
  34. package/dist/axom-patch/cli/zcode-account-limits.json +31 -0
  35. package/dist/axom-patch/cli/zcode-models.json +38 -0
  36. package/dist/axom-patch/cli/zcode-subagent-subwork.json +73 -0
  37. package/dist/cli.js +36 -1
  38. package/dist/commands/auth.js +4 -13
  39. package/dist/commands/execute.js +4 -4
  40. package/dist/commands/record.js +6 -6
  41. package/dist/commands/struct.js +4 -4
  42. package/dist/commands/workstation.js +2 -2
  43. package/dist/credentials.js +2 -2
  44. package/dist/install/claude/commands.js +13 -13
  45. package/dist/keepers/acp/keeper.mjs +3083 -0
  46. package/dist/keepers/acp/manifest.json +9 -0
  47. package/dist/keepers/terminal/keeper.mjs +8469 -0
  48. package/dist/keepers/terminal/manifest.json +9 -0
  49. package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  50. package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  51. package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
  52. package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
  53. package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
  54. package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
  55. package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
  56. package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
  57. package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
  58. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
  59. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
  60. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
  61. package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  62. package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
  63. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  64. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  65. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  66. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  67. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  68. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  69. package/dist/mcp.js +32 -14
  70. package/dist/runtime-deps/qoder-sdk/package-lock.json +1189 -0
  71. package/dist/runtime-deps/qoder-sdk/package.json +8 -0
  72. package/dist/test/keeperKit.js +67 -0
  73. package/dist/workstation/acp/acpAgents.js +2514 -681
  74. package/dist/workstation/acp/acpClient.js +95 -318
  75. package/dist/workstation/acp/activityBusy.js +43 -0
  76. package/dist/workstation/acp/activityReporting.js +54 -0
  77. package/dist/workstation/acp/adoption.js +28 -356
  78. package/dist/workstation/acp/authUrlSniffer.js +36 -0
  79. package/dist/workstation/acp/binaryInstall.js +100 -27
  80. package/dist/workstation/acp/builds/gc.js +245 -0
  81. package/dist/workstation/acp/builds/install/binary.js +28 -0
  82. package/dist/workstation/acp/builds/install/npm.js +235 -0
  83. package/dist/workstation/acp/builds/install/uv.js +254 -0
  84. package/dist/workstation/acp/builds/migrate.js +195 -0
  85. package/dist/workstation/acp/builds/model.js +128 -0
  86. package/dist/workstation/acp/builds/nodeGate.js +100 -0
  87. package/dist/workstation/acp/builds/store.js +352 -0
  88. package/dist/workstation/acp/commandTableProbe.js +68 -0
  89. package/dist/workstation/acp/configOptions.js +103 -1
  90. package/dist/workstation/acp/delegationMcp.js +24 -0
  91. package/dist/workstation/acp/envSecrets.js +27 -0
  92. package/dist/workstation/acp/initializeResponse.js +285 -0
  93. package/dist/workstation/acp/installErrors.js +12 -2
  94. package/dist/workstation/acp/installProbe.js +1 -1
  95. package/dist/workstation/acp/managedPatches.js +144 -0
  96. package/dist/workstation/acp/outbox.js +330 -0
  97. package/dist/workstation/acp/patchAssets.js +80 -0
  98. package/dist/workstation/acp/permission.js +335 -37
  99. package/dist/workstation/acp/processor/checkpoint.js +87 -0
  100. package/dist/workstation/acp/processor/commands.js +35 -0
  101. package/dist/workstation/acp/processor/control.js +355 -0
  102. package/dist/workstation/acp/processor/emit.js +26 -0
  103. package/dist/workstation/acp/processor/jsonrpc.js +172 -0
  104. package/dist/workstation/acp/processor/memoryLog.js +49 -0
  105. package/dist/workstation/acp/processor/origins.js +48 -0
  106. package/dist/workstation/acp/processor/output.js +67 -0
  107. package/dist/workstation/acp/processor/projection.js +42 -0
  108. package/dist/workstation/acp/processor/record.js +128 -0
  109. package/dist/workstation/acp/processor/runtime.js +181 -0
  110. package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
  111. package/dist/workstation/acp/processor/session.js +525 -0
  112. package/dist/workstation/acp/processor/sideChannel.js +80 -0
  113. package/dist/workstation/acp/processor/state.js +74 -0
  114. package/dist/workstation/acp/processor/step.js +459 -0
  115. package/dist/workstation/acp/processor/test/fixtureLog.js +65 -0
  116. package/dist/workstation/acp/processor/test/harness.js +120 -0
  117. package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
  118. package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
  119. package/dist/workstation/acp/processor/transport/generations.js +76 -0
  120. package/dist/workstation/acp/processor/transport/keeper.js +130 -0
  121. package/dist/workstation/acp/processor/transport/local.js +269 -0
  122. package/dist/workstation/acp/processor/transport/types.js +78 -0
  123. package/dist/workstation/acp/processor/updates.js +364 -0
  124. package/dist/workstation/acp/processorSession.js +1512 -0
  125. package/dist/workstation/acp/qoderStartup.js +45 -0
  126. package/dist/workstation/acp/registry/catalog.js +385 -4
  127. package/dist/workstation/acp/registry/detect.js +55 -0
  128. package/dist/workstation/acp/registry/launch.js +61 -119
  129. package/dist/workstation/acp/sessionUpdateMapper.js +56 -27
  130. package/dist/workstation/acp/steering.js +58 -0
  131. package/dist/workstation/acp/switching.js +114 -0
  132. package/dist/workstation/acp/terminalLogin.js +171 -0
  133. package/dist/workstation/acp/terminalLoginSpec.js +41 -0
  134. package/dist/workstation/acp/test/adapterFixtures.js +256 -0
  135. package/dist/workstation/acp/test/daemonLife.js +106 -0
  136. package/dist/workstation/acp/test/fakeAgent.js +185 -9
  137. package/dist/workstation/acp/test/keeperWorld.js +267 -0
  138. package/dist/workstation/acp/test/memoryOutbox.js +55 -0
  139. package/dist/workstation/acp/test/raw/fixtures.js +196 -0
  140. package/dist/workstation/acp/test/raw/qoderCaptures.js +125 -0
  141. package/dist/workstation/acp/test/raw/sanitize.js +344 -0
  142. package/dist/workstation/acp/test/raw/sources.js +249 -0
  143. package/dist/workstation/acp/test/realAdapter.js +377 -0
  144. package/dist/workstation/acp/test/scriptAgent.js +206 -0
  145. package/dist/workstation/acp/test/sessionFrames.js +42 -0
  146. package/dist/workstation/acp/timedCommand.js +4 -2
  147. package/dist/workstation/acp/uvxInstall.js +72 -122
  148. package/dist/workstation/acp/validate.js +33 -5
  149. package/dist/workstation/agentFlow.js +103 -0
  150. package/dist/workstation/agentSessions.js +1686 -348
  151. package/dist/workstation/channels/agent.js +14 -6
  152. package/dist/workstation/channels/computer.js +203 -7
  153. package/dist/workstation/channels/computerExecs.js +200 -0
  154. package/dist/workstation/channels/management.js +86 -6
  155. package/dist/workstation/config.js +3 -1
  156. package/dist/workstation/connection/encryptedChannel.js +88 -21
  157. package/dist/workstation/daemon.js +309 -6
  158. package/dist/workstation/daemonctl.js +28 -2
  159. package/dist/workstation/desktop/binding.js +36 -0
  160. package/dist/workstation/desktop/bridge.js +212 -0
  161. package/dist/workstation/desktop/flow-control.js +52 -0
  162. package/dist/workstation/desktop/focused-text.js +130 -0
  163. package/dist/workstation/desktop/protocol.js +35 -0
  164. package/dist/workstation/exec/attach.js +126 -0
  165. package/dist/workstation/exec/command.js +40 -0
  166. package/dist/workstation/exec/durable.js +517 -0
  167. package/dist/workstation/exec/fallback.js +106 -0
  168. package/dist/workstation/exec/policy.js +837 -0
  169. package/dist/workstation/exec/shaping.js +211 -0
  170. package/dist/workstation/exec.js +3 -112
  171. package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
  172. package/dist/workstation/files/adapters/toolRequests.js +121 -0
  173. package/dist/workstation/files/client.js +455 -0
  174. package/dist/workstation/files/errors.js +28 -0
  175. package/dist/workstation/files/fence.js +350 -0
  176. package/dist/workstation/files/find/fileIndex.js +153 -0
  177. package/dist/workstation/files/find/fuzzy.js +155 -0
  178. package/dist/workstation/files/host.js +122 -0
  179. package/dist/workstation/files/hostProtocol.js +2 -0
  180. package/dist/workstation/files/media/cache.js +620 -0
  181. package/dist/workstation/files/media/encoding.js +308 -0
  182. package/dist/workstation/files/media/estimator.js +100 -0
  183. package/dist/workstation/files/media/media.js +325 -0
  184. package/dist/workstation/files/media/packedAudio.js +23 -0
  185. package/dist/workstation/files/media/scheduler.js +551 -0
  186. package/dist/workstation/files/media/types.js +5 -0
  187. package/dist/workstation/files/scope.js +45 -0
  188. package/dist/workstation/files/structured.js +284 -0
  189. package/dist/workstation/files/text.js +356 -0
  190. package/dist/workstation/files/uploads.js +351 -0
  191. package/dist/workstation/journal.js +65 -236
  192. package/dist/workstation/keepers/acp/client.js +101 -0
  193. package/dist/workstation/keepers/acp/envelope.js +62 -0
  194. package/dist/workstation/keepers/acp/host.js +419 -0
  195. package/dist/workstation/keepers/acp/main.js +18 -0
  196. package/dist/workstation/keepers/acp/memoryStream.js +121 -0
  197. package/dist/workstation/keepers/acp/params.js +137 -0
  198. package/dist/workstation/keepers/acp/protocol.js +66 -0
  199. package/dist/workstation/keepers/acp/recover.js +120 -0
  200. package/dist/workstation/keepers/acp/session.js +584 -0
  201. package/dist/workstation/keepers/acp/stderr.js +154 -0
  202. package/dist/workstation/keepers/kit/activity.js +103 -0
  203. package/dist/workstation/keepers/kit/bootstrap.js +164 -0
  204. package/dist/workstation/keepers/kit/buildVersion.js +51 -0
  205. package/dist/workstation/keepers/kit/bundle.js +120 -0
  206. package/dist/workstation/keepers/kit/client.js +432 -0
  207. package/dist/workstation/keepers/kit/identity.js +21 -0
  208. package/dist/workstation/keepers/kit/ipc.js +112 -0
  209. package/dist/workstation/keepers/kit/launchConfig.js +8 -0
  210. package/dist/workstation/keepers/kit/launcher.js +249 -0
  211. package/dist/workstation/keepers/kit/lock.js +123 -0
  212. package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
  213. package/dist/workstation/keepers/kit/paths.js +75 -0
  214. package/dist/workstation/keepers/kit/protocol.js +43 -0
  215. package/dist/workstation/keepers/kit/server.js +518 -0
  216. package/dist/workstation/keepers/kit/streamLog.js +697 -0
  217. package/dist/workstation/keepers/legacy.js +296 -0
  218. package/dist/workstation/keepers/policy.js +576 -0
  219. package/dist/workstation/keepers/stop.js +34 -0
  220. package/dist/workstation/keepers/terminal/client.js +213 -0
  221. package/dist/workstation/keepers/terminal/exec.js +981 -0
  222. package/dist/workstation/keepers/terminal/generations.js +129 -0
  223. package/dist/workstation/keepers/terminal/keeper.js +62 -0
  224. package/dist/workstation/keepers/terminal/main.js +43 -0
  225. package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
  226. package/dist/workstation/keepers/terminal/params.js +27 -0
  227. package/dist/workstation/keepers/terminal/protocol.js +56 -0
  228. package/dist/workstation/keepers/terminal/pty.js +330 -0
  229. package/dist/workstation/oauth/deviceAuthorization.js +21 -12
  230. package/dist/workstation/protocol/accountLimits.js +47 -0
  231. package/dist/workstation/protocol/agent.js +201 -8
  232. package/dist/workstation/protocol/common.js +36 -0
  233. package/dist/workstation/protocol/computer.js +107 -7
  234. package/dist/workstation/protocol/management.js +50 -2
  235. package/dist/workstation/qoderAdapter/backgroundProcess.js +118 -0
  236. package/dist/workstation/qoderAdapter/backgroundTerminal.js +193 -0
  237. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +372 -0
  238. package/dist/workstation/qoderAdapter/callbackContext.js +76 -0
  239. package/dist/workstation/qoderAdapter/completedContinuation.js +139 -0
  240. package/dist/workstation/qoderAdapter/contract.js +78 -0
  241. package/dist/workstation/qoderAdapter/foregroundOutput.js +36 -0
  242. package/dist/workstation/qoderAdapter/history.js +85 -0
  243. package/dist/workstation/qoderAdapter/identityIndex.js +191 -0
  244. package/dist/workstation/qoderAdapter/inputQueue.js +42 -0
  245. package/dist/workstation/qoderAdapter/main.js +95 -0
  246. package/dist/workstation/qoderAdapter/mapper.js +117 -0
  247. package/dist/workstation/qoderAdapter/observerPlugin.js +245 -0
  248. package/dist/workstation/qoderAdapter/observerReceiver.js +187 -0
  249. package/dist/workstation/qoderAdapter/permissions.js +61 -0
  250. package/dist/workstation/qoderAdapter/profile.js +93 -0
  251. package/dist/workstation/qoderAdapter/sdkDriver.js +48 -0
  252. package/dist/workstation/qoderAdapter/sdkRuntime.js +93 -0
  253. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +145 -0
  254. package/dist/workstation/qoderAdapter/session.js +319 -0
  255. package/dist/workstation/qoderAdapter/subagentMeta.js +28 -0
  256. package/dist/workstation/qoderAdapter/subwork.js +85 -0
  257. package/dist/workstation/qoderAdapter/taskControl.js +38 -0
  258. package/dist/workstation/qoderAdapter/taskLedger.js +163 -0
  259. package/dist/workstation/qoderAdapter/types.js +1 -0
  260. package/dist/workstation/qoderAdapter/usage.js +37 -0
  261. package/dist/workstation/register.js +4 -0
  262. package/dist/workstation/systemMetrics.js +110 -1
  263. package/dist/workstation/tools/browser.js +394 -0
  264. package/dist/workstation/tools/client.js +277 -0
  265. package/dist/workstation/tools/info.js +50 -0
  266. package/dist/workstation/tools/keeperProcess.js +124 -0
  267. package/dist/workstation/tools/router.js +55 -0
  268. package/dist/workstation/tools/terminalRouter.js +222 -0
  269. package/dist/workstation/tools/validation.js +86 -0
  270. package/dist/workstation/update/coordinator.js +32 -0
  271. package/dist/workstation/update/npmInstaller.js +2 -0
  272. package/dist/workstation/update/ptyPreflight.js +45 -0
  273. package/dist/workstation/update/taskRegistry.js +141 -23
  274. package/dist/workstation/update/watchdog.js +66 -0
  275. package/package.json +20 -7
  276. package/skills/axom/SKILL.md +55 -17
  277. package/skills/axom-definition/SKILL.md +32 -32
  278. package/skills/axom-platform/SKILL.md +39 -39
  279. package/dist/workstation/acp/acpSession.js +0 -1047
  280. package/dist/workstation/acp/contract.js +0 -494
  281. package/dist/workstation/acp/draftInterceptor.js +0 -154
@@ -0,0 +1,355 @@
1
+ /**
2
+ * The open session and its controls, as notes and their responses (Task 17b): `session/new|load|resume`,
3
+ * `historyEnd`, `session/set_config_option`/`set_mode`, `_session/steering`, `_session/async_task/stop`,
4
+ * delegation's non-secret fields and the runtime `launch` (spec 2026-10-11 §5.4).
5
+ *
6
+ * ## Opening, and binding from the response
7
+ *
8
+ * `agent_session_open{mode, cwd, sessionId?, mcpServers?, redact?, delegation?}` sends one request —
9
+ * the params `AcpSession.open` (`buildSession`), `replayNativeSession` (`session/load`) and the Qoder
10
+ * resume path build, `_meta` included (`newSessionMetaFor`, decided from the initialize reading). The
11
+ * MCP server list arrives finished: the daemon validated the delegation lease (`buildSessionMcpServers`,
12
+ * which reads the clock) before it wrote the note, and the bearer in it is a `secretRef` token the
13
+ * executor substitutes into stdin only (`redact`, spec §13). The **response** binds the root
14
+ * (`bindRootSession`) and sets the config snapshot — no pre-bind buffer, no window. A failed open is
15
+ * reported (`processor.open_result`) and never retried as `session/new` here.
16
+ *
17
+ * ## Replies
18
+ *
19
+ * `agent_config_option_set` and `agent_steered` are RPC replies: unnumbered frames carrying the
20
+ * server's frame `id` from the note, built as `agentSessions.ts` builds them. A steer's blocks travel
21
+ * like a prompt's (`fill: {blocksRef}`); if the executor lost them, the same `agent_prompt_failed` note
22
+ * fails the steer.
23
+ *
24
+ * ## `steeredIds` (spec §5.5.4)
25
+ *
26
+ * {@link steeredIds}: steers whose line reached the agent and that are unanswered, plus the last
27
+ * {@link STEERED_HISTORY_LIMIT} the agent answered `injected`. A `promptRequired`, a refusal, or a
28
+ * line that never reached the agent is not steered: the server delivers that message the ordinary way.
29
+ */
30
+ import { newSessionMetaFor } from '../activityReporting.js';
31
+ import { flatChoicesOf, modesAlreadyRepresented, modelsAlreadyRepresented, normalizeConfigOptions, SYNTHESIZED_MODES_CONFIG_ID, SYNTHESIZED_MODEL_CONFIG_ID } from '../configOptions.js';
32
+ import { ASYNC_TASK_STOP_METHOD, mapSteerOutcome, STEER_METHOD } from '../steering.js';
33
+ import { emitEvent, isRecord } from './emit.js';
34
+ import { settlementErrorCode } from './jsonrpc.js';
35
+ import { sameCause } from './record.js';
36
+ import { onLaunchNote, onRootBound } from './runtime.js';
37
+ import { bindRootSession, closeErrorText, connected } from './session.js';
38
+ import { getOwn, putOwn, STEERED_HISTORY_LIMIT, } from './state.js';
39
+ import { closeRepair } from './sideChannel.js';
40
+ import { endHistory } from './updates.js';
41
+ const OPEN_METHODS = { new: 'session/new', load: 'session/load', resume: 'session/resume' };
42
+ export const NOT_INITIALIZED = 'The ACP connection has not finished initialize';
43
+ export const ALREADY_OPEN = 'An ACP session is already open on this connection';
44
+ export const NOT_OPEN = 'No ACP session is open on this connection';
45
+ function request(state, out, method, params, kind, ctx, extras = {}) {
46
+ const { id, cause } = out.request(method, params, extras);
47
+ const pending = { id, method, kind, cause, ...(ctx !== undefined ? { ctx } : {}) };
48
+ state.outbound[String(id)] = pending;
49
+ return pending;
50
+ }
51
+ function isStringArray(value) {
52
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
53
+ }
54
+ function delegationOf(value) {
55
+ if (value === undefined)
56
+ return null;
57
+ if (!isRecord(value))
58
+ return undefined;
59
+ const { expiresAtEpochMs, leaseId, bindingId, instanceId } = value;
60
+ if (!Number.isSafeInteger(expiresAtEpochMs) || typeof leaseId !== 'string' || leaseId === ''
61
+ || (bindingId !== undefined && typeof bindingId !== 'string') || (instanceId !== undefined && typeof instanceId !== 'string')
62
+ || (bindingId !== undefined) !== (instanceId !== undefined))
63
+ return undefined;
64
+ return {
65
+ expiresAtEpochMs: expiresAtEpochMs, leaseId,
66
+ ...(bindingId !== undefined ? { bindingId, instanceId: instanceId, renewalRevision: 0 } : {}),
67
+ };
68
+ }
69
+ // ---- open ---------------------------------------------------------------------------------------
70
+ /** `agent_session_open{mode, cwd, sessionId?, mcpServers?, redact?, delegation?}`. */
71
+ export function onOpenNote(state, note, out) {
72
+ const { mode, cwd, sessionId, mcpServers, redact } = note;
73
+ const delegation = delegationOf(note.delegation);
74
+ if ((mode !== 'new' && mode !== 'load' && mode !== 'resume') || typeof cwd !== 'string' || cwd === ''
75
+ || (mode === 'new' ? sessionId !== undefined : typeof sessionId !== 'string' || sessionId === '')
76
+ || (mcpServers !== undefined && !Array.isArray(mcpServers)) || (redact !== undefined && !isStringArray(redact))
77
+ || delegation === undefined) {
78
+ state.diagnostics.droppedNotes += 1;
79
+ return;
80
+ }
81
+ const refuse = (error) => out.unjournaled({ t: 'processor.open_result', ok: false, mode, error });
82
+ if (!connected(state))
83
+ return refuse(closeErrorText(state));
84
+ if (state.initialize.status !== 'ready')
85
+ return refuse(NOT_INITIALIZED);
86
+ if (state.open.status === 'pending' || state.open.status === 'open')
87
+ return refuse(ALREADY_OPEN);
88
+ const meta = mode === 'resume' ? undefined : newSessionMetaFor(state.initialize.facts.agentInfo?.name);
89
+ const servers = mcpServers ?? [];
90
+ const params = mode === 'new'
91
+ ? { cwd, mcpServers: servers, ...(meta ? { _meta: meta } : {}) }
92
+ : mode === 'load'
93
+ ? { sessionId, cwd, mcpServers: servers, ...(meta ? { _meta: meta } : {}) }
94
+ : { sessionId, cwd, mcpServers: servers };
95
+ const pending = request(state, out, OPEN_METHODS[mode], params, 'open', { mode, ...(sessionId !== undefined ? { sessionId } : {}), ...(delegation ? { delegation } : {}) }, redact !== undefined && redact.length > 0 ? { redact } : {});
96
+ state.open = { status: 'pending', requestId: pending.id, mode };
97
+ if (mode === 'load')
98
+ state.history = { rootSessionId: sessionId, scopes: {}, messages: [], droppedCount: 0 };
99
+ }
100
+ /** The open request settled: on success the response binds the root session, now. */
101
+ export function settleOpen(state, entry, settlement, out) {
102
+ const ctx = entry.ctx;
103
+ if (state.open.status !== 'pending' || state.open.requestId !== entry.id)
104
+ return;
105
+ const { mode } = ctx;
106
+ if (settlement.outcome !== 'result') {
107
+ const code = settlementErrorCode(settlement);
108
+ const error = settlement.outcome === 'error' ? settlement.error.message : closeErrorText(state);
109
+ state.open = { status: 'failed', mode, error, ...(code !== undefined ? { code } : {}) };
110
+ endHistory(state, out, false);
111
+ // A failed open closes the attached session, and its repair with it (`AcpSession.close`).
112
+ closeRepair(state);
113
+ out.unjournaled({ t: 'processor.open_result', ok: false, mode, error, ...(code !== undefined ? { code } : {}) });
114
+ return;
115
+ }
116
+ const result = isRecord(settlement.result) ? settlement.result : {};
117
+ const nativeSessionId = mode === 'new' ? (typeof result.sessionId === 'string' ? result.sessionId : '') : ctx.sessionId;
118
+ if (nativeSessionId.length > 0) {
119
+ bindRootSession(state, nativeSessionId);
120
+ onRootBound(state, out, nativeSessionId);
121
+ }
122
+ state.configOptions = normalizeConfigOptions(result.configOptions, result.modes, result.models);
123
+ state.delegation = ctx.delegation ?? null;
124
+ state.open = { status: 'open', mode };
125
+ out.unjournaled({
126
+ t: 'processor.open_result', ok: true, mode,
127
+ nativeSessionId: nativeSessionId.length > 0 ? nativeSessionId : null,
128
+ configOptions: state.configOptions,
129
+ });
130
+ }
131
+ /** `historyEnd{}` — the daemon's decision that a load's history is over (analysis B A4). */
132
+ export function onHistoryEndNote(state, out) {
133
+ if (!state.history) {
134
+ state.diagnostics.droppedNotes += 1;
135
+ return;
136
+ }
137
+ endHistory(state, out, true);
138
+ }
139
+ /** Usable for a session request: connected, and the open succeeded with a root. */
140
+ function sessionReady(state) {
141
+ if (!connected(state))
142
+ return closeErrorText(state);
143
+ if (state.open.status !== 'open' || state.rootSessionId === null)
144
+ return NOT_OPEN;
145
+ return undefined;
146
+ }
147
+ /** `agent_set_config_option{id, configId, value}` — `setConfigOptionRequest`'s request half. */
148
+ export function onSetConfigOptionNote(state, note, out) {
149
+ const { id, configId, value } = note;
150
+ if (typeof id !== 'string' || typeof configId !== 'string' || (typeof value !== 'string' && typeof value !== 'boolean')) {
151
+ state.diagnostics.droppedNotes += 1;
152
+ return;
153
+ }
154
+ const fail = (error) => out.unjournaled({ t: 'agent_config_option_set', id, ok: false, error });
155
+ const unusable = sessionReady(state);
156
+ if (unusable !== undefined)
157
+ return fail(unusable);
158
+ const sessionId = state.rootSessionId;
159
+ const ctx = { id, configId, value };
160
+ if (configId === SYNTHESIZED_MODES_CONFIG_ID) {
161
+ if (typeof value !== 'string')
162
+ return fail(`[acp] ${SYNTHESIZED_MODES_CONFIG_ID} takes a mode id (a string), got ${typeof value}`);
163
+ request(state, out, 'session/set_mode', { sessionId, modeId: value }, 'config', ctx);
164
+ return;
165
+ }
166
+ // 合成的模型控件走 unstable 的专用 RPC,与 set_mode 同一条保留 id 的路由规则。hermes 回合进行中
167
+ // 会拒绝("Session is busy"),那正是 settle 的错误支要说的话。
168
+ if (configId === SYNTHESIZED_MODEL_CONFIG_ID) {
169
+ if (typeof value !== 'string')
170
+ return fail(`[acp] ${SYNTHESIZED_MODEL_CONFIG_ID} takes a model id (a string), got ${typeof value}`);
171
+ request(state, out, 'session/set_model', { sessionId, modelId: value }, 'config', ctx);
172
+ return;
173
+ }
174
+ request(state, out, 'session/set_config_option', typeof value === 'boolean' ? { sessionId, configId, type: 'boolean', value } : { sessionId, configId, value }, 'config', ctx);
175
+ }
176
+ /** The set settled — `setConfigOptionRequest`'s snapshot rules, then `agent_config_option_set`. */
177
+ export function settleConfig(state, entry, settlement, out) {
178
+ const { id, configId, value } = entry.ctx;
179
+ if (settlement.outcome !== 'result') {
180
+ out.unjournaled({ t: 'agent_config_option_set', id, ok: false,
181
+ error: settlement.outcome === 'error' ? settlement.error.message : closeErrorText(state) });
182
+ return;
183
+ }
184
+ const current = state.configOptions ?? [];
185
+ let next;
186
+ if (entry.method === 'session/set_mode') {
187
+ next = current.map((option) => (option.id === configId && option.type === 'select' ? { ...option, currentValue: value } : option));
188
+ }
189
+ else if (entry.method === 'session/set_model') {
190
+ // hermes 的 SetSessionModelResponse 是空对象(切完不回快照、也不推更新),与 set_mode 同款:
191
+ // 本地把 currentValue 换成刚设的值;agent 若将来回了新状态,也只会被下一次 open 快照纠回真相。
192
+ next = current.map((option) => (option.id === configId && option.type === 'select' ? { ...option, currentValue: value } : option));
193
+ }
194
+ else {
195
+ const result = isRecord(settlement.result) ? settlement.result : {};
196
+ const reNormalized = normalizeConfigOptions(result.configOptions, null);
197
+ // 两个合成控件(modes、model)都不在 agent 自己的 configOptions 里,re-normalize 会把它们丢掉;
198
+ // 只要 agent 的新快照里还没有能替代它们的控件,就原样携带。
199
+ next = reNormalized.concat(current.filter((option) => (option.id === SYNTHESIZED_MODES_CONFIG_ID
200
+ && !modesAlreadyRepresented(reNormalized, flatChoicesOf(option) ?? []))
201
+ || (option.id === SYNTHESIZED_MODEL_CONFIG_ID && !modelsAlreadyRepresented(reNormalized))));
202
+ }
203
+ state.configOptions = next;
204
+ out.unjournaled({ t: 'agent_config_option_set', id, ok: true, configOptions: next });
205
+ }
206
+ // ---- steering -----------------------------------------------------------------------------------
207
+ /** `agent_steer{id, blocksRef, priority?}` — `steerRequest`, blocks filled by the executor. */
208
+ export function onSteerNote(state, note, out) {
209
+ const { id, blocksRef, priority } = note;
210
+ if (typeof id !== 'string' || id === '' || typeof blocksRef !== 'string' || blocksRef === ''
211
+ || (priority !== undefined && typeof priority !== 'string')) {
212
+ state.diagnostics.droppedNotes += 1;
213
+ return;
214
+ }
215
+ const unusable = sessionReady(state);
216
+ if (unusable !== undefined) {
217
+ out.unjournaled({ t: 'agent_steered', id, ok: false, error: unusable });
218
+ return;
219
+ }
220
+ if (getOwn(state.steers, id)) {
221
+ state.diagnostics.droppedNotes += 1;
222
+ return;
223
+ }
224
+ const pending = request(state, out, STEER_METHOD, {
225
+ sessionId: state.rootSessionId,
226
+ prompt: [],
227
+ _meta: { steering: { idleBehavior: 'promptRequired', ...(priority === undefined ? {} : { priority }) } },
228
+ }, 'steer', { id }, { fill: { blocksRef } });
229
+ putOwn(state.steers, id, { requestId: pending.id, cause: pending.cause, sent: false });
230
+ }
231
+ /** A `down` record: a queued steer's line reached the agent. */
232
+ export function onSteerDown(state, record) {
233
+ const cause = record.annotation?.cause;
234
+ if (!cause)
235
+ return;
236
+ for (const steer of Object.values(state.steers)) {
237
+ if (!steer.sent && sameCause(cause, steer.cause))
238
+ steer.sent = true;
239
+ }
240
+ }
241
+ function rememberSteered(state, id) {
242
+ state.steered.push(id);
243
+ if (state.steered.length > STEERED_HISTORY_LIMIT)
244
+ state.steered.splice(0, state.steered.length - STEERED_HISTORY_LIMIT);
245
+ }
246
+ /** The steer settled: `agent_steered`, with `mapSteerOutcome`'s narrowing (a throw is a failed reply). */
247
+ export function settleSteer(state, entry, settlement, out) {
248
+ const { id } = entry.ctx;
249
+ delete state.steers[id];
250
+ if (settlement.outcome !== 'result') {
251
+ out.unjournaled({ t: 'agent_steered', id, ok: false,
252
+ error: settlement.outcome === 'error' ? settlement.error.message : closeErrorText(state) });
253
+ return;
254
+ }
255
+ let outcome;
256
+ try {
257
+ outcome = mapSteerOutcome(settlement.result);
258
+ }
259
+ catch (error) {
260
+ out.unjournaled({ t: 'agent_steered', id, ok: false, error: error instanceof Error ? error.message : String(error) });
261
+ return;
262
+ }
263
+ if (outcome === 'injected')
264
+ rememberSteered(state, id);
265
+ out.unjournaled({ t: 'agent_steered', id, ok: true, outcome });
266
+ }
267
+ /** `agent_prompt_failed` for a steer whose blocks were gone: it never reached the agent. */
268
+ export function failUnsentSteer(state, entry, error, out) {
269
+ const { id } = entry.ctx;
270
+ const steer = getOwn(state.steers, id);
271
+ if (!steer || steer.sent)
272
+ return false;
273
+ delete state.outbound[String(entry.id)];
274
+ delete state.steers[id];
275
+ out.unjournaled({ t: 'agent_steered', id, ok: false, error });
276
+ return true;
277
+ }
278
+ /** `agent_opened.steeredIds` (spec §5.5.4): see the file header. */
279
+ export function steeredIds(state) {
280
+ const inFlight = Object.entries(state.steers).filter(([, steer]) => steer.sent).map(([id]) => id);
281
+ return [...state.steered, ...inFlight];
282
+ }
283
+ // ---- background tasks, delegation, launch -------------------------------------------------------
284
+ /** `agent_async_task_stop{asyncTaskId}` — `asyncTaskStopRequest`; its answer is diagnostics only. */
285
+ export function onAsyncTaskStopNote(state, note, out) {
286
+ const { asyncTaskId } = note;
287
+ if (typeof asyncTaskId !== 'string' || !asyncTaskId.trim() || sessionReady(state) !== undefined) {
288
+ state.diagnostics.droppedNotes += 1;
289
+ return;
290
+ }
291
+ request(state, out, ASYNC_TASK_STOP_METHOD, { sessionId: state.rootSessionId, asyncTaskId }, 'rpc', undefined);
292
+ }
293
+ /**
294
+ * `agent_delegation_renewed{expiresAtEpochMs, renewalRevision}` — a renewal the daemon accepted
295
+ * (`agent_delegation_renew`'s checks read the clock, so the decision is the daemon's; the note is
296
+ * its record).
297
+ */
298
+ export function onDelegationRenewedNote(state, note) {
299
+ const { expiresAtEpochMs, renewalRevision } = note;
300
+ const delegation = state.delegation;
301
+ if (!delegation || delegation.bindingId === undefined || !Number.isSafeInteger(expiresAtEpochMs) || !Number.isSafeInteger(renewalRevision)) {
302
+ state.diagnostics.droppedNotes += 1;
303
+ return;
304
+ }
305
+ delegation.expiresAtEpochMs = expiresAtEpochMs;
306
+ delegation.renewalRevision = renewalRevision;
307
+ }
308
+ /** `agent_delegation_refresh_pending{}` — a refreshed lease waits for the session to go idle. */
309
+ export function onDelegationRefreshPendingNote(state) {
310
+ if (!state.delegation) {
311
+ state.diagnostics.droppedNotes += 1;
312
+ return;
313
+ }
314
+ state.delegation.refreshPending = true;
315
+ }
316
+ /** `agent_launch{launch}` (see `runtime.ts`). */
317
+ export function onLaunch(state, note) {
318
+ if (!onLaunchNote(state, note.launch))
319
+ state.diagnostics.droppedNotes += 1;
320
+ }
321
+ /**
322
+ * `agent_install_outcome{ok, agentName, version, error?}` (Task 20): the adapter install an open had to run first finished within
323
+ * the open's budget (spec §6.2). The daemon writes it right after `agent_launch`; it becomes one past-tense `step` on the session's
324
+ * timeline, numbered like any other event — the words the manager used to journal itself (interface-contracts, 15a/15c).
325
+ */
326
+ export function onInstallOutcomeNote(state, note, out) {
327
+ const { ok, agentName, version, error } = note;
328
+ if (typeof ok !== 'boolean' || typeof agentName !== 'string' || typeof version !== 'string' || (!ok && typeof error !== 'string')) {
329
+ state.diagnostics.droppedNotes += 1;
330
+ return;
331
+ }
332
+ emitEvent(state, out, {
333
+ eventName: 'step',
334
+ payload: ok
335
+ ? { title: 'Agent adapter', message: `Installed ${agentName} adapter ${version}`, tone: 'neutral' }
336
+ : { title: 'Agent adapter', message: `Could not install ${agentName} adapter ${version}`, detail: error, tone: 'error' },
337
+ });
338
+ }
339
+ /**
340
+ * `agent_output_gap{detail}` (Task 21): this session numbers on in a transport other than the one an earlier life of the ref ran in,
341
+ * and that life's unacknowledged output could not be read (keeper-acp unreachable while the session runs in the daemon) — its frames
342
+ * may never reach the server. The daemon seeded the seq above everything that life may have numbered (spec §5.5), so nothing is
343
+ * reused; this past-tense `step` says that output may be missing.
344
+ */
345
+ export function onOutputGapNote(state, note, out) {
346
+ const { detail } = note;
347
+ if (typeof detail !== 'string' || detail.length === 0) {
348
+ state.diagnostics.droppedNotes += 1;
349
+ return;
350
+ }
351
+ emitEvent(state, out, {
352
+ eventName: 'step',
353
+ payload: { title: 'Agent session', message: 'Earlier output of this session could not be recovered', detail, tone: 'error' },
354
+ });
355
+ }
@@ -0,0 +1,26 @@
1
+ import { validateWireEvent } from '../validate.js';
2
+ export function nextSeq(state) {
3
+ state.seq += 1;
4
+ return state.seq;
5
+ }
6
+ /**
7
+ * A journaled `agent_event` (the wire frame minus `sessionRef`, which the projection adds). Returns
8
+ * whether it was emitted.
9
+ */
10
+ export function emitEvent(state, out, event) {
11
+ if (validateWireEvent(event)) {
12
+ state.diagnostics.invalidEvents += 1;
13
+ return false;
14
+ }
15
+ out.frame({
16
+ t: 'agent_event',
17
+ seq: nextSeq(state),
18
+ eventName: event.eventName,
19
+ payload: event.payload,
20
+ ...(event.acpSessionId ? { acpSessionId: event.acpSessionId } : {}),
21
+ });
22
+ return true;
23
+ }
24
+ export function isRecord(value) {
25
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Synchronous JSON-RPC for the processor: id allocation, message classification, response
3
+ * correlation, and the exact bytes of every line it writes.
4
+ *
5
+ * ## Why the processor does this itself
6
+ *
7
+ * Today the SDK's `Connection` owns JSON-RPC, and its correlation is a per-connection counter from 0
8
+ * kept in a `Map` that dies with the process. Re-attaching to a still-running adapter with a fresh
9
+ * connection therefore reuses ids, and a late response to a previous daemon's `session/prompt`
10
+ * resolves whatever new request happens to hold the same number — measured in
11
+ * `docs/superpowers/reports/2026-10-11-keepers-analysis/experiments/exp-reattach.mjs naive`, where the
12
+ * new connection's third `_test/slow` call resolved to `{"stopReason":"end_turn"}`.
13
+ *
14
+ * Here:
15
+ * - every outbound request id is `o * 16 + i` ({@link outboundRequestId}) — numeric, strictly
16
+ * increasing along the log, and therefore never reused by any daemon generation reading the same
17
+ * log;
18
+ * - a response is matched only against the processor's own pending table, keyed by that id. An id
19
+ * the table does not hold is an **orphan**: it is counted and never attributed to anything else.
20
+ *
21
+ * ## Same semantics as the SDK, on purpose
22
+ *
23
+ * Every shape below mirrors `@agentclientprotocol/sdk` 1.4.0 `dist/jsonrpc.js` / `dist/stream.js`, so
24
+ * moving a session onto the processor changes nothing an adapter or the server can observe:
25
+ * - classification follows `receiveWireMessage`/`receiveMessage` (`isRequestMessage`,
26
+ * `isNotificationMessage`, `isResponseShapedMessage`, `isJsonRpcId`);
27
+ * - an unparseable line answers `-32700 Parse error`, a non-message `-32600 Invalid request`, both
28
+ * with `id: null` (`protocolErrorResponse`);
29
+ * - a response-shaped message for a pending id that is not a valid response settles that request
30
+ * as `RequestError.invalidRequest(response)` (`handleResponse`);
31
+ * - an error response settles with `{code, message, data}` exactly as `new RequestError(code, message,
32
+ * data)` would, and {@link settlementErrorCode} applies `jsonRpcErrorCode`'s rule to it;
33
+ * - an unhandled inbound request answers `RequestError.methodNotFound(method)`, a zod failure
34
+ * `RequestError.invalidParams(error.format())`.
35
+ */
36
+ import { jsonRpcErrorCodeOf } from '../initializeResponse.js';
37
+ export const CANCEL_REQUEST_METHOD = '$/cancel_request';
38
+ /** Requests one record may send: their `i` ranges over `[0, 16)` so `o * 16 + i` is collision-free. */
39
+ export const COMMANDS_PER_RECORD = 16;
40
+ /**
41
+ * Commands of any kind one record may produce. Responses and notifications take no id, so only the
42
+ * first 16 commands may be requests; the rest (e.g. an interrupt cancelling 64 pending permission
43
+ * cards) only need a unique `(o, i)` cause for idempotency.
44
+ */
45
+ export const MAX_COMMANDS_PER_RECORD = 256;
46
+ export class CommandBudgetExceededError extends Error {
47
+ }
48
+ /**
49
+ * The JSON-RPC id of the request a record's `i`-th command sends (spec §5.4).
50
+ *
51
+ * Throws rather than wrapping: a 17th command from one record would collide with the next record's
52
+ * first id, which is precisely the defect this scheme exists to make impossible.
53
+ */
54
+ export function outboundRequestId(cause) {
55
+ const { o, i } = cause;
56
+ if (!Number.isSafeInteger(o) || o < 0)
57
+ throw new CommandBudgetExceededError(`record offset ${o} is not a non-negative integer`);
58
+ if (!Number.isInteger(i) || i < 0 || i >= COMMANDS_PER_RECORD) {
59
+ throw new CommandBudgetExceededError(`record ${o} produced command #${i}; at most ${COMMANDS_PER_RECORD} commands per record`);
60
+ }
61
+ const id = o * COMMANDS_PER_RECORD + i;
62
+ if (!Number.isSafeInteger(id))
63
+ throw new CommandBudgetExceededError(`record offset ${o} is too large for a safe JSON-RPC id`);
64
+ return id;
65
+ }
66
+ function isRecord(value) {
67
+ return typeof value === 'object' && value !== null;
68
+ }
69
+ export function isJsonRpcId(value) {
70
+ return value === null || typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value));
71
+ }
72
+ function isEnvelope(value) {
73
+ return isRecord(value) && value.jsonrpc === '2.0';
74
+ }
75
+ function isRequestMessage(value) {
76
+ return isEnvelope(value) && 'id' in value && typeof value.method === 'string' && isJsonRpcId(value.id);
77
+ }
78
+ function isNotificationMessage(value) {
79
+ return isEnvelope(value) && !('id' in value) && typeof value.method === 'string';
80
+ }
81
+ function isResponseShaped(value) {
82
+ return !('method' in value) && ('id' in value || 'result' in value || 'error' in value);
83
+ }
84
+ function isErrorObject(value) {
85
+ return isRecord(value) && typeof value.code === 'number' && Number.isInteger(value.code) && typeof value.message === 'string';
86
+ }
87
+ /** The SDK's `isResponseMessage`: exactly one of `result`/`error`, a valid id, a well-formed error. */
88
+ function isValidResponse(value) {
89
+ if (!isEnvelope(value) || 'method' in value)
90
+ return false;
91
+ if (!('id' in value) || !isJsonRpcId(value.id))
92
+ return false;
93
+ const hasResult = Object.hasOwn(value, 'result');
94
+ const hasError = Object.hasOwn(value, 'error');
95
+ if (hasResult === hasError)
96
+ return false;
97
+ return !hasError || isErrorObject(value.error);
98
+ }
99
+ /** Classify one adapter stdout line the way the SDK's stream + connection would. */
100
+ export function classifyLine(line) {
101
+ const trimmed = line.trim();
102
+ if (!trimmed)
103
+ return { type: 'empty' };
104
+ let message;
105
+ try {
106
+ message = JSON.parse(trimmed);
107
+ }
108
+ catch {
109
+ return { type: 'parse_error' };
110
+ }
111
+ if (Array.isArray(message))
112
+ return { type: 'batch', raw: message };
113
+ if (!isRecord(message))
114
+ return { type: 'invalid', raw: message };
115
+ if (isRequestMessage(message)) {
116
+ return { type: 'request', id: message.id, method: message.method, params: message.params, raw: message };
117
+ }
118
+ if (isNotificationMessage(message)) {
119
+ return { type: 'notification', method: message.method, params: message.params, raw: message };
120
+ }
121
+ if (isResponseShaped(message)) {
122
+ return 'id' in message ? { type: 'response', id: message.id, raw: message } : { type: 'unaddressed', raw: message };
123
+ }
124
+ return { type: 'invalid', raw: message };
125
+ }
126
+ /** Turn a response for a pending id into its settlement — `handleResponse`'s three branches. */
127
+ export function settlementOf(response) {
128
+ if (!isValidResponse(response))
129
+ return { outcome: 'error', error: invalidRequest(response) };
130
+ if (Object.hasOwn(response, 'result'))
131
+ return { outcome: 'result', result: response.result };
132
+ const { code, message, data } = response.error;
133
+ return { outcome: 'error', error: { code, message, data } };
134
+ }
135
+ /** `jsonRpcErrorCode(err)` for a settlement: the code an agent answered with, if a frame may carry it. */
136
+ export function settlementErrorCode(settlement) {
137
+ return settlement.outcome === 'error' ? jsonRpcErrorCodeOf(settlement.error.code) : undefined;
138
+ }
139
+ /** The key a numeric outbound id is stored under; only numbers can ever match our own ids. */
140
+ export function outboundKey(id) {
141
+ return typeof id === 'number' && Number.isSafeInteger(id) ? String(id) : undefined;
142
+ }
143
+ /** The key an inbound (agent-allocated) id is stored under — type-tagged, as the SDK's `Map` keys are. */
144
+ export function inboundKey(id) {
145
+ return JSON.stringify(id);
146
+ }
147
+ // ---- error objects, exactly as `RequestError.<factory>(…).toErrorResponse()` spells them ----------
148
+ export function parseError() {
149
+ return { code: -32700, message: 'Parse error', data: undefined };
150
+ }
151
+ export function invalidRequest(data) {
152
+ return { code: -32600, message: 'Invalid request', data };
153
+ }
154
+ export function methodNotFound(method) {
155
+ return { code: -32601, message: `"Method not found": ${method}`, data: { method } };
156
+ }
157
+ export function invalidParams(data) {
158
+ return { code: -32602, message: 'Invalid params', data };
159
+ }
160
+ // ---- lines, with the SDK's key order (`{jsonrpc, id, method, params}`, `{jsonrpc, id, ...result}`) ----
161
+ export function requestLine(id, method, params) {
162
+ return JSON.stringify({ jsonrpc: '2.0', id, method, params });
163
+ }
164
+ export function notificationLine(method, params) {
165
+ return JSON.stringify({ jsonrpc: '2.0', method, params });
166
+ }
167
+ export function resultLine(id, result) {
168
+ return JSON.stringify({ jsonrpc: '2.0', id, result: result ?? null });
169
+ }
170
+ export function errorLine(id, error) {
171
+ return JSON.stringify({ jsonrpc: '2.0', id, error });
172
+ }
@@ -0,0 +1,49 @@
1
+ import { causeKey } from './record.js';
2
+ export class MemoryRawLog {
3
+ clock;
4
+ stdin;
5
+ records = [];
6
+ downsByCause = new Map();
7
+ /**
8
+ * @param clock the keeper-receive time stamped on each appended record.
9
+ * @param stdin where `writeDown` delivers the line, standing in for the adapter's stdin.
10
+ */
11
+ constructor(clock, stdin = () => { }) {
12
+ this.clock = clock;
13
+ this.stdin = stdin;
14
+ }
15
+ /** The offset the next record will get (one past the last). */
16
+ get head() {
17
+ return this.records.length;
18
+ }
19
+ /** The spawn this "keeper" runs now (from its own `proc` records), or `null`. */
20
+ running = null;
21
+ append(entry) {
22
+ const record = { ...entry, o: this.records.length, t: this.clock() };
23
+ this.records.push(record);
24
+ if (record.kind === 'proc')
25
+ this.running = record.proc.type === 'spawn' ? record.proc.spawnId : null;
26
+ if (record.kind === 'down' && record.annotation)
27
+ this.downsByCause.set(causeKey(record.annotation.cause), record);
28
+ return record;
29
+ }
30
+ /** Records with `o >= from`, at most `limit` of them, in order. */
31
+ read(from, limit = Number.POSITIVE_INFINITY) {
32
+ return this.records.slice(Math.max(0, from), Math.min(this.records.length, Math.max(0, from) + limit));
33
+ }
34
+ hasDown(cause) {
35
+ return this.downsByCause.has(causeKey(cause));
36
+ }
37
+ writeDown(command) {
38
+ if (command.spawnId !== this.running)
39
+ return 'refused';
40
+ this.stdin(command.line);
41
+ this.append({
42
+ kind: 'down',
43
+ line: command.line,
44
+ annotation: { ...command.annotation, cause: command.cause, spawnId: command.spawnId },
45
+ ...(command.redact !== undefined ? { redact: command.redact } : {}),
46
+ });
47
+ return 'written';
48
+ }
49
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The permission-origin graph (Task 17a, moved here in 17b): which native sessions may ask for
3
+ * permission — the root, and children with a complete `subagent_spawned` path to it
4
+ * (`AcpSession.observePermissionOrigin`). Fed by every mapped `subagent_spawned` event, live or load
5
+ * history (`observeReplayEvent`), in log order.
6
+ */
7
+ import { getOwn, putOwn, UNRESOLVED_ORIGIN_LIMIT } from './state.js';
8
+ /**
9
+ * `AcpSession.observePermissionOrigin`, over plain records. `root` is the bound root session, or —
10
+ * inside a load's history window, before the load response binds it — the session being loaded.
11
+ */
12
+ export function observeSubagentSpawned(state, parent, child, root = state.rootSessionId) {
13
+ const reject = () => { state.diagnostics.rejectedRelations += 1; };
14
+ if (typeof parent !== 'string' || !parent.trim() || typeof child !== 'string' || !child.trim())
15
+ return reject();
16
+ if (child === parent || child === root)
17
+ return reject();
18
+ const existing = getOwn(state.permissionParents, child);
19
+ if (existing !== undefined) {
20
+ if (existing !== parent)
21
+ reject();
22
+ return;
23
+ }
24
+ for (let ancestor = parent; ancestor !== undefined; ancestor = getOwn(state.permissionParents, ancestor)) {
25
+ if (ancestor === child)
26
+ return reject();
27
+ }
28
+ const trusted = parent === root || getOwn(state.permissionOrigins, parent) === true;
29
+ if (!trusted && Object.keys(state.unresolvedPermissionOrigins).length >= UNRESOLVED_ORIGIN_LIMIT)
30
+ return reject();
31
+ putOwn(state.permissionParents, child, parent);
32
+ if (!trusted) {
33
+ putOwn(state.unresolvedPermissionOrigins, child, true);
34
+ return;
35
+ }
36
+ putOwn(state.permissionOrigins, child, true);
37
+ let promoted;
38
+ do {
39
+ promoted = false;
40
+ for (const candidate of Object.keys(state.unresolvedPermissionOrigins)) {
41
+ if (getOwn(state.permissionOrigins, getOwn(state.permissionParents, candidate))) {
42
+ delete state.unresolvedPermissionOrigins[candidate];
43
+ putOwn(state.permissionOrigins, candidate, true);
44
+ promoted = true;
45
+ }
46
+ }
47
+ } while (promoted);
48
+ }