@hellcoder/companion 0.96.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 (242) hide show
  1. package/bin/cli.ts +168 -0
  2. package/bin/ctl.ts +528 -0
  3. package/bin/generate-token.ts +28 -0
  4. package/dist/apple-touch-icon.png +0 -0
  5. package/dist/assets/AgentsPage-DCFhrJ28.js +13 -0
  6. package/dist/assets/CronManager-EGwLJONv.js +1 -0
  7. package/dist/assets/IntegrationsPage-CTMRnbQS.js +1 -0
  8. package/dist/assets/LinearOAuthSettingsPage-CgQFMIgr.js +1 -0
  9. package/dist/assets/LinearSettingsPage-C9nok1qi.js +1 -0
  10. package/dist/assets/Playground-BV3k0RbV.js +109 -0
  11. package/dist/assets/PromptsPage-CFojqNKP.js +4 -0
  12. package/dist/assets/RunsPage-DUJ1QUSa.js +1 -0
  13. package/dist/assets/SandboxManager-CrVQ-VU_.js +8 -0
  14. package/dist/assets/SettingsPage-D1fPCL19.js +1 -0
  15. package/dist/assets/TailscalePage-D06cyvyC.js +1 -0
  16. package/dist/assets/index-BhUa1e6X.css +1 -0
  17. package/dist/assets/index-DkqeP-R9.js +134 -0
  18. package/dist/assets/sw-register-BibwRdvC.js +1 -0
  19. package/dist/assets/workbox-window.prod.es5-BIl4cyR9.js +2 -0
  20. package/dist/favicon.svg +8 -0
  21. package/dist/fonts/MesloLGSNerdFontMono-Bold.woff2 +0 -0
  22. package/dist/fonts/MesloLGSNerdFontMono-Regular.woff2 +0 -0
  23. package/dist/icon-192.png +0 -0
  24. package/dist/icon-512.png +0 -0
  25. package/dist/index.html +20 -0
  26. package/dist/logo-codex.svg +14 -0
  27. package/dist/logo-docker.svg +4 -0
  28. package/dist/logo.svg +14 -0
  29. package/dist/manifest.json +24 -0
  30. package/dist/sw.js +2 -0
  31. package/package.json +104 -0
  32. package/server/agent-cron-migrator.test.ts +610 -0
  33. package/server/agent-cron-migrator.ts +85 -0
  34. package/server/agent-executor.test.ts +1108 -0
  35. package/server/agent-executor.ts +346 -0
  36. package/server/agent-store.test.ts +588 -0
  37. package/server/agent-store.ts +185 -0
  38. package/server/agent-types.ts +138 -0
  39. package/server/ai-validation-settings.test.ts +128 -0
  40. package/server/ai-validation-settings.ts +35 -0
  41. package/server/ai-validator.test.ts +387 -0
  42. package/server/ai-validator.ts +271 -0
  43. package/server/auth-manager.test.ts +83 -0
  44. package/server/auth-manager.ts +150 -0
  45. package/server/auto-namer.test.ts +252 -0
  46. package/server/auto-namer.ts +78 -0
  47. package/server/backend-adapter.test.ts +38 -0
  48. package/server/backend-adapter.ts +54 -0
  49. package/server/cache-headers.test.ts +98 -0
  50. package/server/cache-headers.ts +61 -0
  51. package/server/claude-adapter.test.ts +1363 -0
  52. package/server/claude-adapter.ts +889 -0
  53. package/server/claude-container-auth.test.ts +44 -0
  54. package/server/claude-container-auth.ts +30 -0
  55. package/server/claude-protocol-contract.test.ts +71 -0
  56. package/server/claude-protocol-drift.test.ts +78 -0
  57. package/server/claude-session-discovery.test.ts +132 -0
  58. package/server/claude-session-discovery.ts +157 -0
  59. package/server/claude-session-history.test.ts +158 -0
  60. package/server/claude-session-history.ts +410 -0
  61. package/server/cli-launcher.test.ts +1343 -0
  62. package/server/cli-launcher.ts +1298 -0
  63. package/server/cli.test.ts +16 -0
  64. package/server/codex-adapter.test.ts +5545 -0
  65. package/server/codex-adapter.ts +3062 -0
  66. package/server/codex-container-auth.test.ts +50 -0
  67. package/server/codex-container-auth.ts +24 -0
  68. package/server/codex-home.test.ts +61 -0
  69. package/server/codex-home.ts +26 -0
  70. package/server/codex-protocol-contract.test.ts +96 -0
  71. package/server/codex-protocol-drift.test.ts +123 -0
  72. package/server/codex-ws-proxy.cjs +226 -0
  73. package/server/commands-discovery.test.ts +179 -0
  74. package/server/commands-discovery.ts +81 -0
  75. package/server/constants.ts +7 -0
  76. package/server/container-manager.test.ts +1211 -0
  77. package/server/container-manager.ts +1053 -0
  78. package/server/cron-scheduler.test.ts +957 -0
  79. package/server/cron-scheduler.ts +243 -0
  80. package/server/cron-store.test.ts +422 -0
  81. package/server/cron-store.ts +148 -0
  82. package/server/cron-types.ts +63 -0
  83. package/server/env-manager.test.ts +268 -0
  84. package/server/env-manager.ts +161 -0
  85. package/server/event-bus-types.ts +64 -0
  86. package/server/event-bus.test.ts +244 -0
  87. package/server/event-bus.ts +124 -0
  88. package/server/execution-store.test.ts +307 -0
  89. package/server/execution-store.ts +170 -0
  90. package/server/fs-utils.ts +15 -0
  91. package/server/git-utils.test.ts +938 -0
  92. package/server/git-utils.ts +421 -0
  93. package/server/github-pr.test.ts +498 -0
  94. package/server/github-pr.ts +379 -0
  95. package/server/image-pull-manager.test.ts +303 -0
  96. package/server/image-pull-manager.ts +279 -0
  97. package/server/index.ts +396 -0
  98. package/server/linear-agent-bridge.test.ts +1157 -0
  99. package/server/linear-agent-bridge.ts +629 -0
  100. package/server/linear-agent.test.ts +473 -0
  101. package/server/linear-agent.ts +479 -0
  102. package/server/linear-cache.test.ts +136 -0
  103. package/server/linear-cache.ts +113 -0
  104. package/server/linear-connections.test.ts +350 -0
  105. package/server/linear-connections.ts +231 -0
  106. package/server/linear-credential-migration.test.ts +337 -0
  107. package/server/linear-credential-migration.ts +63 -0
  108. package/server/linear-oauth-connections-migration.test.ts +268 -0
  109. package/server/linear-oauth-connections.test.ts +365 -0
  110. package/server/linear-oauth-connections.ts +294 -0
  111. package/server/linear-project-manager.test.ts +162 -0
  112. package/server/linear-project-manager.ts +111 -0
  113. package/server/linear-prompt-builder.test.ts +74 -0
  114. package/server/linear-prompt-builder.ts +61 -0
  115. package/server/linear-staging.test.ts +276 -0
  116. package/server/linear-staging.ts +142 -0
  117. package/server/logger.test.ts +393 -0
  118. package/server/logger.ts +259 -0
  119. package/server/metrics-collector.test.ts +413 -0
  120. package/server/metrics-collector.ts +350 -0
  121. package/server/metrics-types.ts +108 -0
  122. package/server/middleware/managed-auth.test.ts +264 -0
  123. package/server/middleware/managed-auth.ts +195 -0
  124. package/server/novnc-proxy.test.ts +333 -0
  125. package/server/novnc-proxy.ts +99 -0
  126. package/server/path-resolver.test.ts +552 -0
  127. package/server/path-resolver.ts +186 -0
  128. package/server/paths.test.ts +31 -0
  129. package/server/paths.ts +11 -0
  130. package/server/pr-poller.test.ts +191 -0
  131. package/server/pr-poller.ts +162 -0
  132. package/server/prompt-manager.test.ts +211 -0
  133. package/server/prompt-manager.ts +211 -0
  134. package/server/protocol/claude-upstream/README.md +19 -0
  135. package/server/protocol/claude-upstream/sdk.d.ts.txt +1943 -0
  136. package/server/protocol/codex-upstream/ClientNotification.ts.txt +5 -0
  137. package/server/protocol/codex-upstream/ClientRequest.ts.txt +60 -0
  138. package/server/protocol/codex-upstream/README.md +18 -0
  139. package/server/protocol/codex-upstream/ServerNotification.ts.txt +41 -0
  140. package/server/protocol/codex-upstream/ServerRequest.ts.txt +16 -0
  141. package/server/protocol/codex-upstream/v2/DynamicToolCallParams.ts.txt +6 -0
  142. package/server/protocol/codex-upstream/v2/DynamicToolCallResponse.ts.txt +6 -0
  143. package/server/protocol-monitor.ts +50 -0
  144. package/server/recorder.test.ts +454 -0
  145. package/server/recorder.ts +374 -0
  146. package/server/recording-hub/compat-validator.test.ts +150 -0
  147. package/server/recording-hub/compat-validator.ts +284 -0
  148. package/server/recording-hub/diagnostics.test.ts +140 -0
  149. package/server/recording-hub/diagnostics.ts +299 -0
  150. package/server/recording-hub/hub-config.test.ts +44 -0
  151. package/server/recording-hub/hub-config.ts +19 -0
  152. package/server/recording-hub/hub-routes.test.ts +417 -0
  153. package/server/recording-hub/hub-routes.ts +236 -0
  154. package/server/recording-hub/hub-store.test.ts +262 -0
  155. package/server/recording-hub/hub-store.ts +265 -0
  156. package/server/recording-hub/replay-adapter.test.ts +294 -0
  157. package/server/recording-hub/replay-adapter.ts +207 -0
  158. package/server/relay-client.test.ts +337 -0
  159. package/server/relay-client.ts +320 -0
  160. package/server/replay.test.ts +200 -0
  161. package/server/replay.ts +78 -0
  162. package/server/routes/agent-routes.test.ts +1400 -0
  163. package/server/routes/agent-routes.ts +409 -0
  164. package/server/routes/cron-routes.test.ts +881 -0
  165. package/server/routes/cron-routes.ts +103 -0
  166. package/server/routes/env-routes.test.ts +383 -0
  167. package/server/routes/env-routes.ts +95 -0
  168. package/server/routes/fs-routes.test.ts +1198 -0
  169. package/server/routes/fs-routes.ts +605 -0
  170. package/server/routes/git-routes.test.ts +813 -0
  171. package/server/routes/git-routes.ts +97 -0
  172. package/server/routes/linear-agent-routes.test.ts +721 -0
  173. package/server/routes/linear-agent-routes.ts +304 -0
  174. package/server/routes/linear-connection-routes.test.ts +927 -0
  175. package/server/routes/linear-connection-routes.ts +244 -0
  176. package/server/routes/linear-oauth-connection-routes.test.ts +406 -0
  177. package/server/routes/linear-oauth-connection-routes.ts +129 -0
  178. package/server/routes/linear-routes.test.ts +1510 -0
  179. package/server/routes/linear-routes.ts +953 -0
  180. package/server/routes/metrics-routes.test.ts +103 -0
  181. package/server/routes/metrics-routes.ts +13 -0
  182. package/server/routes/prompt-routes.ts +67 -0
  183. package/server/routes/sandbox-routes.test.ts +513 -0
  184. package/server/routes/sandbox-routes.ts +127 -0
  185. package/server/routes/settings-routes.ts +270 -0
  186. package/server/routes/skills-routes.test.ts +690 -0
  187. package/server/routes/skills-routes.ts +100 -0
  188. package/server/routes/system-routes.test.ts +637 -0
  189. package/server/routes/system-routes.ts +228 -0
  190. package/server/routes/tailscale-routes.test.ts +176 -0
  191. package/server/routes/tailscale-routes.ts +22 -0
  192. package/server/routes.test.ts +4655 -0
  193. package/server/routes.ts +1277 -0
  194. package/server/sandbox-manager.test.ts +378 -0
  195. package/server/sandbox-manager.ts +168 -0
  196. package/server/service.test.ts +1419 -0
  197. package/server/service.ts +718 -0
  198. package/server/session-creation-service.test.ts +661 -0
  199. package/server/session-creation-service.ts +473 -0
  200. package/server/session-git-info.ts +104 -0
  201. package/server/session-linear-issues.test.ts +118 -0
  202. package/server/session-linear-issues.ts +88 -0
  203. package/server/session-names.test.ts +94 -0
  204. package/server/session-names.ts +67 -0
  205. package/server/session-orchestrator.test.ts +1784 -0
  206. package/server/session-orchestrator.ts +973 -0
  207. package/server/session-state-machine.test.ts +606 -0
  208. package/server/session-state-machine.ts +207 -0
  209. package/server/session-store.test.ts +290 -0
  210. package/server/session-store.ts +146 -0
  211. package/server/session-types.ts +509 -0
  212. package/server/settings-manager.test.ts +275 -0
  213. package/server/settings-manager.ts +173 -0
  214. package/server/tailscale-manager.test.ts +553 -0
  215. package/server/tailscale-manager.ts +451 -0
  216. package/server/terminal-manager.ts +240 -0
  217. package/server/update-checker.test.ts +306 -0
  218. package/server/update-checker.ts +197 -0
  219. package/server/usage-limits.test.ts +536 -0
  220. package/server/usage-limits.ts +225 -0
  221. package/server/worktree-tracker.test.ts +243 -0
  222. package/server/worktree-tracker.ts +84 -0
  223. package/server/ws-auth.test.ts +59 -0
  224. package/server/ws-auth.ts +41 -0
  225. package/server/ws-bridge-browser-ingest.test.ts +272 -0
  226. package/server/ws-bridge-browser-ingest.ts +72 -0
  227. package/server/ws-bridge-browser.ts +112 -0
  228. package/server/ws-bridge-cli-ingest.test.ts +302 -0
  229. package/server/ws-bridge-cli-ingest.ts +81 -0
  230. package/server/ws-bridge-codex.test.ts +1837 -0
  231. package/server/ws-bridge-codex.ts +266 -0
  232. package/server/ws-bridge-controls.test.ts +124 -0
  233. package/server/ws-bridge-controls.ts +20 -0
  234. package/server/ws-bridge-persist.test.ts +296 -0
  235. package/server/ws-bridge-persist.ts +66 -0
  236. package/server/ws-bridge-publish.test.ts +234 -0
  237. package/server/ws-bridge-publish.ts +79 -0
  238. package/server/ws-bridge-replay.test.ts +44 -0
  239. package/server/ws-bridge-replay.ts +61 -0
  240. package/server/ws-bridge-types.ts +106 -0
  241. package/server/ws-bridge.test.ts +4777 -0
  242. package/server/ws-bridge.ts +1279 -0
