spexcode 0.6.7 → 0.7.0-next.0

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 (247) hide show
  1. package/README.md +12 -7
  2. package/node_modules/@spexcode/session-application/dist/index.d.ts +17 -0
  3. package/node_modules/@spexcode/session-application/dist/index.js +35 -0
  4. package/node_modules/@spexcode/session-application/dist/migration.d.ts +51 -0
  5. package/node_modules/@spexcode/session-application/dist/migration.js +694 -0
  6. package/node_modules/@spexcode/session-application/dist/production.d.ts +109 -0
  7. package/node_modules/@spexcode/session-application/dist/production.js +438 -0
  8. package/node_modules/@spexcode/session-application/dist/schema.d.ts +2 -0
  9. package/node_modules/@spexcode/session-application/dist/schema.js +39 -0
  10. package/node_modules/@spexcode/session-application/package.json +29 -0
  11. package/node_modules/@spexcode/session-events/dist/errors.d.ts +6 -0
  12. package/node_modules/@spexcode/session-events/dist/errors.js +11 -0
  13. package/node_modules/@spexcode/session-events/dist/index.d.ts +46 -0
  14. package/node_modules/@spexcode/session-events/dist/index.js +245 -0
  15. package/node_modules/@spexcode/session-events/dist/schema.d.ts +3 -0
  16. package/node_modules/@spexcode/session-events/dist/schema.js +44 -0
  17. package/node_modules/@spexcode/session-events/package.json +24 -0
  18. package/node_modules/@spexcode/session-protocol/dist/canonical.d.ts +36 -0
  19. package/node_modules/@spexcode/session-protocol/dist/canonical.js +139 -0
  20. package/node_modules/@spexcode/session-protocol/dist/engine.d.ts +22 -0
  21. package/node_modules/@spexcode/session-protocol/dist/engine.js +478 -0
  22. package/node_modules/@spexcode/session-protocol/dist/errors.d.ts +8 -0
  23. package/node_modules/@spexcode/session-protocol/dist/errors.js +39 -0
  24. package/node_modules/@spexcode/session-protocol/dist/index.d.ts +60 -0
  25. package/node_modules/@spexcode/session-protocol/dist/index.js +4 -0
  26. package/node_modules/@spexcode/session-protocol/dist/schema.d.ts +36 -0
  27. package/node_modules/@spexcode/session-protocol/dist/schema.js +192 -0
  28. package/node_modules/@spexcode/{session-core → session-protocol}/package.json +4 -8
  29. package/node_modules/@spexcode/session-runtime/dist/errors.d.ts +6 -0
  30. package/node_modules/@spexcode/session-runtime/dist/errors.js +11 -0
  31. package/node_modules/@spexcode/session-runtime/dist/index.d.ts +33 -0
  32. package/node_modules/@spexcode/session-runtime/dist/index.js +193 -0
  33. package/node_modules/@spexcode/session-runtime/dist/schema.d.ts +2 -0
  34. package/node_modules/@spexcode/session-runtime/dist/schema.js +37 -0
  35. package/node_modules/@spexcode/session-runtime/package.json +24 -0
  36. package/node_modules/@spexcode/session-selflaunch/bin/spex-session.mjs +4 -0
  37. package/node_modules/@spexcode/session-selflaunch/dist/cli.d.ts +25 -0
  38. package/node_modules/@spexcode/session-selflaunch/dist/cli.js +156 -0
  39. package/node_modules/@spexcode/session-selflaunch/dist/index.d.ts +28 -0
  40. package/node_modules/@spexcode/session-selflaunch/dist/index.js +25 -0
  41. package/node_modules/@spexcode/session-selflaunch/dist/locality.d.ts +31 -0
  42. package/node_modules/@spexcode/session-selflaunch/dist/locality.js +135 -0
  43. package/node_modules/@spexcode/session-selflaunch/dist/path.d.ts +13 -0
  44. package/node_modules/@spexcode/session-selflaunch/dist/path.js +45 -0
  45. package/node_modules/@spexcode/session-selflaunch/package.json +35 -0
  46. package/node_modules/@spexcode/session-topology/dist/errors.d.ts +6 -0
  47. package/node_modules/@spexcode/session-topology/dist/errors.js +11 -0
  48. package/node_modules/@spexcode/session-topology/dist/index.d.ts +24 -0
  49. package/node_modules/@spexcode/session-topology/dist/index.js +173 -0
  50. package/node_modules/@spexcode/session-topology/dist/schema.d.ts +3 -0
  51. package/node_modules/@spexcode/session-topology/dist/schema.js +27 -0
  52. package/node_modules/@spexcode/session-topology/package.json +24 -0
  53. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +175 -21
  54. package/node_modules/@spexcode/spec-cli/dist/cli.js +121 -59
  55. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +1 -3
  56. package/node_modules/@spexcode/spec-cli/dist/client.js +49 -30
  57. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +11 -0
  58. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +46 -9
  59. package/node_modules/@spexcode/spec-cli/dist/delivery-lock.d.ts +2 -0
  60. package/node_modules/@spexcode/spec-cli/dist/delivery-lock.js +58 -0
  61. package/node_modules/@spexcode/spec-cli/dist/doctor.js +53 -11
  62. package/node_modules/@spexcode/spec-cli/dist/file-write.js +14 -2
  63. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +2 -1
  64. package/node_modules/@spexcode/spec-cli/dist/gateway.js +6 -3
  65. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +32 -2
  66. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +57 -2
  67. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +2 -0
  68. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +83 -3
  69. package/node_modules/@spexcode/spec-cli/dist/guide.js +29 -18
  70. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +16 -3
  71. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +24 -6
  72. package/node_modules/@spexcode/spec-cli/dist/harness.js +510 -72
  73. package/node_modules/@spexcode/spec-cli/dist/help.js +11 -8
  74. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +8 -0
  75. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +29 -8
  76. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +7 -0
  77. package/node_modules/@spexcode/spec-cli/dist/host.js +98 -1
  78. package/node_modules/@spexcode/spec-cli/dist/index.js +294 -36
  79. package/node_modules/@spexcode/spec-cli/dist/init.js +1 -1
  80. package/node_modules/@spexcode/spec-cli/dist/lint.js +70 -35
  81. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +3 -2
  82. package/node_modules/@spexcode/spec-cli/dist/listen.js +14 -2
  83. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +2 -1
  84. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +46 -15
  85. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +2 -2
  86. package/node_modules/@spexcode/spec-cli/dist/materialize.js +176 -35
  87. package/node_modules/@spexcode/spec-cli/dist/opencode.js +10 -2
  88. package/node_modules/@spexcode/spec-cli/dist/pi-harness.js +18 -5
  89. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +2 -0
  90. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +27 -0
  91. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +14 -14
  92. package/node_modules/@spexcode/spec-cli/dist/reviews.js +12 -7
  93. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +11 -0
  94. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +79 -1
  95. package/node_modules/@spexcode/spec-cli/dist/session-application.d.ts +23 -0
  96. package/node_modules/@spexcode/spec-cli/dist/session-application.js +189 -0
  97. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +13 -1
  98. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +6 -0
  99. package/node_modules/@spexcode/spec-cli/dist/session-files.js +13 -1
  100. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +39 -22
  101. package/node_modules/@spexcode/{session-core/dist/record-lock.d.ts → spec-cli/dist/session-record-lock.d.ts} +0 -4
  102. package/node_modules/@spexcode/spec-cli/dist/session-record-lock.js +94 -0
  103. package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.d.ts +44 -0
  104. package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.js +37 -0
  105. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +25 -2
  106. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +68 -11
  107. package/node_modules/@spexcode/spec-cli/dist/session-transcript.d.ts +57 -0
  108. package/node_modules/@spexcode/spec-cli/dist/session-transcript.js +137 -0
  109. package/node_modules/@spexcode/spec-cli/dist/session-web.js +4 -4
  110. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +112 -15
  111. package/node_modules/@spexcode/spec-cli/dist/sessions.js +1553 -764
  112. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.js +14 -0
  113. package/node_modules/@spexcode/spec-cli/dist/source-list.d.ts +13 -0
  114. package/node_modules/@spexcode/spec-cli/dist/source-list.js +99 -0
  115. package/node_modules/@spexcode/spec-cli/dist/source-read.d.ts +16 -0
  116. package/node_modules/@spexcode/spec-cli/dist/source-read.js +84 -0
  117. package/node_modules/@spexcode/spec-cli/dist/spec-attachments.d.ts +7 -0
  118. package/node_modules/@spexcode/spec-cli/dist/spec-attachments.js +89 -0
  119. package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.d.ts +23 -0
  120. package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.js +138 -0
  121. package/node_modules/@spexcode/spec-cli/dist/supervise.js +15 -6
  122. package/node_modules/@spexcode/spec-cli/hooks/dispatch.sh +19 -31
  123. package/node_modules/@spexcode/spec-cli/hooks/harness.sh +6 -6
  124. package/node_modules/@spexcode/spec-cli/package.json +8 -6
  125. package/node_modules/@spexcode/spec-cli/templates/hooks/post-checkout +4 -2
  126. package/node_modules/@spexcode/spec-cli/templates/hooks/post-merge +2 -1
  127. package/node_modules/@spexcode/spec-cli/templates/hooks/pre-commit +5 -3
  128. package/node_modules/@spexcode/spec-cli/templates/hooks/reference-transaction +5 -3
  129. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/spec.md +2 -7
  130. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/idle.sh +4 -10
  131. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
  132. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/mark-active.sh +22 -24
  133. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +10 -2
  134. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/fail.sh +8 -7
  135. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +3 -1
  136. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/session-listen.sh +133 -0
  137. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/spec.md +36 -0
  138. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -0
  139. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +1 -1
  140. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +17 -20
  141. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/merge/spec.md +33 -0
  142. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/spec.md +2 -6
  143. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/spec.md +7 -0
  144. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +13 -2
  145. package/node_modules/@spexcode/spec-core/dist/anchors.js +311 -15
  146. package/node_modules/@spexcode/spec-core/dist/git.d.ts +4 -0
  147. package/node_modules/@spexcode/spec-core/dist/git.js +9 -9
  148. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +12 -1
  149. package/node_modules/@spexcode/spec-core/dist/layout.js +4 -2
  150. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +1 -0
  151. package/node_modules/@spexcode/spec-core/dist/resilience.js +8 -0
  152. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +1 -0
  153. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +7 -3
  154. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +1 -0
  155. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +4 -0
  156. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +2 -0
  157. package/node_modules/@spexcode/spec-core/dist/specs.js +30 -0
  158. package/node_modules/@spexcode/spec-core/package.json +4 -1
  159. package/node_modules/@spexcode/spec-core/templates/spexcode.json +36 -13
  160. package/node_modules/@spexcode/spec-eval/dist/cli.js +1 -1
  161. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +1 -0
  162. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +6 -1
  163. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +1 -1
  164. package/node_modules/@spexcode/spec-eval/dist/freshness.js +12 -12
  165. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +2 -1
  166. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +2 -1
  167. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +20 -3
  168. package/node_modules/@spexcode/spec-eval/package.json +2 -2
  169. package/node_modules/@spexcode/spec-forge/package.json +2 -2
  170. package/node_modules/@spexcode/transcript/dist/frames.d.ts +44 -0
  171. package/node_modules/@spexcode/transcript/dist/frames.js +85 -0
  172. package/node_modules/@spexcode/transcript/dist/index.d.ts +5 -0
  173. package/node_modules/@spexcode/transcript/dist/index.js +5 -0
  174. package/node_modules/@spexcode/transcript/dist/live.d.ts +16 -0
  175. package/node_modules/@spexcode/transcript/dist/live.js +65 -0
  176. package/node_modules/@spexcode/transcript/dist/parsers.d.ts +47 -0
  177. package/node_modules/@spexcode/transcript/dist/parsers.js +285 -0
  178. package/node_modules/@spexcode/transcript/dist/readers.d.ts +10 -0
  179. package/node_modules/@spexcode/transcript/dist/readers.js +296 -0
  180. package/node_modules/@spexcode/transcript/dist/turns.d.ts +42 -0
  181. package/node_modules/@spexcode/transcript/dist/turns.js +11 -0
  182. package/node_modules/@spexcode/transcript/package.json +30 -0
  183. package/node_modules/@vscode/tree-sitter-wasm/LICENSE +21 -0
  184. package/node_modules/@vscode/tree-sitter-wasm/README.md +36 -0
  185. package/node_modules/@vscode/tree-sitter-wasm/SECURITY.md +41 -0
  186. package/node_modules/@vscode/tree-sitter-wasm/cgmanifest.json +16 -0
  187. package/node_modules/@vscode/tree-sitter-wasm/package.json +42 -0
  188. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-bash.wasm +0 -0
  189. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-c-sharp.wasm +0 -0
  190. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-cpp.wasm +0 -0
  191. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-css.wasm +0 -0
  192. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-go.wasm +0 -0
  193. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ini.wasm +0 -0
  194. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-java.wasm +0 -0
  195. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-javascript.wasm +0 -0
  196. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-php.wasm +0 -0
  197. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-powershell.wasm +0 -0
  198. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-python.wasm +0 -0
  199. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-regex.wasm +0 -0
  200. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ruby.wasm +0 -0
  201. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-rust.wasm +0 -0
  202. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-tsx.wasm +0 -0
  203. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-typescript.wasm +0 -0
  204. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.js +4075 -0
  205. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.wasm +0 -0
  206. package/node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts +1027 -0
  207. package/node_modules/smol-toml/LICENSE +24 -0
  208. package/node_modules/smol-toml/README.md +418 -0
  209. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  210. package/node_modules/smol-toml/dist/date.js +127 -0
  211. package/node_modules/smol-toml/dist/error.d.ts +38 -0
  212. package/node_modules/smol-toml/dist/error.js +63 -0
  213. package/node_modules/smol-toml/dist/extract.js +69 -0
  214. package/node_modules/smol-toml/dist/index.cjs +734 -0
  215. package/node_modules/smol-toml/dist/index.d.ts +43 -0
  216. package/node_modules/smol-toml/dist/index.js +33 -0
  217. package/node_modules/smol-toml/dist/parse.d.ts +36 -0
  218. package/node_modules/smol-toml/dist/parse.js +149 -0
  219. package/node_modules/smol-toml/dist/primitive.js +238 -0
  220. package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
  221. package/node_modules/smol-toml/dist/stringify.js +181 -0
  222. package/node_modules/smol-toml/dist/struct.js +179 -0
  223. package/node_modules/smol-toml/dist/util.d.ts +38 -0
  224. package/node_modules/smol-toml/dist/util.js +89 -0
  225. package/node_modules/smol-toml/package.json +68 -0
  226. package/package.json +9 -3
  227. package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +0 -24
  228. package/node_modules/@spexcode/session-core/dist/delivery-queue.js +0 -206
  229. package/node_modules/@spexcode/session-core/dist/index.d.ts +0 -5
  230. package/node_modules/@spexcode/session-core/dist/index.js +0 -5
  231. package/node_modules/@spexcode/session-core/dist/internal.d.ts +0 -3
  232. package/node_modules/@spexcode/session-core/dist/internal.js +0 -3
  233. package/node_modules/@spexcode/session-core/dist/message.d.ts +0 -22
  234. package/node_modules/@spexcode/session-core/dist/message.js +0 -53
  235. package/node_modules/@spexcode/session-core/dist/record-lock.js +0 -152
  236. package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +0 -62
  237. package/node_modules/@spexcode/session-core/dist/runtime-session.js +0 -326
  238. package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +0 -14
  239. package/node_modules/@spexcode/session-core/dist/session-cursors.js +0 -82
  240. package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +0 -47
  241. package/node_modules/@spexcode/session-core/dist/session-timeline.js +0 -216
  242. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +0 -26
  243. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +0 -475
  244. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +0 -10
  245. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +0 -70
  246. package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -53
  247. package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-sed-v0.fixture +0 -46
