@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
@@ -1,9 +1,67 @@
1
- import { decodeChannelFrame, hasOptional, hasRequired, isBoolean, isNumber, isString, isStringArray, isStringRecord } from './common.js';
1
+ import { validDesktopFrame } from '../desktop/protocol.js';
2
+ import { decodeChannelFrame, hasOptional, hasRequired, isBoolean, isNumber, isObject, isString, isStringArray, isStringRecord } from './common.js';
3
+ import { TOOL_REQUEST_BYTES, TOOL_RESULT_BYTES } from '../tools/validation.js';
4
+ export const MAX_COMPUTER_FRAME_BYTES = 8 * 1024 * 1024;
2
5
  const DISCRIMINATORS = new Set([
6
+ 'desktop_capability', 'desktop_open', 'desktop_opened', 'desktop_data', 'desktop_credit', 'desktop_close',
3
7
  'computer_hello', 'computer_ready', 'heartbeat', 'heartbeat_ack',
4
- 'exec_request', 'exec_output', 'exec_exit', 'exec_cancel',
8
+ 'exec_request', 'exec_output', 'exec_exit', 'exec_cancel', 'tool_request', 'tool_result',
9
+ 'exec_started', 'exec_ack', 'exec_attach', 'exec_status',
10
+ 'file_read_request', 'file_read_result', 'file_write_request', 'file_write_result',
11
+ 'file_ls_request', 'file_ls_result', 'file_mkdir_request', 'file_mkdir_result',
5
12
  ]);