@@ -0,0 +1,200 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import {
5
+ loadRecording,
6
+ filterEntries,
7
+ getExpectedBrowserMessages,
8
+ getIncomingCLIMessages,
9
+ } from "./replay.js";
10
+ import type { RecordingHeader, RecordingEntry } from "./recorder.js";
11
+
12
+ let tempDir: string;
13
+
14
+ beforeEach(() => {
15
+ tempDir = mkdtempSync(join(tmpdir(), "replay-test-"));
16
+ });
17
+
18
+ afterEach(() => {
19
+ rmSync(tempDir, { recursive: true, force: true });
20
+ });
21
+
22
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
23
+
24
+ function makeHeader(overrides: Partial<RecordingHeader> = {}): RecordingHeader {
25
+ return {
26
+ _header: true,
27
+ version: 1,
28
+ session_id: "test-session",
29
+ backend_type: "claude",
30
+ started_at: 1739654400000,
31
+ cwd: "/project",
32
+ ...overrides,
33
+ };
34
+ }
35
+
36
+ function makeEntry(overrides: Partial<RecordingEntry> = {}): RecordingEntry {
37
+ return {
38
+ ts: Date.now(),
39
+ dir: "in",
40
+ raw: '{"type":"system","subtype":"init"}',
41
+ ch: "cli",
42
+ ...overrides,
43
+ };
44
+ }
45
+
46
+ /** Write a JSONL recording file and return its path. */
47
+ function writeRecording(
48
+ header: RecordingHeader,
49
+ entries: RecordingEntry[],
50
+ filename = "test.jsonl",
51
+ ): string {
52
+ const lines = [JSON.stringify(header), ...entries.map((e) => JSON.stringify(e))];
53
+ const filePath = join(tempDir, filename);
54
+ writeFileSync(filePath, lines.join("\n") + "\n");
55
+ return filePath;
56
+ }
57
+
58
+ // ─── loadRecording ───────────────────────────────────────────────────────────
59
+
60
+ describe("loadRecording", () => {
61
+ it("parses a valid JSONL recording with header and entries", () => {
62
+ const header = makeHeader();
63
+ const entries = [
64
+ makeEntry({ dir: "in", raw: '{"type":"system"}', ch: "cli" }),
65
+ makeEntry({ dir: "out", raw: '{"type":"session_init"}', ch: "browser" }),
66
+ ];
67
+ const path = writeRecording(header, entries);
68
+
69
+ const recording = loadRecording(path);
70
+
71
+ expect(recording.header._header).toBe(true);
72
+ expect(recording.header.version).toBe(1);
73
+ expect(recording.header.session_id).toBe("test-session");
74
+ expect(recording.header.backend_type).toBe("claude");
75
+ expect(recording.entries).toHaveLength(2);
76
+ expect(recording.entries[0].dir).toBe("in");
77
+ expect(recording.entries[0].ch).toBe("cli");
78
+ expect(recording.entries[1].dir).toBe("out");
79
+ expect(recording.entries[1].ch).toBe("browser");
80
+ });
81
+
82
+ it("throws on empty file", () => {
83
+ const path = join(tempDir, "empty.jsonl");
84
+ writeFileSync(path, "");
85
+
86
+ expect(() => loadRecording(path)).toThrow("empty");
87
+ });
88
+
89
+ it("throws on missing header", () => {
90
+ // Write a file where the first line is not a header
91
+ const path = join(tempDir, "no-header.jsonl");
92
+ writeFileSync(path, '{"ts":123,"dir":"in","raw":"hello","ch":"cli"}\n');
93
+
94
+ expect(() => loadRecording(path)).toThrow("Invalid recording header");
95
+ });
96
+
97
+ it("throws on wrong header version", () => {
98
+ const path = join(tempDir, "bad-version.jsonl");
99
+ writeFileSync(
100
+ path,
101
+ JSON.stringify({ _header: true, version: 99, session_id: "x", backend_type: "claude", started_at: 0, cwd: "/" }) + "\n",
102
+ );
103
+
104
+ expect(() => loadRecording(path)).toThrow("version");
105
+ });
106
+
107
+ it("skips malformed entry lines gracefully", () => {
108
+ // A recording file where one entry line is corrupt (simulating truncation)
109
+ const header = makeHeader();
110
+ const path = join(tempDir, "corrupt-entry.jsonl");
111
+ writeFileSync(
112
+ path,
113
+ [
114
+ JSON.stringify(header),
115
+ JSON.stringify(makeEntry({ raw: "good" })),
116
+ "{not valid json!!!",
117
+ JSON.stringify(makeEntry({ raw: "also-good" })),
118
+ ].join("\n") + "\n",
119
+ );
120
+
121
+ const recording = loadRecording(path);
122
+ expect(recording.entries).toHaveLength(2);
123
+ expect(recording.entries[0].raw).toBe("good");
124
+ expect(recording.entries[1].raw).toBe("also-good");
125
+ });
126
+
127
+ it("preserves raw strings exactly", () => {
128
+ // Raw strings should be stored verbatim, including any internal formatting
129
+ const rawWithSpaces = '{"type":"system", "subtype": "init"}';
130
+ const header = makeHeader();
131
+ const entries = [makeEntry({ raw: rawWithSpaces })];
132
+ const path = writeRecording(header, entries);
133
+
134
+ const recording = loadRecording(path);
135
+ expect(recording.entries[0].raw).toBe(rawWithSpaces);
136
+ });
137
+
138
+ it("handles header-only file (no entries)", () => {
139
+ const header = makeHeader();
140
+ const path = writeRecording(header, []);
141
+
142
+ const recording = loadRecording(path);
143
+ expect(recording.header.session_id).toBe("test-session");
144
+ expect(recording.entries).toHaveLength(0);
145
+ });
146
+ });
147
+
148
+ // ─── filterEntries ───────────────────────────────────────────────────────────
149
+
150
+ describe("filterEntries", () => {
151
+ it("filters by direction and channel", () => {
152
+ const entries = [
153
+ makeEntry({ dir: "in", ch: "cli", raw: "a" }),
154
+ makeEntry({ dir: "out", ch: "cli", raw: "b" }),
155
+ makeEntry({ dir: "in", ch: "browser", raw: "c" }),
156
+ makeEntry({ dir: "out", ch: "browser", raw: "d" }),
157
+ ];
158
+
159
+ expect(filterEntries(entries, "in", "cli")).toHaveLength(1);
160
+ expect(filterEntries(entries, "in", "cli")[0].raw).toBe("a");
161
+
162
+ expect(filterEntries(entries, "out", "browser")).toHaveLength(1);
163
+ expect(filterEntries(entries, "out", "browser")[0].raw).toBe("d");
164
+
165
+ expect(filterEntries(entries, "out", "cli")).toHaveLength(1);
166
+ expect(filterEntries(entries, "in", "browser")).toHaveLength(1);
167
+ });
168
+ });
169
+
170
+ // ─── getExpectedBrowserMessages ──────────────────────────────────────────────
171
+
172
+ describe("getExpectedBrowserMessages", () => {
173
+ it("returns raw strings of outgoing browser messages", () => {
174
+ const entries = [
175
+ makeEntry({ dir: "in", ch: "cli", raw: "cli-in" }),
176
+ makeEntry({ dir: "out", ch: "browser", raw: "browser-out-1" }),
177
+ makeEntry({ dir: "out", ch: "cli", raw: "cli-out" }),
178
+ makeEntry({ dir: "out", ch: "browser", raw: "browser-out-2" }),
179
+ ];
180
+
181
+ const result = getExpectedBrowserMessages(entries);
182
+ expect(result).toEqual(["browser-out-1", "browser-out-2"]);
183
+ });
184
+ });
185
+
186
+ // ─── getIncomingCLIMessages ──────────────────────────────────────────────────
187
+
188
+ describe("getIncomingCLIMessages", () => {
189
+ it("returns raw strings of incoming CLI messages", () => {
190
+ const entries = [
191
+ makeEntry({ dir: "in", ch: "cli", raw: "cli-in-1" }),
192
+ makeEntry({ dir: "out", ch: "browser", raw: "browser-out" }),
193
+ makeEntry({ dir: "in", ch: "cli", raw: "cli-in-2" }),
194
+ makeEntry({ dir: "in", ch: "browser", raw: "browser-in" }),
195
+ ];
196
+
197
+ const result = getIncomingCLIMessages(entries);
198
+ expect(result).toEqual(["cli-in-1", "cli-in-2"]);
199
+ });
200
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Replay utility for session recordings.
3
+ *
4
+ * Loads JSONL recording files and replays them through WsBridge or CodexAdapter
5
+ * to produce browser messages. Used in tests to validate that message processing
6
+ * produces the expected output from recorded real sessions.
7
+ */
8
+
9
+ import { readFileSync } from "node:fs";
10
+ import type { RecordingHeader, RecordingEntry } from "./recorder.js";
11
+
12
+ // ─── Types ───────────────────────────────────────────────────────────────────
13
+
14
+ export interface Recording {
15
+ header: RecordingHeader;
16
+ entries: RecordingEntry[];
17
+ }
18
+
19
+ // ─── Loading ─────────────────────────────────────────────────────────────────
20
+
21
+ /**
22
+ * Load a JSONL recording file. Returns the parsed header and all entries.
23
+ * Throws if the file is missing a valid header.
24
+ */
25
+ export function loadRecording(path: string): Recording {
26
+ const content = readFileSync(path, "utf-8");
27
+ const lines = content.split("\n").filter((l) => l.trim());
28
+
29
+ if (lines.length === 0) {
30
+ throw new Error("Recording file is empty");
31
+ }
32
+
33
+ const header = JSON.parse(lines[0]) as RecordingHeader;
34
+ if (!header._header || header.version !== 1) {
35
+ throw new Error("Invalid recording header: missing _header or version !== 1");
36
+ }
37
+
38
+ const entries: RecordingEntry[] = [];
39
+ for (let i = 1; i < lines.length; i++) {
40
+ try {
41
+ entries.push(JSON.parse(lines[i]) as RecordingEntry);
42
+ } catch {
43
+ // Skip malformed lines — recording might have been truncated
44
+ }
45
+ }
46
+
47
+ return { header, entries };
48
+ }
49
+
50
+ // ─── Replay helpers ──────────────────────────────────────────────────────────
51
+
52
+ /**
53
+ * Filter recording entries by direction and channel.
54
+ * Useful for extracting only incoming CLI messages for replay.
55
+ */
56
+ export function filterEntries(
57
+ entries: RecordingEntry[],
58
+ dir: "in" | "out",
59
+ channel: "cli" | "browser",
60
+ ): RecordingEntry[] {
61
+ return entries.filter((e) => e.dir === dir && e.ch === channel);
62
+ }
63
+
64
+ /**
65
+ * Get all outgoing browser messages from a recording.
66
+ * These represent what the server actually sent to browsers during the recorded session.
67
+ */
68
+ export function getExpectedBrowserMessages(entries: RecordingEntry[]): string[] {
69
+ return filterEntries(entries, "out", "browser").map((e) => e.raw);
70
+ }
71
+
72
+ /**
73
+ * Get all incoming CLI messages from a recording.
74
+ * These are the raw NDJSON/JSON-RPC lines received from the backend.
75
+ */
76
+ export function getIncomingCLIMessages(entries: RecordingEntry[]): string[] {
77
+ return filterEntries(entries, "in", "cli").map((e) => e.raw);
78
+ }