@@ -0,0 +1,85 @@
1
+ import { TranscriptReadError } from './turns.js';
2
+ export const isErrorFrame = (frame) => 'error' in frame;
3
+ // The revision an absent source reports: the thread has not started writing. Not an error.
4
+ export const ABSENT_REVISION = 'absent';
5
+ export const withheld = (read) => ({
6
+ ...read,
7
+ turns: read.turns.map((turn) => turn.tools
8
+ ? { ...turn, tools: turn.tools.map((tool) => tool.output === undefined ? tool : { ...tool, output: null }) }
9
+ : turn),
10
+ });
11
+ // THE PRODUCER'S HALF. Feed it every read of the open interval; it returns the frame that read is worth — the
12
+ // first one is the whole interval (`full`), every later one a `delta` holding only the turns that are new or
13
+ // changed since the previous frame (a turn changes when a call in it gains its result) and the ids the turn cap
14
+ // evicted; the counters are always absolute — or `null` when the read changed nothing the subscriber holds.
15
+ export class FrameProducer {
16
+ primed = false; // a `full` frame has been delivered on this stream
17
+ sent = new Map(); // turn id → the serialized turn the subscriber holds
18
+ counters = ''; // the absolute counters as last sent
19
+ next(read) {
20
+ const held = withheld(read);
21
+ const next = new Map(held.turns.map((turn) => [turn.id, JSON.stringify(turn)]));
22
+ const changed = held.turns.filter((turn) => this.sent.get(turn.id) !== next.get(turn.id));
23
+ const removed = [...this.sent.keys()].filter((turnId) => !next.has(turnId));
24
+ const counters = `${held.truncated}:${held.omittedTurns}:${held.omittedBytes}:${held.outOfOrderEvents}`;
25
+ if (this.primed && !changed.length && !removed.length && counters === this.counters)
26
+ return null;
27
+ const frame = this.primed ? { ...held, kind: 'delta', turns: changed, removed } : { ...held, kind: 'full' };
28
+ this.sent = next;
29
+ this.counters = counters;
30
+ this.primed = true;
31
+ return frame;
32
+ }
33
+ // the next frame is `full` again — a subscriber that reconnects, or a source that vanished, holds nothing
34
+ reset() { this.primed = false; this.sent = new Map(); this.counters = ''; }
35
+ }
36
+ export const absentFrame = (from, to) => ({ kind: 'full', revision: ABSENT_REVISION, from, to, turns: [], truncated: false, omittedTurns: 0, omittedBytes: 0, outOfOrderEvents: 0 });
37
+ export function openFrameStream(reader, threadId, from, clock = () => Date.now()) {
38
+ let last;
39
+ let tail = null; // opened on the first non-absent revision
40
+ const producer = new FrameProducer();
41
+ return {
42
+ async publish(now = clock()) {
43
+ const revision = reader.revision(threadId) ?? ABSENT_REVISION;
44
+ if (revision === last)
45
+ return null;
46
+ last = revision;
47
+ const to = Math.max(from + 1, now);
48
+ if (revision === ABSENT_REVISION) {
49
+ producer.reset();
50
+ return absentFrame(from, to);
51
+ }
52
+ try {
53
+ return producer.next(await (tail ??= reader.tail(threadId, from)).advance(to));
54
+ }
55
+ catch (error) {
56
+ if (!(error instanceof TranscriptReadError))
57
+ throw error;
58
+ return { revision, from, to, error: error.message, reason: error.reason };
59
+ }
60
+ },
61
+ close() { tail?.close(); tail = null; },
62
+ };
63
+ }
64
+ export function mergeTranscriptFrame(state, frame) {
65
+ if (isErrorFrame(frame))
66
+ return { state, payload: frame };
67
+ const { kind, removed, turns: incoming, ...rest } = frame;
68
+ if (kind !== 'delta') {
69
+ const turns = [...(incoming || [])];
70
+ return { state: { turns }, payload: { ...rest, turns } };
71
+ }
72
+ const gone = new Set(removed || []);
73
+ const turns = state.turns.filter((turn) => !gone.has(turn.id));
74
+ const index = new Map(turns.map((turn, at) => [turn.id, at]));
75
+ for (const turn of incoming || []) {
76
+ const at = index.get(turn.id);
77
+ if (at === undefined) {
78
+ index.set(turn.id, turns.length);
79
+ turns.push(turn);
80
+ }
81
+ else
82
+ turns[at] = turn;
83
+ }
84
+ return { state: { turns }, payload: { ...rest, turns } };
85
+ }
@@ -0,0 +1,5 @@
1
+ export * from './turns.js';
2
+ export * from './parsers.js';
3
+ export * from './readers.js';
4
+ export * from './frames.js';
5
+ export * from './live.js';
@@ -0,0 +1,5 @@
1
+ export * from './turns.js';
2
+ export * from './parsers.js';
3
+ export * from './readers.js';
4
+ export * from './frames.js';
5
+ export * from './live.js';
@@ -0,0 +1,16 @@
1
+ import { type TranscriptRange, type TranscriptRead, type TranscriptReader, type TranscriptTail } from './turns.js';
2
+ import { type Parse } from './parsers.js';
3
+ export declare class LiveTranscript implements TranscriptReader {
4
+ private readonly parse;
5
+ readonly threadId: string;
6
+ private readonly events;
7
+ private writes;
8
+ private readonly listeners;
9
+ constructor(parse: Parse, threadId: string);
10
+ push(native: unknown): boolean;
11
+ onChange(listener: () => void): () => void;
12
+ private own;
13
+ revision(threadId: string): string | null;
14
+ read(threadId: string, range: TranscriptRange): Promise<TranscriptRead>;
15
+ tail(threadId: string, from: number): TranscriptTail;
16
+ }
@@ -0,0 +1,65 @@
1
+ import { TranscriptReadError } from './turns.js';
2
+ import { IntervalCollector } from './parsers.js';
3
+ // THE IN-MEMORY SOURCE. A headless controller already holds the harness's native events as they stream past —
4
+ // Claude's `stream-json` lines, an app-server's notifications — so a transcript need not be re-read from the
5
+ // file the harness also writes. Push each native event here, through the same parser the file reader uses, and
6
+ // this is a `TranscriptReader` like any other: the frame protocol, the interval read, and every renderer work
7
+ // unchanged, and `onChange` lets a producer publish on arrival instead of on a tick. One instance is one thread.
8
+ export class LiveTranscript {
9
+ parse;
10
+ threadId;
11
+ events = [];
12
+ writes = 0;
13
+ listeners = new Set();
14
+ constructor(parse, threadId) {
15
+ this.parse = parse;
16
+ this.threadId = threadId;
17
+ }
18
+ // `true` when the record meant something to the parser; an unrecognized record is not an error — a native
19
+ // stream carries plenty the transcript does not show (control replies, usage, the result envelope)
20
+ push(native) {
21
+ const event = this.parse(native);
22
+ if (!event)
23
+ return false;
24
+ this.events.push(event);
25
+ this.writes++;
26
+ for (const listener of this.listeners)
27
+ listener();
28
+ return true;
29
+ }
30
+ onChange(listener) {
31
+ this.listeners.add(listener);
32
+ return () => { this.listeners.delete(listener); };
33
+ }
34
+ own(threadId) {
35
+ if (threadId !== this.threadId)
36
+ throw new TranscriptReadError('missing', `live transcript holds thread ${this.threadId}, not ${threadId}`);
37
+ }
38
+ // `null` until the first event lands: the thread has not started, which the frame protocol reads as absent
39
+ revision(threadId) {
40
+ this.own(threadId);
41
+ return this.writes ? `${this.writes}` : null;
42
+ }
43
+ async read(threadId, range) {
44
+ this.own(threadId);
45
+ const collector = new IntervalCollector(range);
46
+ for (const event of this.events)
47
+ collector.add(event);
48
+ return collector.finish(`${this.writes}`, 'live');
49
+ }
50
+ // the cursor is an index into the event list: each advance collects only what was pushed since the last one
51
+ tail(threadId, from) {
52
+ this.own(threadId);
53
+ const collector = new IntervalCollector({ from, to: from });
54
+ let consumed = 0;
55
+ return {
56
+ advance: async (to) => {
57
+ collector.extend(to);
58
+ for (; consumed < this.events.length; consumed++)
59
+ collector.add(this.events[consumed]);
60
+ return collector.finish(`${this.writes}`, 'live');
61
+ },
62
+ close: () => { },
63
+ };
64
+ }
65
+ }
@@ -0,0 +1,47 @@
1
+ import { type TranscriptRange, type TranscriptRead } from './turns.js';
2
+ export declare const MAX_TURNS = 200;
3
+ export declare const MAX_OUTPUT_BYTES: number;
4
+ export type MutableTool = {
5
+ id: string;
6
+ name: string;
7
+ input?: string;
8
+ output?: string;
9
+ outputLines: number;
10
+ outputBytes: number;
11
+ };
12
+ export type MutableTurn = {
13
+ id: string | null;
14
+ at: number;
15
+ role: 'user' | 'assistant';
16
+ text?: string;
17
+ tools: MutableTool[];
18
+ };
19
+ export type ParsedEvent = {
20
+ at: number | null;
21
+ turn: MutableTurn | null;
22
+ toolOutputs?: readonly {
23
+ id: string;
24
+ text: string;
25
+ }[];
26
+ };
27
+ export type Parse = (value: unknown) => ParsedEvent | null;
28
+ export declare function claudeEvent(value: unknown): ParsedEvent | null;
29
+ export declare function codexEvent(value: unknown): ParsedEvent | null;
30
+ export declare function piEvent(value: unknown): ParsedEvent | null;
31
+ export declare function opencodeEvents(value: unknown): ParsedEvent[];
32
+ export declare class IntervalCollector {
33
+ readonly turns: MutableTurn[];
34
+ private readonly byTool;
35
+ private readonly evicted;
36
+ private readonly synthesized;
37
+ sawTimestamp: boolean;
38
+ omittedTurns: number;
39
+ omittedBytes: number;
40
+ outOfOrderEvents: number;
41
+ private pastRange;
42
+ private readonly range;
43
+ constructor(range: TranscriptRange);
44
+ extend(to: number): void;
45
+ add(event: ParsedEvent): boolean;
46
+ finish(revision: string, harness: string): TranscriptRead;
47
+ }
@@ -0,0 +1,285 @@
1
+ import { TranscriptReadError } from './turns.js';
2
+ // ONE PARSER PER HARNESS. Each function turns one native record — a line of Claude's project JSONL (which is
3
+ // also exactly what its `--output-format stream-json` prints), a Codex rollout line, a pi session line, an
4
+ // OpenCode export — into the normalized event the interval collector consumes. The same parser serves a file
5
+ // being tailed ([[transcript-reader]]) and an event stream held in memory ([[live-transcript]]): the source is
6
+ // where bytes come from, the parser is what they mean, and neither knows the other.
7
+ export const MAX_TURNS = 200;
8
+ export const MAX_OUTPUT_BYTES = 64 * 1024;
9
+ const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;
10
+ const items = (value) => Array.isArray(value) ? value : [];
11
+ const string = (value) => typeof value === 'string' && value.trim() ? value : null;
12
+ const idOf = (value) => {
13
+ if (!value)
14
+ return null;
15
+ for (const key of ['id', 'uuid', 'message_id', 'messageId', 'call_id', 'callId', 'client_id', 'clientId']) {
16
+ const found = string(value[key]);
17
+ if (found)
18
+ return found;
19
+ }
20
+ return null;
21
+ };
22
+ const timestamp = (value) => {
23
+ if (typeof value === 'number' && Number.isFinite(value))
24
+ return value;
25
+ if (typeof value !== 'string' || !value.trim())
26
+ return null;
27
+ const numeric = Number(value);
28
+ if (Number.isFinite(numeric))
29
+ return numeric;
30
+ const parsed = Date.parse(value);
31
+ return Number.isFinite(parsed) ? parsed : null;
32
+ };
33
+ const at = (value) => {
34
+ if (!value)
35
+ return null;
36
+ for (const key of ['timestamp', 'created_at', 'createdAt', 'created', 'time']) {
37
+ const candidate = timestamp(value[key]);
38
+ if (candidate !== null)
39
+ return candidate;
40
+ }
41
+ return null;
42
+ };
43
+ const compact = (value) => {
44
+ if (typeof value === 'string')
45
+ return value;
46
+ try {
47
+ return JSON.stringify(value) ?? String(value);
48
+ }
49
+ catch {
50
+ return String(value);
51
+ }
52
+ };
53
+ const lineCount = (value) => value ? value.split(/\r?\n/).length : 0;
54
+ // --- the four native shapes --------------------------------------------------------------------------------
55
+ export function claudeEvent(value) {
56
+ const entry = object(value);
57
+ const message = object(entry?.message);
58
+ if (!entry || !message)
59
+ return null;
60
+ const eventAt = at(entry) ?? at(message);
61
+ if (eventAt === null)
62
+ return { at: null, turn: null };
63
+ if (entry.type === 'user' && message.role === 'user') {
64
+ const blocks = items(message.content);
65
+ const text = typeof message.content === 'string'
66
+ ? string(message.content)
67
+ : blocks.map((block) => string(object(block)?.text)).filter(Boolean).join('\n') || null;
68
+ const outputs = blocks.flatMap((block) => {
69
+ const b = object(block);
70
+ const id = string(b?.tool_use_id);
71
+ return b?.type === 'tool_result' && id ? [{ id, text: compact(b?.content ?? '') }] : [];
72
+ });
73
+ if (outputs.length)
74
+ return { at: eventAt, turn: null, toolOutputs: outputs };
75
+ if (text)
76
+ return { at: eventAt, turn: { id: idOf(entry) ?? idOf(message), at: eventAt, role: 'user', text, tools: [] } };
77
+ }
78
+ if (entry.type === 'assistant' && message.role === 'assistant') {
79
+ const turn = { id: idOf(entry) ?? idOf(message), at: eventAt, role: 'assistant', tools: [] };
80
+ for (const blockValue of items(message.content)) {
81
+ const block = object(blockValue);
82
+ if (block?.type === 'text')
83
+ turn.text = [turn.text, string(block.text)].filter(Boolean).join('\n') || undefined;
84
+ if (block?.type === 'tool_use') {
85
+ const id = string(block.id) ?? `tool-${turn.tools.length}`;
86
+ turn.tools.push({ id, name: string(block.name) ?? 'tool', input: block.input === undefined ? undefined : compact(block.input), outputLines: 0, outputBytes: 0 });
87
+ }
88
+ }
89
+ return { at: eventAt, turn };
90
+ }
91
+ return null;
92
+ }
93
+ export function codexEvent(value) {
94
+ const entry = object(value);
95
+ const payload = object(entry?.payload);
96
+ if (!entry || !payload)
97
+ return null;
98
+ const eventAt = at(payload) ?? at(entry);
99
+ if (eventAt === null)
100
+ return { at: null, turn: null };
101
+ const type = string(payload.type);
102
+ if ((entry.type === 'event_msg' && type === 'user_message')
103
+ || (entry.type === 'response_item' && (type === 'message' || type === 'input_message') && payload.role === 'user')) {
104
+ const text = typeof payload.message === 'string' ? payload.message : compact(payload.message ?? payload.content ?? '');
105
+ return text ? { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'user', text, tools: [] } } : null;
106
+ }
107
+ // commentary AND the final answer are both what the agent said; only structured reasoning stays private
108
+ if (entry.type === 'event_msg' && type === 'agent_message') {
109
+ const text = string(payload.message ?? payload.text);
110
+ return text ? { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'assistant', text, tools: [] } } : null;
111
+ }
112
+ if (entry.type === 'response_item' && (type === 'custom_tool_call' || type === 'function_call')) {
113
+ const id = string(payload.call_id ?? payload.id) ?? 'tool';
114
+ return { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'assistant', tools: [{ id, name: string(payload.name ?? payload.tool_name) ?? 'tool', input: payload.input === undefined && payload.arguments === undefined ? undefined : compact(payload.input ?? payload.arguments), outputLines: 0, outputBytes: 0 }] } };
115
+ }
116
+ if (entry.type === 'response_item' && (type === 'custom_tool_call_output' || type === 'function_call_output')) {
117
+ const id = string(payload.call_id ?? payload.id);
118
+ const output = payload.output ?? payload.result ?? '';
119
+ return id ? { at: eventAt, turn: null, toolOutputs: [{ id, text: compact(output) }] } : null;
120
+ }
121
+ return null;
122
+ }
123
+ const blockText = (content) => typeof content === 'string'
124
+ ? string(content)
125
+ : items(content).map((block) => string(object(block)?.text)).filter(Boolean).join('\n') || null;
126
+ export function piEvent(value) {
127
+ const entry = object(value);
128
+ const message = object(entry?.message);
129
+ if (!entry || entry.type !== 'message' || !message)
130
+ return null;
131
+ const eventAt = at(entry) ?? at(message);
132
+ if (eventAt === null)
133
+ return { at: null, turn: null };
134
+ if (message.role === 'user') {
135
+ const text = blockText(message.content);
136
+ return text ? { at: eventAt, turn: { id: idOf(entry) ?? idOf(message), at: eventAt, role: 'user', text, tools: [] } } : null;
137
+ }
138
+ if (message.role === 'assistant') {
139
+ const turn = { id: idOf(entry) ?? idOf(message), at: eventAt, role: 'assistant', tools: [] };
140
+ for (const blockValue of items(message.content)) {
141
+ const block = object(blockValue);
142
+ if (block?.type === 'text')
143
+ turn.text = [turn.text, string(block.text)].filter(Boolean).join('\n') || undefined;
144
+ if (block?.type === 'toolCall') {
145
+ const id = string(block.id) ?? `tool-${turn.tools.length}`;
146
+ turn.tools.push({ id, name: string(block.name) ?? 'tool', input: block.arguments === undefined ? undefined : compact(block.arguments), outputLines: 0, outputBytes: 0 });
147
+ }
148
+ }
149
+ return { at: eventAt, turn };
150
+ }
151
+ if (message.role === 'toolResult') {
152
+ const id = string(message.toolCallId);
153
+ return id ? { at: eventAt, turn: null, toolOutputs: [{ id, text: blockText(message.content) ?? compact(message.content ?? '') }] } : null;
154
+ }
155
+ return null;
156
+ }
157
+ // OpenCode's export is one JSON document, not a line stream: every message arrives with its parts, and a tool
158
+ // part already carries its own result — so its turn is complete on arrival, and a part still running simply has
159
+ // no output yet.
160
+ export function opencodeEvents(value) {
161
+ const events = [];
162
+ for (const messageValue of items(object(value)?.messages)) {
163
+ const message = object(messageValue);
164
+ const info = object(message?.info);
165
+ if (!message || !info)
166
+ continue;
167
+ const eventAt = at(object(info.time)) ?? at(info);
168
+ if (eventAt === null) {
169
+ events.push({ at: null, turn: null });
170
+ continue;
171
+ }
172
+ const role = info.role === 'user' ? 'user' : info.role === 'assistant' ? 'assistant' : null;
173
+ if (!role)
174
+ continue;
175
+ const turn = { id: idOf(info) ?? idOf(message), at: eventAt, role, tools: [] };
176
+ for (const partValue of items(message.parts)) {
177
+ const part = object(partValue);
178
+ if (part?.type === 'text')
179
+ turn.text = [turn.text, string(part.text)].filter(Boolean).join('\n') || undefined;
180
+ if (part?.type === 'tool' && role === 'assistant') {
181
+ const state = object(part.state);
182
+ const status = (string(state?.status) ?? '').toLowerCase();
183
+ const tool = { id: string(part.callID ?? part.id) ?? `tool-${turn.tools.length}`, name: string(part.tool) ?? 'tool', input: state?.input === undefined ? undefined : compact(state.input), outputLines: 0, outputBytes: 0 };
184
+ if (/completed|error|cancelled/.test(status)) {
185
+ const output = compact(state?.output ?? state?.error ?? '');
186
+ tool.output = output.slice(0, MAX_OUTPUT_BYTES);
187
+ tool.outputBytes = Buffer.byteLength(output);
188
+ tool.outputLines = lineCount(output);
189
+ }
190
+ turn.tools.push(tool);
191
+ }
192
+ }
193
+ if (role === 'user' && !turn.text)
194
+ continue;
195
+ events.push({ at: eventAt, turn });
196
+ }
197
+ return events;
198
+ }
199
+ // --- reading an interval ------------------------------------------------------------------------------------
200
+ // Collects the interval's turns from any event source. Caps keep the NEWEST turns, because a live tail and a
201
+ // closed stretch are both read for what happened last; every dropped turn or byte is counted, never hidden.
202
+ export class IntervalCollector {
203
+ turns = [];
204
+ byTool = new Map();
205
+ evicted = new Set();
206
+ synthesized = new Map(); // `<role>@<at>` → how many turns already wore it
207
+ sawTimestamp = false;
208
+ omittedTurns = 0;
209
+ omittedBytes = 0;
210
+ outOfOrderEvents = 0;
211
+ pastRange = false;
212
+ range;
213
+ constructor(range) { this.range = { from: range.from, to: range.to }; }
214
+ // the open interval's end is "now" and moves; extending it never revisits what was already collected
215
+ extend(to) { if (to > this.range.to)
216
+ this.range.to = to; }
217
+ // returns true once the source has moved past `to` (the caller may then bound its lookahead)
218
+ add(event) {
219
+ const eventAt = event.at;
220
+ if (eventAt === null)
221
+ return this.pastRange;
222
+ this.sawTimestamp = true;
223
+ if (!this.pastRange && eventAt > this.range.to)
224
+ this.pastRange = true;
225
+ else if (this.pastRange && eventAt <= this.range.to)
226
+ this.outOfOrderEvents++;
227
+ if (eventAt < this.range.from || eventAt > this.range.to)
228
+ return this.pastRange;
229
+ if (event.toolOutputs) {
230
+ for (const output of event.toolOutputs) {
231
+ const bytes = Buffer.byteLength(output.text);
232
+ const tool = this.byTool.get(output.id);
233
+ if (!tool || this.evicted.has(output.id)) {
234
+ this.omittedBytes += bytes;
235
+ continue;
236
+ }
237
+ tool.outputBytes += bytes;
238
+ tool.outputLines += lineCount(output.text);
239
+ if (tool.output === undefined)
240
+ tool.output = '';
241
+ const remaining = Math.max(0, MAX_OUTPUT_BYTES - Buffer.byteLength(tool.output));
242
+ tool.output += output.text.slice(0, remaining);
243
+ if (bytes > remaining)
244
+ this.omittedBytes += bytes - remaining;
245
+ }
246
+ }
247
+ else if (event.turn) {
248
+ // the parsed event is never written to: an in-memory source ([[live-transcript]]) collects the same
249
+ // event again on every read, so the collector works on its own copy of the turn and its calls
250
+ const turn = { ...event.turn, tools: event.turn.tools.map((tool) => ({ ...tool })) };
251
+ // a turn without a native id gets one from its place in the thread — deterministic across re-reads of an
252
+ // append-only source, which is what lets a subscriber match it between frames
253
+ if (turn.id === null) {
254
+ const base = `${turn.role}@${turn.at}`;
255
+ const seen = this.synthesized.get(base) ?? 0;
256
+ this.synthesized.set(base, seen + 1);
257
+ turn.id = seen ? `${base}#${seen}` : base;
258
+ }
259
+ this.turns.push(turn);
260
+ for (const tool of turn.tools)
261
+ this.byTool.set(tool.id, tool);
262
+ if (this.turns.length > MAX_TURNS) {
263
+ const dropped = this.turns.shift();
264
+ for (const tool of dropped.tools)
265
+ this.evicted.add(tool.id);
266
+ this.omittedTurns++;
267
+ }
268
+ }
269
+ return this.pastRange;
270
+ }
271
+ finish(revision, harness) {
272
+ if (!this.sawTimestamp)
273
+ throw new TranscriptReadError('invalid', `${harness} transcript has no reliable timestamps; interval reads are unavailable`);
274
+ return {
275
+ revision,
276
+ from: this.range.from,
277
+ to: this.range.to,
278
+ turns: this.turns.map((turn) => ({ ...turn, id: turn.id, tools: turn.tools.length ? turn.tools.map((tool) => ({ ...tool })) : undefined })),
279
+ truncated: this.omittedTurns > 0 || this.omittedBytes > 0 || this.outOfOrderEvents > 0,
280
+ omittedTurns: this.omittedTurns,
281
+ omittedBytes: this.omittedBytes,
282
+ outOfOrderEvents: this.outOfOrderEvents,
283
+ };
284
+ }
285
+ }
@@ -0,0 +1,10 @@
1
+ import { type TranscriptReader } from './turns.js';
2
+ export declare function claudeTranscriptPath(threadId: string, root?: string): string | null;
3
+ export declare function codexRolloutPath(threadId: string, root?: string): string | null;
4
+ export declare function piSessionPath(threadId: string, root?: string): string | null;
5
+ export declare const claudeTranscript: TranscriptReader;
6
+ export declare const codexTranscript: TranscriptReader;
7
+ export declare const piTranscript: TranscriptReader;
8
+ export declare function opencodeTranscriptReader(root?: string, load?: (threadId: string) => string): TranscriptReader;
9
+ export declare const opencodeTranscript: TranscriptReader;
10
+ export declare function unsupportedTranscript(harness: string): TranscriptReader;