13
+ const fileErrorCodes = new Set([
14
+ 'invalid_argument', 'invalid_path', 'unsupported_workstation', 'unsupported_protocol',
15
+ 'workstation_offline', 'not_found', 'not_regular_file', 'invalid_encoding', 'line_too_long',
16
+ 'too_large', 'conflict', 'permission_denied', 'io_error', 'outcome_unknown',
17
+ ]);
18
+ const isNonnegativeInteger = (value) => Number.isSafeInteger(value) && value >= 0;
19
+ const isPositiveInteger = (value) => Number.isSafeInteger(value) && value >= 1;
20
+ /** A record offset, or -1 for "none" (exec_status / exec_ack). */
21
+ const isOffsetOrNone = (value) => Number.isSafeInteger(value) && value >= -1;
22
+ const EXEC_STATES = new Set(['running', 'exited', 'unknown']);
23
+ const isSha256 = (value) => typeof value === 'string' && /^[0-9a-f]{64}$/.test(value);
24
+ const onlyKeys = (value, keys) => Object.keys(value).every(key => keys.includes(key));
25
+ function isFileError(value) {
26
+ return isObject(value) && hasRequired(value, { code: isString, message: isString })
27
+ && fileErrorCodes.has(value.code) && onlyKeys(value, ['code', 'message']);
28
+ }
29
+ function isReadData(value) {
30
+ return isObject(value) && hasRequired(value, {
31
+ path: isString, content: isString, startLine: isPositiveInteger, endLine: isNonnegativeInteger,
32
+ truncated: isBoolean, sizeBytes: isNonnegativeInteger, sha256: isSha256,
33
+ }) && hasOptional(value, { nextLine: isPositiveInteger })
34
+ && onlyKeys(value, ['path', 'content', 'startLine', 'endLine', 'nextLine', 'truncated', 'sizeBytes', 'sha256']);
35
+ }
36
+ function isWriteData(value) {
37
+ return isObject(value) && hasRequired(value, { path: isString, sizeBytes: isNonnegativeInteger, sha256: isSha256, created: isBoolean })
38
+ && onlyKeys(value, ['path', 'sizeBytes', 'sha256', 'created']);
39
+ }
40
+ const dirEntryKinds = new Set(['file', 'directory', 'symlink', 'other']);
41
+ function isLsData(value) {
42
+ return isObject(value) && hasRequired(value, { path: isString, truncated: isBoolean })
43
+ && hasOptional(value, { nextAfter: isString })
44
+ && onlyKeys(value, ['path', 'entries', 'truncated', 'nextAfter'])
45
+ && Array.isArray(value.entries)
46
+ && value.entries.every((entry) => isObject(entry) && hasRequired(entry, { name: isString, kind: isString })
47
+ && dirEntryKinds.has(entry.kind)
48
+ && hasOptional(entry, { sizeBytes: isNonnegativeInteger })
49
+ && onlyKeys(entry, ['name', 'kind', 'sizeBytes']));
50
+ }
51
+ function isMkdirData(value) {
52
+ return isObject(value) && hasRequired(value, { path: isString, created: isBoolean })
53
+ && onlyKeys(value, ['path', 'created']);
54
+ }
55
+ function isFileResult(value, validData) {
56
+ if (!hasRequired(value, { id: isString }) || !onlyKeys(value, ['t', 'id', 'data', 'error']))
57
+ return false;
58
+ const hasData = Object.hasOwn(value, 'data');
59
+ const hasError = Object.hasOwn(value, 'error');
60
+ return hasData !== hasError && (hasData ? validData(value.data) : isFileError(value.error));
61
+ }
6
62
  function isComputerFrame(value) {
63
+ if (typeof value.t === 'string' && value.t.startsWith('desktop_'))
64
+ return validDesktopFrame(value);
7
65
  switch (value.t) {
8
66
  case 'computer_hello': return hasRequired(value, { ticket: isString });
9
67
  case 'computer_ready': return hasRequired(value, { generation: isString, wireVersion: isNumber });
@@ -12,20 +70,62 @@ function isComputerFrame(value) {
12
70
  case 'exec_request': return hasRequired(value, {
13
71
  id: isString, command: isString, shell: isString, cwd: isString, env: isStringRecord,
14
72
  pathPrepend: isStringArray, streaming: isBoolean,
15
- }) && hasOptional(value, { timeoutSeconds: isNullableNumber });
16
- case 'exec_output': return hasRequired(value, { id: isString, stream: isString, chunk: isString });
73
+ }) && hasOptional(value, { timeoutSeconds: isNullableNumber, label: isString, jobId: isString });
74
+ case 'exec_started': return hasRequired(value, { id: isString, durable: isBoolean, startedAtMs: isNumber })
75
+ && hasOptional(value, { pid: isNonnegativeInteger });
76
+ case 'exec_output': return hasRequired(value, { id: isString, stream: isString, chunk: isString })
77
+ && hasOptional(value, { offset: isNonnegativeInteger, recvAtMs: isNumber });
78
+ case 'exec_ack': return hasRequired(value, { id: isString, throughOffset: isOffsetOrNone });
17
79
  case 'exec_exit': return hasRequired(value, {
18
80
  id: isString, exitCode: isNullableNumber, stdout: isString, stderr: isString,
19
81
  timedOut: isBoolean, truncated: isBoolean,
20
- });
21
- case 'exec_cancel': return hasRequired(value, { id: isString });
82
+ }) && hasOptional(value, { signal: isString, cancelled: isBoolean, gaps: isNonnegativeInteger });
83
+ case 'exec_attach': return hasRequired(value, { id: isString, fromOffset: isNonnegativeInteger });
84
+ case 'exec_status': return hasRequired(value, { id: isString, state: isString, headOffset: isOffsetOrNone, committedOffset: isOffsetOrNone })
85
+ && EXEC_STATES.has(value.state);
86
+ case 'tool_request': return hasRequired(value, { id: isString, method: isString, params: isObject })
87
+ && value.id.length > 0 && value.id.length <= 128
88
+ && value.method.length > 0 && value.method.length <= 64
89
+ && Buffer.byteLength(JSON.stringify(value)) <= TOOL_REQUEST_BYTES
90
+ && onlyKeys(value, ['t', 'id', 'method', 'params']);
91
+ case 'tool_result': return hasRequired(value, { id: isString, ok: isBoolean, result: isObject, error: value => value === null || isString(value) })
92
+ && value.id.length > 0 && value.id.length <= 128
93
+ && (value.ok ? value.error === null : typeof value.error === 'string' && value.error.length > 0)
94
+ && Buffer.byteLength(JSON.stringify(value)) <= TOOL_RESULT_BYTES
95
+ && onlyKeys(value, ['t', 'id', 'ok', 'result', 'error']);
96
+ case 'exec_cancel': return hasRequired(value, { id: isString }) && hasOptional(value, { signal: isString });
97
+ case 'file_read_request': return hasRequired(value, {
98
+ id: isString, root: isString, path: isString, startLine: isPositiveInteger, maxLines: isPositiveInteger,
99
+ }) && value.maxLines <= 2000 && onlyKeys(value, ['t', 'id', 'root', 'path', 'startLine', 'maxLines']);
100
+ case 'file_write_request': return hasRequired(value, {
101
+ id: isString, root: isString, path: isString, content: isString, mode: isString,
102
+ }) && (value.mode === 'create' || value.mode === 'replace')
103
+ && hasOptional(value, { expectedSha256: isSha256 })
104
+ && onlyKeys(value, ['t', 'id', 'root', 'path', 'content', 'mode', 'expectedSha256']);
105
+ case 'file_ls_request': return hasRequired(value, {
106
+ id: isString, root: isString, path: isString, maxEntries: isPositiveInteger,
107
+ }) && value.maxEntries <= 2000
108
+ && hasOptional(value, { startAfter: isString })
109
+ && onlyKeys(value, ['t', 'id', 'root', 'path', 'startAfter', 'maxEntries']);
110
+ case 'file_mkdir_request': return hasRequired(value, {
111
+ id: isString, root: isString, path: isString, parents: isBoolean,
112
+ }) && onlyKeys(value, ['t', 'id', 'root', 'path', 'parents']);
113
+ case 'file_read_result': return isFileResult(value, isReadData);
114
+ case 'file_write_result': return isFileResult(value, isWriteData);
115
+ case 'file_ls_result': return isFileResult(value, isLsData);
116
+ case 'file_mkdir_result': return isFileResult(value, isMkdirData);
22
117
  default: return false;
