@platless/axom-cli 0.5.9 → 0.6.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +25 -6
  2. package/dist/axom-patch/cli/README.md +129 -12
  3. package/dist/axom-patch/cli/account-limits/generate.py +15 -12
  4. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
  5. package/dist/axom-patch/cli/claude-account-limits.json +9 -9
  6. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  7. package/dist/axom-patch/cli/codex-account-limits.json +11 -11
  8. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  9. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  10. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  11. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  12. package/dist/axom-patch/cli/nightly-task.md +55 -13
  13. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  14. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  15. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  16. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  17. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  18. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
  19. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
  20. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  21. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  22. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  23. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  24. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  25. package/dist/axom-patch/cli/runtime.json +19 -19
  26. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  27. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  28. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  29. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  30. package/dist/axom-patch/cli/zcode-models.json +9 -10
  31. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
  32. package/dist/cli.js +11 -45
  33. package/dist/commands/auth.js +4 -1
  34. package/dist/commands/knowledge.js +47 -0
  35. package/dist/commands/update.js +126 -3
  36. package/dist/commands/workstation.js +101 -164
  37. package/dist/commands/workstationRegistrations.js +116 -0
  38. package/dist/credentialBroker.js +13 -2
  39. package/dist/credentials.js +16 -0
  40. package/dist/install/claude/commands.js +15 -15
  41. package/dist/keepers/terminal/keeper.mjs +1089 -673
  42. package/dist/keepers/terminal/manifest.json +4 -4
  43. package/dist/mcp.js +1 -1
  44. package/dist/native/darwin-x64/axom-quic +0 -0
  45. package/dist/workstation/acp/acpAgents.js +113 -102
  46. package/dist/workstation/acp/acpClient.js +26 -0
  47. package/dist/workstation/acp/builds/install/npm.js +99 -10
  48. package/dist/workstation/acp/initializeResponse.js +6 -1
  49. package/dist/workstation/acp/managedPatches.js +189 -25
  50. package/dist/workstation/acp/managedRuntime.js +314 -0
  51. package/dist/workstation/acp/outbox.js +12 -5
  52. package/dist/workstation/acp/patchAssets.js +40 -0
  53. package/dist/workstation/acp/permission.js +2 -2
  54. package/dist/workstation/acp/processor/checkpoint.js +26 -34
  55. package/dist/workstation/acp/processor/control.js +7 -80
  56. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  57. package/dist/workstation/acp/processor/runtime.js +5 -81
  58. package/dist/workstation/acp/processor/session.js +54 -82
  59. package/dist/workstation/acp/processor/state.js +7 -13
  60. package/dist/workstation/acp/processor/step.js +3 -20
  61. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  62. package/dist/workstation/acp/processor/transport/local.js +19 -3
  63. package/dist/workstation/acp/processor/updates.js +156 -105
  64. package/dist/workstation/acp/processorSession.js +20 -173
  65. package/dist/workstation/acp/registry/catalog.js +65 -39
  66. package/dist/workstation/acp/registry/launch.js +16 -1
  67. package/dist/workstation/acp/switching.js +0 -3
  68. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  69. package/dist/workstation/acp/test/keeperWorld.js +32 -3
  70. package/dist/workstation/acp/test/raw/sources.js +16 -130
  71. package/dist/workstation/acp/test/realAdapter.js +1 -47
  72. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  73. package/dist/workstation/agentFlow.js +1 -1
  74. package/dist/workstation/agentSessions.js +114 -166
  75. package/dist/workstation/channels/agent.js +9 -1
  76. package/dist/workstation/channels/computer.js +27 -4
  77. package/dist/workstation/channels/management.js +130 -15
  78. package/dist/workstation/channels/url.js +1 -1
  79. package/dist/workstation/config.js +33 -9
  80. package/dist/workstation/connection/encryptedChannel.js +4 -1
  81. package/dist/workstation/control/bindingStore.js +204 -0
  82. package/dist/workstation/control/commands.js +125 -0
  83. package/dist/workstation/control/daemonIdentity.js +143 -0
  84. package/dist/workstation/control/deviceCommands.js +191 -0
  85. package/dist/workstation/control/deviceProtocol.js +68 -0
  86. package/dist/workstation/control/enrollment.js +106 -0
  87. package/dist/workstation/control/inspect.js +71 -0
  88. package/dist/workstation/control/locks.js +110 -0
  89. package/dist/workstation/control/protocol.js +40 -0
  90. package/dist/workstation/control/restartGuard.js +46 -0
  91. package/dist/workstation/control/rootFormat.js +16 -0
  92. package/dist/workstation/control/runtimeClient.js +152 -0
  93. package/dist/workstation/control/runtimeHost.js +235 -0
  94. package/dist/workstation/control/serverIdentity.js +35 -0
  95. package/dist/workstation/control/updateTransition.js +91 -0
  96. package/dist/workstation/daemon.js +266 -264
  97. package/dist/workstation/daemonctl.js +226 -25
  98. package/dist/workstation/desktopControl/audio.js +99 -0
  99. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  100. package/dist/workstation/desktopControl/automationJson.js +74 -0
  101. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  102. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  103. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  104. package/dist/workstation/desktopControl/automationSession.js +81 -0
  105. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  106. package/dist/workstation/desktopControl/capabilities.js +93 -0
  107. package/dist/workstation/desktopControl/captureModes.js +9 -0
  108. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  109. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  110. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  111. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  112. package/dist/workstation/desktopControl/helperProcess.js +602 -0
  113. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  114. package/dist/workstation/desktopControl/lobby.js +131 -0
  115. package/dist/workstation/desktopControl/macProvider.js +156 -0
  116. package/dist/workstation/desktopControl/media.js +331 -0
  117. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  118. package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
  119. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  120. package/dist/workstation/desktopControl/overview.js +58 -0
  121. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  122. package/dist/workstation/desktopControl/protocol.js +311 -0
  123. package/dist/workstation/desktopControl/provider.js +1 -0
  124. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  125. package/dist/workstation/desktopControl/rateController.js +226 -0
  126. package/dist/workstation/desktopControl/supervisor.js +1774 -0
  127. package/dist/workstation/desktopControl/transport.js +27 -0
  128. package/dist/workstation/desktopControl/wallpaper.js +98 -0
  129. package/dist/workstation/exec/policy.js +5 -0
  130. package/dist/workstation/files/client.js +10 -1
  131. package/dist/workstation/files/credentials.js +142 -0
  132. package/dist/workstation/files/host.js +9 -2
  133. package/dist/workstation/files/text.js +42 -8
  134. package/dist/workstation/journal.js +5 -2
  135. package/dist/workstation/keepers/acp/client.js +38 -2
  136. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  137. package/dist/workstation/keepers/kit/bundle.js +21 -17
  138. package/dist/workstation/keepers/kit/client.js +2 -2
  139. package/dist/workstation/keepers/kit/launcher.js +123 -7
  140. package/dist/workstation/keepers/kit/paths.js +2 -1
  141. package/dist/workstation/keepers/legacy.js +16 -2
  142. package/dist/workstation/keepers/policy.js +5 -34
  143. package/dist/workstation/keepers/terminal/client.js +115 -23
  144. package/dist/workstation/keepers/terminal/exec.js +6 -3
  145. package/dist/workstation/keepers/terminal/frames.js +73 -0
  146. package/dist/workstation/keepers/terminal/generations.js +5 -8
  147. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  148. package/dist/workstation/keepers/terminal/input.js +146 -0
  149. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  150. package/dist/workstation/keepers/terminal/main.js +1 -1
  151. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  152. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  153. package/dist/workstation/keepers/terminal/pty.js +1 -330
  154. package/dist/workstation/keepers/terminal/screen.js +246 -0
  155. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  156. package/dist/workstation/managedInstall.js +57 -0
  157. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  158. package/dist/workstation/protocol/agent.js +2 -15
  159. package/dist/workstation/protocol/management.js +113 -5
  160. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  161. package/dist/workstation/rd/artifact.js +120 -0
  162. package/dist/workstation/rd/control.js +117 -0
  163. package/dist/workstation/rd/display.js +29 -0
  164. package/dist/workstation/rd/host.js +213 -0
  165. package/dist/workstation/rd/migrationReadiness.js +46 -0
  166. package/dist/workstation/rd/provider.js +256 -0
  167. package/dist/workstation/rd/sessions.js +391 -0
  168. package/dist/workstation/rd/state.js +57 -0
  169. package/dist/workstation/rd/update.js +168 -0
  170. package/dist/workstation/rd/upkeep.js +80 -0
  171. package/dist/workstation/register.js +27 -37
  172. package/dist/workstation/registrations/enrollment.js +250 -0
  173. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  174. package/dist/workstation/registrations/migration.js +356 -0
  175. package/dist/workstation/registrations/model.js +34 -0
  176. package/dist/workstation/registrations/removal.js +222 -0
  177. package/dist/workstation/registrations/store.js +737 -0
  178. package/dist/workstation/runtime/desktopBinding.js +20 -0
  179. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  180. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  181. package/dist/workstation/runtime/deviceServices.js +117 -0
  182. package/dist/workstation/runtime/events.js +20 -0
  183. package/dist/workstation/runtime/ownership.js +100 -0
  184. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  185. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  186. package/dist/workstation/runtime/removalActivity.js +184 -0
  187. package/dist/workstation/service/installer.js +197 -14
  188. package/dist/workstation/service/launchd.js +2 -0
  189. package/dist/workstation/service/systemd.js +1 -0
  190. package/dist/workstation/storageMetrics.js +118 -0
  191. package/dist/workstation/systemMetrics.js +47 -92
  192. package/dist/workstation/toolchain.js +94 -0
  193. package/dist/workstation/tools/browser.js +28 -4
  194. package/dist/workstation/tools/client.js +8 -27
  195. package/dist/workstation/tools/info.js +6 -13
  196. package/dist/workstation/tools/terminalRouter.js +17 -172
  197. package/dist/workstation/tools/validation.js +55 -1
  198. package/dist/workstation/update/archiveEvidence.js +206 -0
  199. package/dist/workstation/update/artifact.js +2 -1
  200. package/dist/workstation/update/coordinator.js +34 -13
  201. package/dist/workstation/update/deviceCompatibility.js +119 -0
  202. package/dist/workstation/update/deviceCoordinator.js +369 -0
  203. package/dist/workstation/update/deviceHealth.js +104 -0
  204. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  205. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  206. package/dist/workstation/update/deviceRollback.js +78 -0
  207. package/dist/workstation/update/globalInstall.js +186 -0
  208. package/dist/workstation/update/legacyBridge.js +414 -0
  209. package/dist/workstation/update/legacyOwner.js +39 -0
  210. package/dist/workstation/update/npmInstaller.js +51 -25
  211. package/dist/workstation/update/privateDurability.js +61 -0
  212. package/dist/workstation/update/rdRollback.js +145 -0
  213. package/dist/workstation/update/recoveryPlan.js +124 -0
  214. package/dist/workstation/update/runUpdate.js +10 -3
  215. package/dist/workstation/update/taskRegistry.js +161 -3
  216. package/dist/workstation/update/watchdog.js +29806 -333
  217. package/dist/workstation/update/watchdogProcess.js +72 -0
  218. package/package.json +11 -7
  219. package/skills/axom/SKILL.md +11 -11
  220. package/skills/axom-definition/SKILL.md +3 -3
  221. package/skills/axom-platform/SKILL.md +3 -3
  222. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  223. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  224. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  225. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  226. package/dist/commands/guide.js +0 -38
  227. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  228. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  229. package/dist/workstation/acp/delegationMcp.js +0 -24
  230. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  231. package/dist/workstation/acp/qoderStartup.js +0 -45
  232. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  233. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  234. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  235. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  236. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  237. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  238. package/dist/workstation/qoderAdapter/contract.js +0 -78
  239. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  240. package/dist/workstation/qoderAdapter/history.js +0 -85
  241. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  242. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  243. package/dist/workstation/qoderAdapter/main.js +0 -95
  244. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  245. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  246. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  247. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  248. package/dist/workstation/qoderAdapter/profile.js +0 -93
  249. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  250. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  251. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  252. package/dist/workstation/qoderAdapter/session.js +0 -319
  253. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  254. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  255. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  256. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  257. package/dist/workstation/qoderAdapter/usage.js +0 -37
  258. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