23
118
  }
24
119
  }
25
120
  function isNullableNumber(value) { return value === null || isNumber(value); }
26
121
  export function encodeComputerFrame(frame) {
27
- return JSON.stringify(frame);
122
+ const encoded = JSON.stringify(frame);
123
+ if (Buffer.byteLength(encoded, 'utf8') > MAX_COMPUTER_FRAME_BYTES)
124
+ throw new TypeError('Computer frame too large');
125
+ return encoded;
28
126
  }
29
127
  export function decodeComputerFrame(text) {
128
+ if (Buffer.byteLength(text, 'utf8') > MAX_COMPUTER_FRAME_BYTES)
129
+ throw new TypeError('Computer frame too large');
30
130
  return decodeChannelFrame(text, DISCRIMINATORS, isComputerFrame);
31
131
  }
@@ -8,11 +8,20 @@ export const MANAGEMENT_WIRE_METRICS_VERSION = 4;
8
8
  export const MANAGEMENT_WIRE_PRESSURE_VERSION = 5;
9
9
  /** Replaces normalized CPU load with raw 1/5/15-minute load averages and CPU capacity. */
10
10
  export const MANAGEMENT_WIRE_LOAD_AVERAGES_VERSION = 6;
11
- export const MANAGEMENT_WIRE_VERSION = MANAGEMENT_WIRE_LOAD_AVERAGES_VERSION;
11
+ /** Adds per-GPU utilization and GPU memory to heartbeat metrics. */
12
+ export const MANAGEMENT_WIRE_GPU_VERSION = 7;
13
+ /** Adds `keepers` to heartbeats and the `keeper_control` / `keeper_control_result` pair (spec §8.1, Task 26). */
14
+ export const MANAGEMENT_WIRE_KEEPERS_VERSION = 8;
15
+ export const MANAGEMENT_WIRE_VERSION = MANAGEMENT_WIRE_KEEPERS_VERSION;
16
+ /** Bounds of one heartbeat's `keepers` (spec §8.1). */
17
+ export const KEEPER_STATUS_MAX_ITEMS = 32;
18
+ export const KEEPER_LABEL_MAX_CHARS = 64;
19
+ export const KEEPER_STATUS_MAX_ENTRIES = 16;
20
+ export const KEEPER_AFFECTED_MAX_ITEMS = 256;
12
21
  const DISCRIMINATORS = new Set([
13
22
  'management_hello', 'management_ready', 'heartbeat', 'heartbeat_ack',
14
23
  'channel_ticket_request', 'channel_ticket', 'client_update', 'client_update_status',
15
- 'external_agents_scan', 'external_agents_scan_result',
24
+ 'external_agents_scan', 'external_agents_scan_result', 'keeper_control', 'keeper_control_result',
16
25
  ]);