@@ -1,18 +1,13 @@
1
1
  /**
2
2
  * A representative raw log that touches every piece of processor state Tasks 16–17b keep: initialize
3
- * facts and the Qoder profile, the open session bound from its response (new, then — after a respawn —
4
- * load with history and `historyEnd`), delegation and launch notes, config and steering round trips,
3
+ * facts and runtime diagnostics, the open session bound from its response (new, then — after a respawn —
4
+ * load with history and `historyEnd`), launch notes, config and steering round trips,
5
5
  * every kind of `session/update` route (root, draft, child), message synthesis including an
6
6
  * out-of-turn trailing chunk, async tasks, activity, both card kinds and the origin graph, turns
7
7
  * (completed, interrupted, cut by an exit), the 17a low-level notes, and the diagnostics.
8
8
  */
9
9
  import { createProcessor } from '../step.js';
10
10
  import { append, chunk, elicitationRequest, exitEntry, initializeResult, lastPromptId, lastRequestId, newSession, note, permissionRequest, prompt, respond, run, spawnEntry, subagentSpawned, up, update, } from './harness.js';
11
- export const QODER_PROFILE = {
12
- cliVersion: '1.1.61', sdkVersion: '0.1.0', executable: '/usr/local/bin/qodercli', persistent: true, agentMessaging: false,
13
- observerHealthy: true, completedContinuationEnabled: false, sendMessageExposed: false, mode: 'sdk-adapter', transport: 'process',
14
- capabilities: ['background_tasks_v1'],
15
- };
16
11
  export const LAUNCH = { source: 'registry', executable: '/usr/local/bin/agent', platform: 'darwin', architecture: 'arm64', transport: 'stdio' };
17
12
  export function representativeLog() {
18
13
  const session = newSession();
@@ -27,15 +22,13 @@ export function representativeLog() {
27
22
  agentCapabilities: { loadSession: true, sessionCapabilities: { list: {} } },
28
23
  authMethods: [{ id: 'login', name: 'Login', type: 'terminal', args: ['login'] }, { type: 'terminal' }],
29
24
  agentInfo: { name: '@agentclientprotocol/claude-agent-acp', version: '1.0.0' },
30
- _meta: { steering: { supported: true, timings: ['next'] }, qoderProfile: QODER_PROFILE },
25
+ _meta: { steering: { supported: true, timings: ['next'] } },
31
26
  })));
32
27
  step(
33
- // Before the open response: a profile candidate (kept), an update (no root yet: dropped).
34
- up({ jsonrpc: '2.0', method: '_axom/qoder/profile', params: { sessionId: 'S1', revision: 1,
35
- profile: { ...QODER_PROFILE, rootSessionId: 'S1', runtimeGeneration: 'g1', rootConfirmed: true, initialized: true } } }), note('agent_session_open', {
36
- mode: 'new', cwd: '/work', redact: ['secret:delegation'],
37
- mcpServers: [{ type: 'http', name: 'axom-delegation', url: 'https://x.test/mcp/delegation', headers: [{ name: 'Authorization', value: 'secret:delegation' }] }],
38
- delegation: { expiresAtEpochMs: 1_900_000_000_000, leaseId: 'lease-hash', bindingId: 'b-1', instanceId: 'i-1' },
28
+ // Before the open response: an update with no root yet is dropped.
29
+ note('agent_session_open', {
30
+ mode: 'new', cwd: '/work', redact: ['secret:mcp'],
31
+ mcpServers: [{ type: 'http', name: 'tools', url: 'https://x.test/mcp/tools', headers: [{ name: 'Authorization', value: 'secret:mcp' }] }],
39
32
  }), update('S1', { sessionUpdate: 'available_commands_update', availableCommands: [] }));
40
33
  step(respond(lastRequestId(session, 'session/new'), {
41
34
  sessionId: 'S1',
@@ -52,14 +45,14 @@ export function representativeLog() {
52
45
  step(note('agent_steer', { id: 'steer-1', blocksRef: 'steer-1', priority: 'next' }));
53
46
  step(respond(lastRequestId(session, '_session/steering'), { outcome: 'injected' }), respond(lastPromptId(session), { stopReason: 'end_turn' }),
54
47
  // Trailing, after the response: out of turn, closed before the next turn starts.
55
- chunk('S1', 'late words'), update('S1', { sessionUpdate: 'async_task_state_update', asyncTaskId: 'task-1', state: 'stopped' }), update('S1', { sessionUpdate: 'async_task_state_update', asyncTaskId: 'task-1', state: 'completed' }), note('agent_delegation_renewed', { expiresAtEpochMs: 1_900_000_100_000, renewalRevision: 1 }), prompt(session, 'two'), chunk('S1', 'thinking'), update('S1', { sessionUpdate: 'tool_call', toolCallId: 't2', title: 'Serve', kind: 'execute', rawInput: { command: 'serve', run_in_background: true } }), permissionRequest(10), note('agent_interrupt'));
48
+ chunk('S1', 'late words'), update('S1', { sessionUpdate: 'async_task_state_update', asyncTaskId: 'task-1', state: 'stopped' }), update('S1', { sessionUpdate: 'async_task_state_update', asyncTaskId: 'task-1', state: 'completed' }), prompt(session, 'two'), chunk('S1', 'thinking'), update('S1', { sessionUpdate: 'tool_call', toolCallId: 't2', title: 'Serve', kind: 'execute', rawInput: { command: 'serve', run_in_background: true } }), permissionRequest(10), note('agent_interrupt'));
56
49
  step(respond(lastPromptId(session), { stopReason: 'cancelled' }), prompt(session, 'three', { platformTurnId: 'turn-3' }), note('rpc.respond', { requestId: 5, result: { outcome: { outcome: 'cancelled' } } }));
57
50
  step(exitEntry(0), spawnEntry('spawn-b'));
58
51
  step(respond(lastRequestId(session, 'initialize'), initializeResult({ agentInfo: { name: 'other-agent', version: '2' } })));
59
52
  step(note('agent_launch', { launch: LAUNCH }), note('agent_session_open', { mode: 'load', cwd: '/work', sessionId: 'S1' }));
60
53
  step(update('S1', { sessionUpdate: 'user_message_chunk', content: { type: 'text', text: 'old question' } }), chunk('S1', 'old answer', 'h1'), subagentSpawned('S1', 'C9'), chunk('C9', 'old child'));
61
54
  step(respond(lastRequestId(session, 'session/load'), { configOptions: [] }));
62
- step(chunk('S1', 'qoder replays one more old chunk after its response'), note('historyEnd'));
55
+ step(chunk('S1', 'agent replays one more old chunk after its response'), note('historyEnd'));
63
56
  step(prompt(session, 'four'), chunk('S1', 'new'), note('historyEnd'));
64
57
  return { session, records: session.log.read(0) };
65
58
  }
@@ -19,6 +19,7 @@ import { AcpSession } from '../../../keepers/acp/session.js';
19
19
  import { KeeperMethodError } from '../../../keepers/kit/protocol.js';
20
20
  import { StreamLog } from '../../../keepers/kit/streamLog.js';
21
21
  import { DAEMON_FRAME_INDEX } from '../../outbox.js';
22
+ import { assertOwnedDirectory } from '../../../runtime/ownership.js';
22
23
  import { RawTruncatedError, checkpointBlob, rawRecordOf, } from './types.js';
23
24
  const DEFAULT_WINDOW = 32;
24
25
  /** Longest directory name used as is (file systems cap a name at 255 bytes); longer keys get a hashed name. */
@@ -43,12 +44,15 @@ export class LocalTransport {
43
44
  logLine;
44
45
  constructor(options) {
45
46
  this.options = options;
46
- this.root = path.join(options.configDir, 'agents', 'local');
47
+ this.root = options.outboxDirectory ?? path.join(options.configDir, 'agents', 'local');
47
48
  this.now = options.now ?? Date.now;
48
49
  this.logLine = options.log ?? (() => { });
49
50
  }
50
51
  open(sessionKey, label, baseOffset, orphan) {
51
52
  const dir = localSessionDir(this.root, sessionKey);
53
+ assertOwnedDirectory(dir, this.options.ownership, 'local-outbox');
54
+ if (this.options.ownership)
55
+ label = { ...label, registrationId: this.options.ownership.registrationId };
52
56
  if (path.basename(dir).startsWith('~')) {
53
57
  mkdirSync(dir, { recursive: true, mode: 0o700 });
54
58
  writeFileSync(path.join(dir, KEY_FILE), sessionKey, { mode: 0o600 });
@@ -71,12 +75,15 @@ export class LocalTransport {
71
75
  async importOutbox(key, records) {
72
76
  const sessionKey = sessionKeyOf(key);
73
77
  const dir = localSessionDir(this.root, sessionKey);
74
- if (this.entries.has(sessionKey) || existsSync(dir))
78
+ if (this.entries.has(sessionKey) || existsSync(dir)) {
79
+ assertOwnedDirectory(dir, this.options.ownership, 'local-outbox');
75
80
  return false;
81
+ }
76
82
  mkdirSync(this.root, { recursive: true, mode: 0o700 });
77
83
  const staging = path.join(this.root, `.staging-${randomUUID()}`);
78
84
  try {
79
85
  mkdirSync(staging, { mode: 0o700 });
86
+ assertOwnedDirectory(staging, this.options.ownership);
80
87
  if (path.basename(dir).startsWith('~'))
81
88
  writeFileSync(path.join(staging, KEY_FILE), sessionKey, { mode: 0o600 });
82
89
  const log = StreamLog.open({ dir: staging, now: this.now, immediateKinds: [] });
@@ -127,6 +134,13 @@ export class LocalTransport {
127
134
  catch {
128
135
  continue;
129
136
  }
137
+ try {
138
+ assertOwnedDirectory(path.join(this.root, name), this.options.ownership, 'local-outbox');
139
+ }
140
+ catch {
141
+ this.logLine('ACP local state skipped: registration owner is unknown or mismatched');
142
+ continue;
143
+ }
130
144
  if (!this.entries.has(key))
131
145
  keys.push(key);
132
146
  }
@@ -135,8 +149,10 @@ export class LocalTransport {
135
149
  entry(key) {
136
150
  const sessionKey = sessionKeyOf(key);
137
151
  const existing = this.entries.get(sessionKey);
138
- if (existing)
152
+ if (existing) {
153
+ assertOwnedDirectory(existing.dir, this.options.ownership, 'local-outbox');
139
154
  return existing;
155
+ }
140
156
  if (this.orphans().includes(sessionKey))
141
157
  return this.open(sessionKey, {}, undefined, true);
142
158
  throw new KeeperMethodError(AcpErrorCode.notFound, 'ACP session not found');
@@ -1,69 +1,6 @@
1
- /**
2
- * `session/update` → journaled `agent_event`s, one record at a time, in log order (Task 17b).
3
- *
4
- * ## One path, in log order (replaces `draftInterceptor` + the SDK's async chain)
5
- *
6
- * Today a draft variant or a child session's update is diverted by `interceptDraftUpdates` and
7
- * applied **synchronously** while the stream is read, and every other update travels the SDK's
8
- * **asynchronous** handler chain — so the two orders depend on pipe chunking (analysis B §0.1: the
9
- * same bytes, written at once or line by line, gave different event orders). Here both kinds are
10
- * handled on their own record, so the order of events is the order of the log, whatever the
11
- * chunking. What differs between them is only what `AcpSession` does with each, kept as it is:
12
- *
13
- * - **The root session's own ordinary update** is parsed with the SDK's `zSessionNotification` (as the
14
- * SDK's router does — so unknown keys are stripped and an unknown variant is dropped), mapped with
15
- * the root scope, and routed like `AcpSession.routeMappedEvent`: background-writer evidence and
16
- * message-boundary tracking for `agent_message` synthesis.
17
- * - **A draft variant (any session) or any update on another session** is mapped raw, with that
18
- * session's own scope, like `AcpSession.applyDivertedUpdate` → `emitEvent`: a child's events carry
19
- * `acpSessionId`, and none of them touch the root's message boundaries.
20
- *
21
- * Both feed the permission-origin graph (`subagent_spawned`) and the unfinished async-task set.
22
- *
23
- * ## Binding, and what replaced the pre-bind buffer
24
- *
25
- * The root is bound by the open **response** (`control.ts`). An update that arrives before any root
26
- * is bound — outside a load's history window — has no session to belong to and is dropped
27
- * (`droppedUpdates`). Today's 32-entry, 5-second pre-bind buffer existed because the SDK processed
28
- * the `session/new` response *after* an update the agent had written *after* it; in log order that
29
- * update follows the response and is applied normally (analysis B A3/C1; the raw captures in `tmp/`
30
- * show Claude's first `available_commands_update` after the response line).
31
- *
32
- * ## Load history
33
- *
34
- * Between a `session/load` request and the `historyEnd` note, every update (any session) is history — except a level update
35
- * ({@link LEVEL_UPDATES}) the loaded session sends once its response is in, which is folded live (Task 21):
36
- * folded with its own scopes and message boundaries exactly as `adoption.ts` `foldScopedReplayEvents`
37
- * folds a finished list — incrementally here — then validated and emitted **unnumbered** as
38
- * `processor.history_event` frames for the daemon's backfill, and observed for origins and
39
- * background writers (`AcpSession.observeReplayEvent`).
40
- *
41
- * ## `agent_message` synthesis and out-of-turn chunks
42
- *
43
- * `AcpSession`'s rule: an `agent_message` closes the streaming assistant message when its `messageId`
44
- * changes and when a turn ends (before the turn's `agent_turn_done`). One addition, decided for 17b:
45
- * chunks that arrive with **no turn running** — after a prompt's response, or an agent working on its
46
- * own — are emitted as they are (an `agent_event` never carries a turn id; that is what today's path
47
- * does with them too), and the message they open is closed **before the next turn starts** instead
48
- * of being merged into that turn's message when neither carries a `messageId`.
49
- *
50
- * ## The Qoder adapter boundaries (Task 17c)
51
- *
52
- * `AcpSession` passes events through three adapter boundaries, and so does the processor, over its
53
- * checkpointed data (`state.boundaries`), at the same points:
54
- * - the root's own update ({@link routeOwnEvent} = `routeMappedEvent`): the foreground-output boundary
55
- * may hold an unaddressed chunk back (certified qoder-cli), the terminal repair observes it, the
56
- * subwork boundary maps it (`supportsSubworkMeta`) — and only an unscoped mapped chunk tracks the
57
- * root's message boundary;
58
- * - everything `AcpSession.emitEvent` emits ({@link emitSessionEvent}): diverted updates, cards, and the
59
- * terminal repair's own events (`sideChannel.ts`) — observed by the repair, then subwork-mapped;
60
- * - load history (`observeReplayEvent`): observed by the repair.
61
- */
1
+ /** Session updates map in log order. Root chunks track streaming message boundaries; diverted and child updates retain their session address. Both feed the permission-origin graph and unfinished async-task state. Load history uses separate mapper scopes and emits unnumbered backfill events. */
62
2
  import { DRAFT_SESSION_UPDATE_NAMES } from '../draftSubagents.js';
63
3
  import { mapSessionUpdate, newMapperScopeData, SessionMapperState } from '../sessionUpdateMapper.js';
64
- import { certifiedRuntime, ForegroundOutputBoundary } from '../../qoderAdapter/foregroundOutput.js';
65
- import { SubworkBoundary, supportsSubworkMeta } from '../../qoderAdapter/subwork.js';
66
- import { TerminalRepairFold } from '../../qoderAdapter/backgroundTerminalFold.js';
67
4
  import { validateWireEvent } from '../validate.js';
68
5
  import { emitEvent, isRecord } from './emit.js';
69
6
  import { observeSubagentSpawned } from './origins.js';
@@ -80,6 +17,10 @@ const DRAFT_NAMES = new Set(DRAFT_SESSION_UPDATE_NAMES);
80
17
  const LEVEL_UPDATES = new Set([
81
18
  'available_commands_update', 'current_mode_update', 'config_option_update', 'usage_update', 'session_info_update',
82
19
  ]);
20
+ /** Only configuration snapshots: terminal usage, titles and actual output remain observable. */
21
+ const CLOSED_REPLAY_LEVELS = new Set([
22
+ 'available_commands_update', 'current_mode_update', 'config_option_update',
23
+ ]);
83
24
  const SESSION_NOTIFICATION = INBOUND_NOTIFICATION_SCHEMAS['session/update'];
84
25
  function stringField(payload, key) {
85
26
  const value = payload[key];
@@ -112,6 +53,15 @@ export function onSessionUpdate(state, params, out, t) {
112
53
  state.diagnostics.droppedUpdates += 1;
113
54
  return;
114
55
  }
56
+ // A load may replay and end a child that the host did not request as backfill.
57
+ // Its later configuration snapshot is still historical, not a live child event.
58
+ // Keep this narrow: unknown scopes and actual content/tool/lifecycle events retain
59
+ // their normal routing and ownership checks. A live spawn removes the tombstone.
60
+ if (sessionId !== root && getOwn(state.closedReplayScopes, sessionId) && CLOSED_REPLAY_LEVELS.has(String(update.sessionUpdate))) {
61
+ state.diagnostics.droppedUpdates += 1;
62
+ out.unjournaled({ t: 'processor.closed_replay_level', sessionId, sessionUpdate: update.sessionUpdate });
63
+ return;
64
+ }
115
65
  const variant = update.sessionUpdate;
116
66
  const draft = typeof variant === 'string' && DRAFT_NAMES.has(variant);
117
67
  if (!draft && sessionId === root) {
@@ -137,63 +87,128 @@ export function onSessionUpdate(state, params, out, t) {
137
87
  for (const event of events)
138
88
  emitSessionEvent(state, out, sessionId === root ? event : { ...event, acpSessionId: sessionId });
139
89
  }
140
- /** The agent `initialize` named (`AcpConnection.agentInfo`), if it is ready. */
141
- function agentInfo(state) {
142
- return state.initialize.status === 'ready' ? state.initialize.facts.agentInfo : undefined;
90
+ const CHUNK_CHURN = new WeakMap();
91
+ const CHUNK_TICKS = new WeakMap();
92
+ const canonical = (value) => { try {
93
+ return JSON.stringify(value) ?? 'undefined';
143
94
  }
144
- /** The terminal repair, once a `qoder_terminal_repair` note enabled it. */
145
- export function repairFold(state) {
146
- return state.boundaries.repair ? new TerminalRepairFold(state.boundaries.repair) : undefined;
95
+ catch {
96
+ return 'unserializable';
97
+ } };
98
+ const isVolatileMetaKey = (key) => key === 'timestamp' || key.endsWith('.timestamp') || key.endsWith('_timestamp');
99
+ /** Every event through the live/history entry points ticks once; a tracker whose last chunk is not the immediately
100
+ * preceding event starts fresh. A tool call or any other update between two chunks therefore keeps their ids
101
+ * meaningful — which also matches the server, where an unrelated event already flushes the pending text. */
102
+ function tickEvent(state) {
103
+ const ticks = CHUNK_TICKS.get(state) ?? { tick: 0 };
104
+ ticks.tick += 1;
105
+ CHUNK_TICKS.set(state, ticks);
106
+ return ticks.tick;
147
107
  }
148
- /** `this.subworkBoundary?.map(event) ?? [event]`. */
149
- function throughSubwork(state, event) {
150
- const root = state.rootSessionId;
151
- if (root === null || !supportsSubworkMeta(agentInfo(state)))
152
- return [event];
153
- return new SubworkBoundary(root, state.boundaries.subwork).map(event);
108
+ function churnStateOf(state, key, tick) {
109
+ let perState = CHUNK_CHURN.get(state);
110
+ if (!perState)
111
+ CHUNK_CHURN.set(state, perState = new Map());
112
+ const fresh = state.turns.length;
113
+ let churn = perState.get(key);
114
+ if (!churn || churn.seenTurns !== fresh || churn.lastTick !== tick - 1)
115
+ perState.set(key, churn = { prevId: null, anchorId: null, trusted: false, prevMeta: null, anchorMeta: {}, churnyMetaKeys: new Set(), trustedMetaKeys: new Set(), seenTurns: fresh, lastTick: tick });
116
+ churn.lastTick = tick;
117
+ return churn;
154
118
  }
155
- /** `this.foregroundOutput?.endTurn()` — no turn remains. */
156
- export function endForegroundTurn(state) {
157
- state.boundaries.foreground.pending = {};
119
+ /** Absorb per-delta id/meta churn on one assistant chunk (see the block comment above); other events pass through. */
120
+ function absorbAssistantChunkChurn(state, tick, event) {
121
+ if (event.eventName !== 'agent_message_chunk' && event.eventName !== 'agent_thought_chunk')
122
+ return event;
123
+ const payload = event.payload;
124
+ const churn = churnStateOf(state, `${event.acpSessionId ?? 'root'}:${event.eventName}`, tick);
125
+ let changed = false;
126
+ const rawId = typeof payload.messageId === 'string' ? payload.messageId : null;
127
+ if (rawId !== null) {
128
+ if (churn.prevId !== null && rawId === churn.prevId)
129
+ churn.trusted = true;
130
+ if (churn.trusted) {
131
+ churn.anchorId = rawId;
132
+ }
133
+ else if (churn.prevId !== null) {
134
+ // A change with no stable pair ever seen: churn — keep the anchor.
135
+ if (churn.anchorId !== null && churn.anchorId !== rawId) {
136
+ payload.messageId = churn.anchorId;
137
+ changed = true;
138
+ }
139
+ }
140
+ else {
141
+ churn.anchorId = rawId;
142
+ }
143
+ churn.prevId = rawId;
144
+ }
145
+ const rawMeta = payload._meta;
146
+ if (rawMeta !== null && typeof rawMeta === 'object') {
147
+ const meta = rawMeta;
148
+ const emitted = {};
149
+ let anchored = false;
150
+ for (const [key, value] of Object.entries(meta)) {
151
+ if (!isVolatileMetaKey(key)) {
152
+ emitted[key] = value;
153
+ continue;
154
+ }
155
+ const previous = churn.prevMeta;
156
+ if (previous !== null && key in previous) {
157
+ if (canonical(value) === canonical(previous[key]))
158
+ churn.trustedMetaKeys.add(key);
159
+ else if (!churn.trustedMetaKeys.has(key)) {
160
+ // First sighting of a change on a never-stable key: anchor it to what the previous chunk
161
+ // already emitted, so every chunk of this stream carries the identical map.
162
+ if (!(key in churn.anchorMeta))
163
+ churn.anchorMeta[key] = previous[key];
164
+ churn.churnyMetaKeys.add(key);
165
+ }
166
+ }
167
+ emitted[key] = churn.churnyMetaKeys.has(key) && key in churn.anchorMeta ? churn.anchorMeta[key] : value;
168
+ if (emitted[key] !== value)
169
+ anchored = true;
170
+ }
171
+ churn.prevMeta = meta;
172
+ if (anchored) {
173
+ payload._meta = emitted;
174
+ changed = true;
175
+ }
176
+ }
177
+ return changed ? { ...event, payload } : event;
158
178
  }
159
- /** `AcpSession.routeMappedEvent`, Qoder boundaries included. */
160
179
  function routeOwnEvent(state, out, event) {
161
- const root = state.rootSessionId;
162
- if (certifiedRuntime(agentInfo(state)) && !new ForegroundOutputBoundary(root, state.boundaries.foreground).accept(event))
163
- return;
164
- repairFold(state)?.observe(event);
165
- const subwork = supportsSubworkMeta(agentInfo(state));
166
- for (const mapped of throughSubwork(state, event)) {
167
- observe(state, mapped);
168
- observeBackgroundWriter(state, mapped);
169
- if ((!subwork || !mapped.acpSessionId || mapped.acpSessionId === root) &&
170
- (mapped.eventName === 'agent_message_chunk' || mapped.eventName === 'agent_thought_chunk'))
171
- trackMessageBoundary(state, out, mapped);
172
- emitEvent(state, out, mapped);
173
- }
180
+ const tick = tickEvent(state);
181
+ event = absorbAssistantChunkChurn(state, tick, event);
182
+ observe(state, event);
183
+ observeBackgroundWriter(state, event);
184
+ if ((!event.acpSessionId || event.acpSessionId === state.rootSessionId) &&
185
+ (event.eventName === 'agent_message_chunk' || event.eventName === 'agent_thought_chunk'))
186
+ trackMessageBoundary(state, out, event);
187
+ emitEvent(state, out, event);
174
188
  }
175
- /**
176
- * `AcpSession.emitEvent` (minus a card's own checks, `session.ts`): a diverted update, a card, or a
177
- * repair event. The repair observes it as given; a root id on `acpSessionId` is dropped; the subwork
178
- * boundary maps it; each mapped event feeds the origin graph and is numbered.
179
- */
189
+ /** Normalize the root session address, observe the event, then emit. */
180
190
  export function emitSessionEvent(state, out, event) {
181
- repairFold(state)?.observe(event);
191
+ const tick = tickEvent(state);
192
+ event = absorbAssistantChunkChurn(state, tick, event);
182
193
  // Inside a load's history window the session being loaded is the root (an attached `AcpSession` knows it).
183
194
  const normalized = event.acpSessionId === (state.rootSessionId ?? state.history?.rootSessionId)
184
195
  ? { eventName: event.eventName, payload: event.payload }
185
196
  : event;
186
- for (const mapped of throughSubwork(state, normalized)) {
187
- observe(state, mapped);
188
- emitEvent(state, out, mapped);
189
- }
197
+ observe(state, normalized);
198
+ emitEvent(state, out, normalized);
190
199
  }
191
200
  /** What every live event feeds besides the outbox: the origin graph and the async-task set. */
192
201
  function observe(state, event) {
193
202
  if (event.eventName === 'subagent_spawned') {
194
203
  observeSubagentSpawned(state, event.acpSessionId ?? state.rootSessionId, event.payload.subagentSessionId);
204
+ const child = event.payload.subagentSessionId;
205
+ if (typeof child === 'string' && getOwn(state.permissionOrigins, child) &&
206
+ getOwn(state.permissionParents, child) === (event.acpSessionId ?? state.rootSessionId)) {
207
+ delete state.closedReplayScopes[child];
208
+ }
195
209
  }
196
210
  trackAsyncTask(state, event);
211
+ trackDraftSubagent(state, event);
197
212
  }
198
213
  /** `AcpSession.observeBackgroundWriter`: an explicitly detached writer blocks `executionEnded` for good. */
199
214
  function observeBackgroundWriter(state, event) {
@@ -205,6 +220,33 @@ function observeBackgroundWriter(state, event) {
205
220
  state.backgroundWriterUnconfirmed = true;
206
221
  }
207
222
  }
223
+ const DRAFT_CHILD_LIMIT = 4096;
224
+ const DRAFT_TERMINALS = new Set(['completed', 'failed', 'cancelled']);
225
+ /** Keep terminal tombstones: duplicated/reordered spawns cannot resurrect completed children.
226
+ * At capacity retain a conservative blocker rather than forget potentially running work. */
227
+ function trackDraftSubagent(state, event) {
228
+ if (event.eventName !== 'subagent_spawned' && event.eventName !== 'subagent_state_update')
229
+ return;
230
+ const id = event.payload.subagentSessionId;
231
+ if (typeof id !== 'string' || !id.length)
232
+ return;
233
+ const data = state.draftSubagents;
234
+ if (id.length > 256) {
235
+ data.overflow = true;
236
+ return;
237
+ }
238
+ const terminal = event.eventName === 'subagent_state_update' && DRAFT_TERMINALS.has(String(event.payload.state));
239
+ if (getOwn(data.children, id) === false)
240
+ return;
241
+ if (!Object.hasOwn(data.children, id) && Object.keys(data.children).length >= DRAFT_CHILD_LIMIT) {
242
+ data.overflow = true;
243
+ return;
244
+ }
245
+ putOwn(data.children, id, !terminal);
246
+ }
247
+ export function unfinishedDraftSubagentCount(state) {
248
+ return Object.values(state.draftSubagents.children).filter(Boolean).length + Number(state.draftSubagents.overflow);
249
+ }
208
250
  const TERMINAL_ASYNC_STATES = new Set(['completed', 'failed', 'stopped']);
209
251
  /**
210
252
  * The unfinished async tasks (AIR `async_task_*`), per native session: spawned → running; a state
@@ -296,7 +338,9 @@ function onHistoryUpdate(state, history, sessionId, update, out) {
296
338
  history.droppedCount += 1;
297
339
  return;
298
340
  }
299
- for (const event of events) {
341
+ for (const rawEvent of events) {
342
+ // Same churn absorption as the live path: a replayed CodeBuddy history fragments identically.
343
+ const event = absorbAssistantChunkChurn(state, tickEvent(state), rawEvent);
300
344
  if (event.eventName === 'user_message_chunk')
301
345
  flushHistoryMessage(state, history, sessionId, out);
302
346
  if (event.eventName === 'agent_message_chunk' || event.eventName === 'agent_thought_chunk') {
@@ -331,10 +375,17 @@ function emitHistory(state, history, out, event, sessionId) {
331
375
  return;
332
376
  }
333
377
  // `AcpSession.observeReplayEvent`: history restores the repair's calls, addresses and background-writer evidence.
334
- repairFold(state)?.observe(scoped);
335
378
  if (scoped.eventName === 'subagent_spawned') {
336
379
  observeSubagentSpawned(state, scoped.acpSessionId ?? history.rootSessionId, scoped.payload.subagentSessionId, history.rootSessionId);
337
380
  }
381
+ trackDraftSubagent(state, scoped);
382
+ if (scoped.eventName === 'subagent_state_update' && DRAFT_TERMINALS.has(String(scoped.payload.state))) {
383
+ const child = scoped.payload.subagentSessionId;
384
+ // Share the lifecycle tracker's bounded, validated identity set.
385
+ if (typeof child === 'string' && getOwn(state.draftSubagents.children, child) === false) {
386
+ putOwn(state.closedReplayScopes, child, true);
387
+ }
388
+ }
338
389
  observeBackgroundWriter(state, scoped);
339
390
  out.unjournaled({
340
391
  t: 'processor.history_event',