17
26
  const CHANNEL_KINDS = new Set(['MANAGEMENT', 'COMPUTER', 'AGENT']);
18
27
  const CHANNEL_STATES = new Set(['DISCONNECTED', 'CONNECTING', 'CONNECTED', 'RECONNECTING']);
@@ -63,6 +72,15 @@ function isStoragePoolMetrics(value) {
63
72
  availableBytes: isNumber,
64
73
  });
65
74
  }
75
+ function isGpuMetrics(value) {
76
+ return isObject(value) && hasRequired(value, { id: isString, name: isString })
77
+ && hasOptional(value, {
78
+ utilization: isNullable(isNumber),
79
+ memoryTotalBytes: isNullable(isNumber),
80
+ memoryUsedBytes: isNullable(isNumber),
81
+ coreCount: isNullable(isNumber),
82
+ });
83
+ }
66
84
  function isWorkstationMetrics(value) {
67
85
  return isObject(value) && hasRequired(value, {
68
86
  cpuUtilization: isNullable(isNumber),
@@ -76,8 +94,31 @@ function isWorkstationMetrics(value) {
76
94
  cpuLoad15: isNullable(isNumber),
77
95
  cpuLogicalCount: isNullable(isNumber),
78
96
  memoryPressure: isNullable(isNumber),
97
+ gpus: isNullable((gpus) => Array.isArray(gpus) && gpus.every(isGpuMetrics)),
79
98
  });
80
99
  }
100
+ const KEEPER_KINDS = new Set(['terminal', 'acp']);
101
+ const KEEPER_STATES = new Set(['running', 'starting', 'stopped', 'unavailable']);
102
+ const KEEPER_REPLACEMENTS = new Set(['none', 'available', 'required', 'mandatory']);
103
+ const KEEPER_ITEM_TYPES = new Set(['pty', 'exec', 'session']);
104
+ const KEEPER_ACTIONS = new Set(['replace-when-idle', 'replace-now', 'cancel']);
105
+ const oneOf = (set) => (value) => typeof value === 'string' && set.has(value);
106
+ const isPending = (value) => value === null || value === 'when-idle';
107
+ function isKeeperItem(value) {
108
+ return isObject(value) && hasRequired(value, { type: oneOf(KEEPER_ITEM_TYPES), id: isString, label: isString, state: isString, sinceEpochMs: isNumber })
109
+ && hasOptional(value, { jobId: isString });
110
+ }
111
+ function isKeeperStatus(value) {
112
+ return isObject(value) && hasRequired(value, {
113
+ kind: oneOf(KEEPER_KINDS), gen: isString, state: oneOf(KEEPER_STATES), protocolVersion: isNumber, buildVersion: isString,
114
+ replacement: oneOf(KEEPER_REPLACEMENTS),
115
+ metrics: (metrics) => isObject(metrics) && Object.values(metrics).every(isNumber),
116
+ items: (items) => Array.isArray(items) && items.length <= KEEPER_STATUS_MAX_ITEMS && items.every(isKeeperItem),
117
+ }) && hasOptional(value, { legacy: isBoolean, pending: isPending });
118
+ }
119
+ function isKeeperAffected(value) {
120
+ return isObject(value) && hasRequired(value, { type: oneOf(KEEPER_ITEM_TYPES), id: isString, label: isString, gen: isString });
121
+ }
81
122
  function isManagementFrame(value) {
82
123
  switch (value.t) {
83
124
  case 'management_hello': return hasRequired(value, {
@@ -93,10 +134,12 @@ function isManagementFrame(value) {
93
134
  channels: (channels) => isObject(channels) && Object.entries(channels).every(([kind, state]) => CHANNEL_KINDS.has(kind) && isChannelState(state)),
94
135
  activeShellTasks: isNumber, activeAgentTasks: isNumber,
95
136
  }) && hasOptional(value, {
137
+ playgroundCwd: isString,
96
138
  protocols: (protocols) => isProtocolRangeMap(protocols, false),
97
139
  os: isNullable(isOsInfo),
98
140
  update: isNullable(isUpdateStatus),
99
141
  metrics: isNullable(isWorkstationMetrics),
142
+ keepers: isNullable((keepers) => Array.isArray(keepers) && keepers.every(isKeeperStatus)),
100
143
  });
101
144
  case 'heartbeat_ack': return hasRequired(value, { seq: isNumber });
102
145
  case 'channel_ticket_request': return hasRequired(value, { channel: isChannelKind });
@@ -105,6 +148,11 @@ function isManagementFrame(value) {
105
148
  case 'client_update_status': return hasRequired(value, { status: isUpdateStatus });
106
149
  case 'external_agents_scan': return hasRequired(value, { commandId: isString });
107
150
  case 'external_agents_scan_result': return hasRequired(value, { commandId: isString, bridges: isBridgeInfoArray });
151
+ case 'keeper_control': return hasRequired(value, { commandId: isString, kind: oneOf(KEEPER_KINDS), action: oneOf(KEEPER_ACTIONS) });
152
+ case 'keeper_control_result': return hasRequired(value, {
153
+ commandId: isString, kind: oneOf(KEEPER_KINDS), action: oneOf(KEEPER_ACTIONS), ok: isBoolean,
154
+ affected: (affected) => Array.isArray(affected) && affected.every(isKeeperAffected),
155
+ }) && hasOptional(value, { pending: isPending, error: isString, affectedIncomplete: (flag) => flag === true });
108
156
  default: return false;
109
157
  }
110
158
  }
@@ -0,0 +1,118 @@
1
+ /** Native Qoder does not forward background child SDK messages through ACP. Read only the
2
+ * exact transcript named by a validated root launch receipt; never scan or infer ownership.
3
+ *
4
+ * Each accepted row's derived events go to `onRow` (with the row's `uuid`) when it is given — the
5
+ * side-file reader records them as one `child` fact (`backgroundTerminalFold.ts`) — and otherwise one
6
+ * by one to `emit`. */
7
+ import { constants } from 'node:fs';
8
+ import { open } from 'node:fs/promises';
9
+ const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
10
+ const id = (value) => typeof value === 'string' && /^[a-zA-Z0-9_-]{1,160}$/.test(value);
11
+ const LIMIT = 2 * 1024 * 1024;
12
+ export class BackgroundProcess {
13
+ options;
14
+ offset = 0;
15
+ tail = Buffer.alloc(0);
16
+ skipLine = false;
17
+ seen = new Set();
18
+ tools = new Map();
19
+ constructor(options) {
20
+ this.options = options;
21
+ }
22
+ async poll() {
23
+ const file = await open(this.options.path, constants.O_RDONLY | constants.O_NOFOLLOW);
24
+ try {
25
+ const stat = await file.stat();
26
+ if (!stat.isFile())
27
+ return true;
28
+ if (stat.size < this.offset) {
29
+ this.offset = 0;
30
+ this.tail = Buffer.alloc(0);
31
+ this.skipLine = false;
32
+ }
33
+ const end = Math.min(stat.size, this.offset + 4 * 1024 * 1024);
34
+ while (this.offset < end) {
35
+ const chunk = Buffer.alloc(Math.min(64 * 1024, end - this.offset));
36
+ const { bytesRead } = await file.read(chunk, 0, chunk.length, this.offset);
37
+ if (!bytesRead)
38
+ break;
39
+ this.offset += bytesRead;
40
+ this.consumeBytes(chunk.subarray(0, bytesRead));
41
+ }
42
+ return this.offset >= stat.size;
43
+ }
44
+ finally {
45
+ await file.close();
46
+ }
47
+ }
48
+ consumeBytes(bytes) {
49
+ let start = 0;
50
+ for (let end = bytes.indexOf(10); end >= 0; end = bytes.indexOf(10, start)) {
51
+ const part = bytes.subarray(start, end);
52
+ if (!this.skipLine && this.tail.length + part.length <= LIMIT) {
53
+ try {
54
+ this.consume(object(JSON.parse(Buffer.concat([this.tail, part]).toString('utf8'))));
55
+ }
56
+ catch { /* not evidence */ }
57
+ }
58
+ this.tail = Buffer.alloc(0);
59
+ this.skipLine = false;
60
+ start = end + 1;
61
+ }
62
+ const part = bytes.subarray(start);
63
+ if (!this.skipLine && this.tail.length + part.length <= LIMIT)
64
+ this.tail = Buffer.concat([this.tail, part]);
65
+ else {
66
+ this.tail = Buffer.alloc(0);
67
+ this.skipLine = true;
68
+ }
69
+ }
70
+ consume(row) {
71
+ const o = this.options;
72
+ if (row.sessionId !== o.sessionId || row.cwd !== o.cwd || row.agentId !== o.agentId ||
73
+ row.parent_tool_use_id !== o.parentToolUseId || row.isSidechain !== true || row.version !== '1.1.61' ||
74
+ !id(row.uuid) || this.seen.has(row.uuid) || this.seen.size >= 4096)
75
+ return;
76
+ this.seen.add(row.uuid);
77
+ const content = object(row.message).content;
78
+ if (!Array.isArray(content))
79
+ return;
80
+ const events = [];
81
+ for (const block of content.map(object)) {
82
+ if (row.type === 'assistant' && block.type === 'tool_use' && id(block.id) && typeof block.name === 'string') {
83
+ if (this.tools.has(block.id) || this.tools.size >= 4096)
84
+ continue;
85
+ const input = object(block.input);
86
+ this.tools.set(block.id, { name: block.name, input });
87
+ this.derive(events, 'tool_call', { toolCallId: block.id, name: block.name, title: block.name,
88
+ kind: block.name === 'Read' ? 'read' : block.name === 'Bash' ? 'execute' : block.name === 'Agent' ? 'think' : 'other',
89
+ rawInput: input, status: 'pending' }, block.name);
90
+ }
91
+ else if (row.type === 'user' && block.type === 'tool_result' && id(block.tool_use_id)) {
92
+ const tool = this.tools.get(block.tool_use_id);
93
+ if (!tool)
94
+ continue;
95
+ const raw = row.toolUseResult ?? block.content;
96
+ const text = typeof block.content === 'string' ? block.content : JSON.stringify(block.content ?? '');
97
+ this.derive(events, 'tool_call_update', { toolCallId: block.tool_use_id, name: tool.name,
98
+ status: block.is_error === true ? 'failed' : 'completed', rawOutput: raw,
99
+ content: [{ type: 'content', content: { type: 'text', text } }] }, tool.name);
100
+ }
101
+ else if (row.type === 'assistant' && ((block.type === 'text' && typeof block.text === 'string') ||
102
+ (block.type === 'thinking' && typeof block.thinking === 'string'))) {
103
+ this.derive(events, block.type === 'text' ? 'agent_message_chunk' : 'agent_thought_chunk', { content: { type: 'text', text: block.type === 'text' ? block.text : block.thinking } });
104
+ }
105
+ }
106
+ if (this.options.onRow) {
107
+ if (events.length > 0)
108
+ this.options.onRow(row.uuid, events);
109
+ }
110
+ else
111
+ for (const event of events)
112
+ this.options.emit(event);
113
+ }
114
+ derive(events, eventName, payload, toolName) {
115
+ events.push({ eventName, payload: { ...payload, _meta: { qoder: { version: 1, source: 'correction',
116
+ parentToolUseId: this.options.parentToolUseId, route: 'child', ...(toolName ? { toolName } : {}) } } } });
117
+ }
118
+ }
@@ -0,0 +1,193 @@
1
+ /** Qoder 1.1.61 drops native background task notifications from both live ACP and session/load.
2
+ * This read-only compatibility boundary repairs only a known root Agent tool, never a session.
3
+ * Lifecycle/removal contract: axom-patch/cli/qoder-background-terminal.md.
4
+ *
5
+ * The impure half (Task 17c): this file reads the side files; everything the repair decides is the
6
+ * pure fold in `backgroundTerminalFold.ts`. {@link BackgroundSideNotes} is the daemon side of the deterministic
7
+ * processor: a poll returns the facts as `qoder_side` note payloads for the raw log; the processor folds them only when it
8
+ * reads those notes, so a replay never touches the files. (The old live path's in-process driver, `BackgroundTerminalRepair`,
9
+ * was deleted with that path in Task 29.)
10
+ */
11
+ import { constants } from 'node:fs';
12
+ import { open } from 'node:fs/promises';
13
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
14
+ import { BackgroundProcess } from './backgroundProcess.js';
15
+ import { address, MAX_TASKS, object, rootRowFacts, sideNotes, } from './backgroundTerminalFold.js';
16
+ export { TerminalRepairFold, newTerminalRepairData, sideNotes } from './backgroundTerminalFold.js';
17
+ const CHUNK = 64 * 1024;
18
+ const MAX_LINE = 2 * 1024 * 1024;
19
+ const MAX_POLL = 4 * 1024 * 1024;
20
+ export function nativeTerminalHistoryPath(identity, sessionId, cwd, historyHome) {
21
+ if (identity?.name !== 'qoder-cli' || identity.version !== '1.1.61' ||
22
+ !address(sessionId) || !isAbsolute(cwd) || !isAbsolute(historyHome))
23
+ return;
24
+ // Certified ~/.qoder/projects layout. Even a colliding directory name cannot cross session/cwd
25
+ // identity: every accepted row is checked again against the exact native session and directory.
26
+ return join(historyHome, '.qoder', 'projects', resolve(cwd).replace(/[^a-zA-Z0-9]/g, '-'), sessionId + '.jsonl');
27
+ }
28
+ /**
29
+ * Reads the root transcript and the background children's transcripts incrementally (≤ 4 MiB per
30
+ * poll and file, lines ≤ 2 MiB, `O_NOFOLLOW`, never written) and hands each derived {@link SideFact}
31
+ * to the sink, in transcript order: root facts, then each followed child's rows and catch-up status,
32
+ * then `poll_end` — only when the root read completed. Its offsets are its own (not checkpointed): a
33
+ * new reader starts at 0 and the fold drops what it already has.
34
+ */
35
+ export class BackgroundSideReader {
36
+ options;
37
+ launches = new Map();
38
+ processes = new Map();
39
+ offset = 0;
40
+ fileIdentity = '';
41
+ pendingLine = Buffer.alloc(0);
42
+ skipLine = false;
43
+ closed = false;
44
+ warned = false;
45
+ restoring;
46
+ constructor(options) {
47
+ this.options = options;
48
+ this.restoring = options.restoreCalls === true;
49
+ }
50
+ /** One poll. Never rejects: a missing, replaced or unreadable file is not evidence. */
51
+ async read(demand, sink) {
52
+ if (this.closed || (!this.restoring && !demand.active()))
53
+ return;
54
+ try {
55
+ await this.readIncrement(demand, sink);
56
+ }
57
+ catch (error) {
58
+ const code = object(error).code;
59
+ if (!['ENOENT', 'ELOOP', 'ENOTDIR', 'EACCES'].includes(String(code)) && !this.warned) {
60
+ this.warned = true;
61
+ console.warn('[acp:qoder] background terminal repair unavailable; retaining native ACP state');
62
+ }
63
+ }
64
+ }
65
+ close() {
66
+ this.closed = true;
67
+ this.launches.clear();
68
+ this.processes.clear();
69
+ this.pendingLine = Buffer.alloc(0);
70
+ }
71
+ async readIncrement(demand, sink) {
72
+ // O_NOFOLLOW avoids importing a transcript through a replaced symlink. File is never written.
73
+ const file = await open(this.options.path, constants.O_RDONLY | constants.O_NOFOLLOW);
74
+ try {
75
+ const stat = await file.stat();
76
+ if (!stat.isFile() || this.closed)
77
+ return;
78
+ const identity = `${stat.dev}:${stat.ino}`;
79
+ if (identity !== this.fileIdentity || stat.size < this.offset) {
80
+ this.offset = 0;
81
+ this.pendingLine = Buffer.alloc(0);
82
+ this.skipLine = false;
83
+ this.fileIdentity = identity;
84
+ }
85
+ const end = Math.min(stat.size, this.offset + MAX_POLL);
86
+ const chunk = Buffer.alloc(CHUNK);
87
+ while (!this.closed && this.offset < end) {
88
+ const { bytesRead } = await file.read(chunk, 0, Math.min(CHUNK, end - this.offset), this.offset);
89
+ if (!bytesRead)
90
+ break;
91
+ this.offset += bytesRead;
92
+ this.lines(chunk.subarray(0, bytesRead), sink);
93
+ }
94
+ if (this.offset >= stat.size)
95
+ this.restoring = false;
96
+ if (!this.closed) {
97
+ await this.pollProcesses(demand, sink);
98
+ if (!this.closed)
99
+ sink({ kind: 'poll_end' });
100
+ }
101
+ }
102
+ finally {
103
+ await file.close();
104
+ }
105
+ }
106
+ lines(chunk, sink) {
107
+ let start = 0;
108
+ for (let end = chunk.indexOf(10); end !== -1; end = chunk.indexOf(10, start)) {
109
+ if (!this.skipLine && this.pendingLine.length + end - start <= MAX_LINE) {
110
+ const line = Buffer.concat([this.pendingLine, chunk.subarray(start, end)]).toString('utf8');
111
+ let row;
112
+ try {
113
+ row = object(JSON.parse(line));
114
+ }
115
+ catch { /* partial/corrupt non-evidence */ }
116
+ if (row)
117
+ this.consume(row, sink);
118
+ }
119
+ this.pendingLine = Buffer.alloc(0);
120
+ this.skipLine = false;
121
+ start = end + 1;
122
+ }
123
+ const tail = chunk.subarray(start);
124
+ if (!this.skipLine && this.pendingLine.length + tail.length <= MAX_LINE) {
125
+ this.pendingLine = Buffer.concat([this.pendingLine, tail]);
126
+ }
127
+ else {
128
+ this.pendingLine = Buffer.alloc(0);
129
+ this.skipLine = true;
130
+ }
131
+ }
132
+ consume(row, sink) {
133
+ const { nativeSessionId, cwd } = this.options;
134
+ for (const fact of rootRowFacts(row, { nativeSessionId, cwd, restoreCalls: this.options.restoreCalls === true })) {
135
+ // The reader's own copy of the receipts (same rule as the fold's): which child transcripts exist.
136
+ if (fact.kind === 'launch' && fact.agentId && this.launches.size < MAX_TASKS && !this.launches.has(fact.toolUseId)) {
137
+ this.launches.set(fact.toolUseId, fact.agentId);
138
+ }
139
+ sink(fact);
140
+ }
141
+ }
142
+ async pollProcesses(demand, sink) {
143
+ for (const [id, agentId] of this.launches) {
144
+ if (this.closed || !demand.wantsProcess(id))
145
+ continue;
146
+ let process = this.processes.get(id);
147
+ if (!process) {
148
+ process = new BackgroundProcess({
149
+ path: join(dirname(this.options.path), this.options.nativeSessionId, 'subagents', `agent-${agentId}.jsonl`),
150
+ sessionId: this.options.nativeSessionId, cwd: this.options.cwd, agentId, parentToolUseId: id,
151
+ emit: () => { },
152
+ onRow: (uuid, events) => { if (!this.closed)
153
+ sink({ kind: 'child', toolUseId: id, uuid, events }); },
154
+ });
155
+ this.processes.set(id, process);
156
+ }
157
+ let caughtUp;
158
+ try {
159
+ caughtUp = await process.poll();
160
+ }
161
+ catch {
162
+ caughtUp = true; /* missing/corrupt transcript is not execution evidence */
163
+ }
164
+ sink({ kind: 'child_status', toolUseId: id, caughtUp });
165
+ }
166
+ }
167
+ }
168
+ /**
169
+ * The daemon side of the processor (T19 runs it on its own timer, for a session whose processor
170
+ * folded a `qoder_terminal_repair` note): one {@link poll} reads what is new and returns the note
171
+ * payloads to append to the raw log, in order. `demand` is asked of the processor's current state
172
+ * (`acp/processor/sideChannel.ts` `sideDemand`); what it answered is recorded only through the facts,
173
+ * so the processor's fold never depends on when the daemon polled.
174
+ */
175
+ export class BackgroundSideNotes {
176
+ reader;
177
+ /** Facts over `MAX_SIDE_FACT_BYTES` that could not be recorded (diagnostics). */
178
+ droppedFacts = 0;
179
+ constructor(options) {
180
+ this.reader = new BackgroundSideReader(options);
181
+ }
182
+ async poll(demand) {
183
+ const notes = [];
184
+ await this.reader.read(demand, (fact) => {
185
+ const encoded = sideNotes(fact);
186
+ if (encoded.length === 0)
187
+ this.droppedFacts += 1;
188
+ notes.push(...encoded);
189
+ });
190
+ return notes;
191
+ }
192
+ close() { this.reader.close(); }
193
+ }