@someonegg/codex-hub 0.0.0-stage → 0.1.1

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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -2
  3. package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/dist/client/assets/index-CRB_Qi32.js +304 -0
  63. package/dist/client/assets/index-lmwuA3r2.css +1 -0
  64. package/dist/client/index.html +16 -0
  65. package/dist/server/server/adapters/codex/agent-identity.js +9 -0
  66. package/dist/server/server/adapters/codex/catalog-source.js +2 -0
  67. package/dist/server/server/adapters/codex/codex-session-source.js +300 -0
  68. package/dist/server/server/adapters/codex/identity-resolver.js +107 -0
  69. package/dist/server/server/adapters/codex/interaction-parser.js +90 -0
  70. package/dist/server/server/adapters/codex/internal-event-parser.js +56 -0
  71. package/dist/server/server/adapters/codex/io-errors.js +5 -0
  72. package/dist/server/server/adapters/codex/jsonl-catalog-source.js +58 -0
  73. package/dist/server/server/adapters/codex/limits.js +10 -0
  74. package/dist/server/server/adapters/codex/message-normalizer.js +121 -0
  75. package/dist/server/server/adapters/codex/path-policy.js +88 -0
  76. package/dist/server/server/adapters/codex/response-item-parser.js +100 -0
  77. package/dist/server/server/adapters/codex/rollout-decoder.js +286 -0
  78. package/dist/server/server/adapters/codex/session-index-reader.js +101 -0
  79. package/dist/server/server/adapters/codex/session-manager.js +65 -0
  80. package/dist/server/server/adapters/codex/session-normalizer.js +257 -0
  81. package/dist/server/server/adapters/codex/tool-normalizer.js +58 -0
  82. package/dist/server/server/adapters/codex/user-input-normalizer.js +123 -0
  83. package/dist/server/server/api/session-api-mapper.js +194 -0
  84. package/dist/server/server/application/session-read-service.js +109 -0
  85. package/dist/server/server/application/session-reader.js +2 -0
  86. package/dist/server/server/catalog/config.js +4 -0
  87. package/dist/server/server/catalog/session-catalog-service.js +103 -0
  88. package/dist/server/server/config.js +145 -0
  89. package/dist/server/server/create-hub.js +64 -0
  90. package/dist/server/server/domain/project-directory.js +5 -0
  91. package/dist/server/server/domain/session-domain.js +2 -0
  92. package/dist/server/server/domain/session-text.js +45 -0
  93. package/dist/server/server/http/api-route-handler.js +2 -0
  94. package/dist/server/server/http/api-router.js +63 -0
  95. package/dist/server/server/http/create-server.js +148 -0
  96. package/dist/server/server/http/query-params.js +19 -0
  97. package/dist/server/server/http/request-body.js +48 -0
  98. package/dist/server/server/http/router.js +16 -0
  99. package/dist/server/server/http/security.js +19 -0
  100. package/dist/server/server/http/session-interaction-routes.js +101 -0
  101. package/dist/server/server/http/session-management-routes.js +90 -0
  102. package/dist/server/server/http/session-path.js +13 -0
  103. package/dist/server/server/http/session-read-routes.js +198 -0
  104. package/dist/server/server/http/session-updates-routes.js +84 -0
  105. package/dist/server/server/http/terminal-preview-websocket.js +179 -0
  106. package/dist/server/server/http/upload-routes.js +74 -0
  107. package/dist/server/server/interaction/interaction-service.js +247 -0
  108. package/dist/server/server/interaction/message.js +19 -0
  109. package/dist/server/server/interaction/pane-binding-manager.js +60 -0
  110. package/dist/server/server/interaction/reconcile-session-state.js +21 -0
  111. package/dist/server/server/interaction/session-runtime.js +86 -0
  112. package/dist/server/server/interaction/terminal-preview-diff.js +71 -0
  113. package/dist/server/server/interaction/terminal-preview-service.js +194 -0
  114. package/dist/server/server/interaction/tmux-launcher.js +75 -0
  115. package/dist/server/server/interaction/tmux-preview-listener.js +101 -0
  116. package/dist/server/server/interaction/tmux-service.js +269 -0
  117. package/dist/server/server/interaction/transport.js +9 -0
  118. package/dist/server/server/main.js +41 -0
  119. package/dist/server/server/management/operation-service.js +553 -0
  120. package/dist/server/server/management/session-exit-service.js +50 -0
  121. package/dist/server/server/management/session-manager.js +59 -0
  122. package/dist/server/server/repository/catalog-snapshot-store.js +304 -0
  123. package/dist/server/server/repository/diagnostic-logger.js +70 -0
  124. package/dist/server/server/repository/opaque-cursor.js +79 -0
  125. package/dist/server/server/repository/refresh-coordinator.js +14 -0
  126. package/dist/server/server/repository/session-queries.js +151 -0
  127. package/dist/server/server/repository/session-query-criteria.js +2 -0
  128. package/dist/server/server/repository/timeline-prefix-index.js +342 -0
  129. package/dist/server/server/repository/timeline-prefix-registry.js +55 -0
  130. package/dist/server/server/security/opaque-id.js +13 -0
  131. package/dist/server/server/source/session-source.js +2 -0
  132. package/dist/server/server/updates/session-updates-service.js +147 -0
  133. package/dist/server/server/updates/update-revision.js +25 -0
  134. package/dist/server/shared/api-contract.js +2 -0
  135. package/dist/server/shared/domain.js +2 -0
  136. package/dist/server/shared/interaction.js +12 -0
  137. package/dist/server/shared/uploads.js +2 -0
  138. package/package.json +79 -4
@@ -0,0 +1,286 @@
1
+ import { createHash } from "node:crypto";
2
+ import { open } from "node:fs/promises";
3
+ import { DECODER_VERSION, MAX_JSONL_LINE_BYTES, MAX_SESSION_DIAGNOSTICS } from "./limits.js";
4
+ const PROBE_BYTES = 4 * 1024;
5
+ export class CheckpointedRolloutDecoder {
6
+ async decode(descriptor, checkpoint) {
7
+ const handle = await open(descriptor.canonicalPath, "r");
8
+ try {
9
+ const observedEof = (await handle.stat()).size;
10
+ let probeBytes = 0;
11
+ let appendCheckpoint = null;
12
+ let validatedWindows = [];
13
+ if (isCompatibleCheckpoint(checkpoint, observedEof)) {
14
+ const validation = await validateProbes(handle, checkpoint);
15
+ probeBytes += validation.bytesRead;
16
+ if (validation.matches) {
17
+ appendCheckpoint = checkpoint;
18
+ validatedWindows = validation.windows;
19
+ }
20
+ }
21
+ const start = appendCheckpoint?.committedOffset ?? 0;
22
+ const baseOrdinal = appendCheckpoint?.physicalLineCount ?? 0;
23
+ const priorDiagnostics = appendCheckpoint?.diagnostics ?? [];
24
+ const decoded = await decodeRange(handle, start, observedEof, baseOrdinal, priorDiagnostics);
25
+ const probes = createProbes(observedEof, [
26
+ ...validatedWindows,
27
+ { offset: start, contents: decoded.headBytes },
28
+ { offset: decoded.tailOffset, contents: decoded.tailBytes },
29
+ ]);
30
+ const checkpointDiagnostics = decoded.diagnostics.map(cloneDiagnostic);
31
+ const nextCheckpoint = {
32
+ decoderVersion: DECODER_VERSION,
33
+ observedEof,
34
+ committedOffset: decoded.committedOffset,
35
+ physicalLineCount: decoded.physicalLineCount,
36
+ diagnostics: checkpointDiagnostics,
37
+ headProbe: probes.head,
38
+ tailProbe: probes.tail,
39
+ };
40
+ return {
41
+ descriptor,
42
+ records: decoded.records,
43
+ diagnostics: checkpointDiagnostics.map(cloneDiagnostic),
44
+ batchDiagnostics: decoded.batchDiagnostics.map(cloneDiagnostic),
45
+ mode: appendCheckpoint === null ? "full" : "append",
46
+ checkpoint: nextCheckpoint,
47
+ telemetry: {
48
+ probeBytes,
49
+ decodeBytes: decoded.bytesRead,
50
+ totalBytes: probeBytes + decoded.bytesRead,
51
+ },
52
+ };
53
+ }
54
+ finally {
55
+ await handle.close();
56
+ }
57
+ }
58
+ }
59
+ async function decodeRange(handle, start, observedEof, baseOrdinal, priorDiagnostics) {
60
+ const records = [];
61
+ const diagnostics = priorDiagnostics.slice(0, MAX_SESSION_DIAGNOSTICS).map(cloneDiagnostic);
62
+ const batchDiagnostics = [];
63
+ const lineChunks = [];
64
+ let lineBytes = 0;
65
+ let ordinal = baseOrdinal;
66
+ let committedOffset = start;
67
+ let bytesRead = 0;
68
+ let droppingOversizedLine = false;
69
+ const headParts = [];
70
+ let headLength = 0;
71
+ let tailBytes = Buffer.alloc(0);
72
+ if (start < observedEof) {
73
+ const stream = handle.createReadStream({
74
+ autoClose: false,
75
+ start,
76
+ end: observedEof - 1,
77
+ });
78
+ for await (const rawChunk of stream) {
79
+ const chunk = rawChunk;
80
+ bytesRead += chunk.length;
81
+ if (headLength < PROBE_BYTES) {
82
+ const part = chunk.subarray(0, PROBE_BYTES - headLength);
83
+ headParts.push(part);
84
+ headLength += part.length;
85
+ }
86
+ tailBytes = tailWindow(tailBytes, chunk);
87
+ let cursor = 0;
88
+ while (cursor < chunk.length) {
89
+ const newline = chunk.indexOf(0x0a, cursor);
90
+ const end = newline < 0 ? chunk.length : newline;
91
+ if (!droppingOversizedLine && end > cursor) {
92
+ const part = chunk.subarray(cursor, end);
93
+ lineBytes += part.length;
94
+ // Keep one possible trailing CR until the newline decides whether it
95
+ // is part of a CRLF terminator. decodeLine applies the final limit
96
+ // after stripping that CR.
97
+ if (lineBytes > MAX_JSONL_LINE_BYTES + 1) {
98
+ lineChunks.length = 0;
99
+ droppingOversizedLine = true;
100
+ }
101
+ else {
102
+ lineChunks.push(part);
103
+ }
104
+ }
105
+ if (newline < 0)
106
+ break;
107
+ ordinal += 1;
108
+ committedOffset = start + bytesRead - (chunk.length - newline - 1);
109
+ if (droppingOversizedLine) {
110
+ retainDiagnostic(diagnostics, batchDiagnostics, lineTooLargeDiagnostic(ordinal));
111
+ }
112
+ else {
113
+ decodeLine(lineBuffer(lineChunks, lineBytes), ordinal, records, diagnostics, batchDiagnostics);
114
+ }
115
+ lineChunks.length = 0;
116
+ lineBytes = 0;
117
+ droppingOversizedLine = false;
118
+ cursor = newline + 1;
119
+ }
120
+ }
121
+ }
122
+ return {
123
+ records,
124
+ diagnostics,
125
+ batchDiagnostics,
126
+ committedOffset,
127
+ physicalLineCount: ordinal,
128
+ bytesRead,
129
+ headBytes: Buffer.concat(headParts, headLength),
130
+ tailBytes,
131
+ tailOffset: observedEof - tailBytes.length,
132
+ };
133
+ }
134
+ function lineBuffer(chunks, length) {
135
+ if (chunks.length === 0)
136
+ return Buffer.alloc(0);
137
+ if (chunks.length === 1)
138
+ return chunks[0];
139
+ return Buffer.concat(chunks, length);
140
+ }
141
+ function decodeLine(rawLine, ordinal, records, diagnostics, batchDiagnostics) {
142
+ const line = rawLine.at(-1) === 0x0d ? rawLine.subarray(0, -1) : rawLine;
143
+ if (line.length === 0)
144
+ return;
145
+ if (line.length > MAX_JSONL_LINE_BYTES) {
146
+ retainDiagnostic(diagnostics, batchDiagnostics, lineTooLargeDiagnostic(ordinal));
147
+ return;
148
+ }
149
+ try {
150
+ const value = JSON.parse(line.toString("utf8"));
151
+ if (!isObject(value)) {
152
+ retainDiagnostic(diagnostics, batchDiagnostics, {
153
+ code: "invalid_record",
154
+ severity: "warning",
155
+ message: "A rollout line was not a JSON object and was skipped.",
156
+ ordinal,
157
+ });
158
+ return;
159
+ }
160
+ records.push({ ordinal, value });
161
+ }
162
+ catch {
163
+ retainDiagnostic(diagnostics, batchDiagnostics, {
164
+ code: "malformed_json",
165
+ severity: "warning",
166
+ message: "A malformed rollout line was skipped.",
167
+ ordinal,
168
+ });
169
+ }
170
+ }
171
+ function lineTooLargeDiagnostic(ordinal) {
172
+ return {
173
+ code: "line_too_large",
174
+ severity: "warning",
175
+ message: "A rollout record exceeded the decode limit and was skipped.",
176
+ ordinal,
177
+ };
178
+ }
179
+ function retainDiagnostic(diagnostics, batchDiagnostics, diagnostic) {
180
+ if (diagnostics.length < MAX_SESSION_DIAGNOSTICS) {
181
+ diagnostics.push(cloneDiagnostic(diagnostic));
182
+ batchDiagnostics.push(cloneDiagnostic(diagnostic));
183
+ }
184
+ }
185
+ function isCompatibleCheckpoint(checkpoint, observedEof) {
186
+ return (checkpoint !== undefined &&
187
+ checkpoint.decoderVersion === DECODER_VERSION &&
188
+ Number.isSafeInteger(checkpoint.observedEof) &&
189
+ Number.isSafeInteger(checkpoint.committedOffset) &&
190
+ Number.isSafeInteger(checkpoint.physicalLineCount) &&
191
+ checkpoint.observedEof >= checkpoint.committedOffset &&
192
+ checkpoint.committedOffset >= 0 &&
193
+ checkpoint.physicalLineCount >= 0 &&
194
+ checkpoint.diagnostics.length <= MAX_SESSION_DIAGNOSTICS &&
195
+ validProbeGeometry(checkpoint) &&
196
+ observedEof > checkpoint.observedEof);
197
+ }
198
+ function validProbeGeometry(checkpoint) {
199
+ const headLength = Math.min(PROBE_BYTES, checkpoint.observedEof);
200
+ const tailOffset = Math.max(0, checkpoint.observedEof - PROBE_BYTES);
201
+ return (checkpoint.headProbe.offset === 0 &&
202
+ checkpoint.headProbe.length === headLength &&
203
+ checkpoint.tailProbe.offset === tailOffset &&
204
+ checkpoint.tailProbe.length === checkpoint.observedEof - tailOffset);
205
+ }
206
+ async function validateProbes(handle, checkpoint) {
207
+ const head = await readProbe(handle, checkpoint.headProbe);
208
+ const tail = await readProbe(handle, checkpoint.tailProbe);
209
+ return {
210
+ matches: head.matches && tail.matches,
211
+ bytesRead: head.bytesRead + tail.bytesRead,
212
+ windows: [
213
+ { offset: checkpoint.headProbe.offset, contents: head.contents },
214
+ { offset: checkpoint.tailProbe.offset, contents: tail.contents },
215
+ ],
216
+ };
217
+ }
218
+ function createProbes(observedEof, windows) {
219
+ const headLength = Math.min(PROBE_BYTES, observedEof);
220
+ const tailOffset = Math.max(0, observedEof - PROBE_BYTES);
221
+ return {
222
+ head: probeFromWindows(0, headLength, windows),
223
+ tail: probeFromWindows(tailOffset, observedEof - tailOffset, windows),
224
+ };
225
+ }
226
+ async function probeAt(handle, offset, length) {
227
+ const buffer = Buffer.alloc(length);
228
+ let bytesRead = 0;
229
+ if (length > 0) {
230
+ ({ bytesRead } = await handle.read(buffer, 0, length, offset));
231
+ }
232
+ const contents = buffer.subarray(0, bytesRead);
233
+ return {
234
+ probe: {
235
+ offset,
236
+ length: bytesRead,
237
+ sha256: sha256(contents),
238
+ },
239
+ bytesRead,
240
+ contents,
241
+ };
242
+ }
243
+ async function readProbe(handle, expected) {
244
+ if (!Number.isSafeInteger(expected.offset) ||
245
+ expected.offset < 0 ||
246
+ !Number.isSafeInteger(expected.length) ||
247
+ expected.length < 0)
248
+ return { matches: false, bytesRead: 0, contents: Buffer.alloc(0) };
249
+ const actual = await probeAt(handle, expected.offset, expected.length);
250
+ const contents = actual.contents;
251
+ return {
252
+ matches: actual.probe.length === expected.length && actual.probe.sha256 === expected.sha256,
253
+ bytesRead: actual.bytesRead,
254
+ contents,
255
+ };
256
+ }
257
+ function sha256(value) {
258
+ return createHash("sha256").update(value).digest("hex");
259
+ }
260
+ function tailWindow(previous, chunk) {
261
+ if (chunk.length >= PROBE_BYTES)
262
+ return chunk.subarray(chunk.length - PROBE_BYTES);
263
+ const keep = Math.min(previous.length, PROBE_BYTES - chunk.length);
264
+ return Buffer.concat([previous.subarray(previous.length - keep), chunk], keep + chunk.length);
265
+ }
266
+ function probeFromWindows(offset, length, windows) {
267
+ const contents = Buffer.alloc(length);
268
+ let cursor = offset;
269
+ while (cursor < offset + length) {
270
+ const window = windows.find(({ offset: windowOffset, contents: value }) => cursor >= windowOffset && cursor < windowOffset + value.length);
271
+ if (window === undefined) {
272
+ throw new Error("Decoder probe bytes were not retained");
273
+ }
274
+ const sourceStart = cursor - window.offset;
275
+ const copied = window.contents.copy(contents, cursor - offset, sourceStart, Math.min(window.contents.length, sourceStart + offset + length - cursor));
276
+ cursor += copied;
277
+ }
278
+ return { offset, length, sha256: sha256(contents) };
279
+ }
280
+ function cloneDiagnostic(value) {
281
+ return { ...value };
282
+ }
283
+ export function isObject(value) {
284
+ return typeof value === "object" && value !== null && !Array.isArray(value);
285
+ }
286
+ //# sourceMappingURL=rollout-decoder.js.map
@@ -0,0 +1,101 @@
1
+ import { open } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { normalizeSessionTitle } from "../../domain/session-text.js";
4
+ import { MAX_JSONL_LINE_BYTES } from "./limits.js";
5
+ export class SessionIndexReader {
6
+ #path;
7
+ #fingerprint = null;
8
+ #threadNames = new Map();
9
+ constructor(codexHome) {
10
+ this.#path = join(codexHome, "session_index.jsonl");
11
+ }
12
+ async refresh() {
13
+ let handle;
14
+ try {
15
+ handle = await open(this.#path, "r");
16
+ const info = await handle.stat();
17
+ const fingerprint = { size: info.size, mtimeMs: info.mtimeMs };
18
+ if (sameFingerprint(this.#fingerprint, fingerprint))
19
+ return;
20
+ const threadNames = info.size === 0
21
+ ? new Map()
22
+ : await readCompleteRecords(handle.createReadStream({ start: 0, end: info.size - 1, autoClose: false }));
23
+ this.#threadNames = threadNames;
24
+ this.#fingerprint = fingerprint;
25
+ }
26
+ catch {
27
+ this.#threadNames = new Map();
28
+ this.#fingerprint = null;
29
+ }
30
+ finally {
31
+ await handle?.close().catch(() => undefined);
32
+ }
33
+ }
34
+ threadName(id) {
35
+ return this.#threadNames.get(id) ?? null;
36
+ }
37
+ }
38
+ async function readCompleteRecords(stream) {
39
+ const threadNames = new Map();
40
+ let parts = [];
41
+ let bytes = 0;
42
+ let droppingOversizedLine = false;
43
+ for await (const rawChunk of stream) {
44
+ const chunk = typeof rawChunk === "string" ? Buffer.from(rawChunk) : rawChunk;
45
+ let cursor = 0;
46
+ while (cursor < chunk.length) {
47
+ const newline = chunk.indexOf(0x0a, cursor);
48
+ const end = newline === -1 ? chunk.length : newline;
49
+ const part = chunk.subarray(cursor, end);
50
+ if (!droppingOversizedLine) {
51
+ if (bytes + part.length <= MAX_JSONL_LINE_BYTES) {
52
+ if (part.length > 0)
53
+ parts.push(part);
54
+ bytes += part.length;
55
+ }
56
+ else {
57
+ parts = [];
58
+ bytes = 0;
59
+ droppingOversizedLine = true;
60
+ }
61
+ }
62
+ if (newline === -1)
63
+ break;
64
+ if (!droppingOversizedLine)
65
+ consumeLine(Buffer.concat(parts, bytes), threadNames);
66
+ parts = [];
67
+ bytes = 0;
68
+ droppingOversizedLine = false;
69
+ cursor = newline + 1;
70
+ }
71
+ }
72
+ // An unterminated tail may still be in flight, so it is deliberately ignored.
73
+ return threadNames;
74
+ }
75
+ function consumeLine(line, threadNames) {
76
+ try {
77
+ const value = JSON.parse(line.toString("utf8"));
78
+ if (!isObject(value))
79
+ return;
80
+ const id = nonEmptyString(value.id);
81
+ const threadName = nonEmptyString(value.thread_name);
82
+ if (id === null || threadName === null)
83
+ return;
84
+ const normalized = normalizeSessionTitle(threadName);
85
+ if (normalized !== null)
86
+ threadNames.set(id, normalized);
87
+ }
88
+ catch {
89
+ // session_index.jsonl is advisory; malformed records do not affect sessions.
90
+ }
91
+ }
92
+ function nonEmptyString(value) {
93
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
94
+ }
95
+ function isObject(value) {
96
+ return typeof value === "object" && value !== null && !Array.isArray(value);
97
+ }
98
+ function sameFingerprint(left, right) {
99
+ return left !== null && left.size === right.size && left.mtimeMs === right.mtimeMs;
100
+ }
101
+ //# sourceMappingURL=session-index-reader.js.map
@@ -0,0 +1,65 @@
1
+ import { execFile } from "node:child_process";
2
+ import { realpath, stat } from "node:fs/promises";
3
+ import { promisify } from "node:util";
4
+ import { validProjectDirectory } from "../../domain/project-directory.js";
5
+ import { TmuxLauncher } from "../../interaction/tmux-launcher.js";
6
+ import { codexBindingCommand } from "./interaction-parser.js";
7
+ const execute = promisify(execFile);
8
+ export class CodexSessionManager {
9
+ home;
10
+ tmux;
11
+ constructor(home, tmux = new TmuxLauncher()) {
12
+ this.home = home;
13
+ this.tmux = tmux;
14
+ }
15
+ async resolveDirectory(input) {
16
+ if (!validProjectDirectory(input))
17
+ throw new Error("Directory must be an absolute local path");
18
+ const directory = await realpath(input);
19
+ if (!(await stat(directory)).isDirectory())
20
+ throw Object.assign(new Error("Path is not a directory"), { code: "ENOTDIR" });
21
+ return { directory };
22
+ }
23
+ isValidNativeId(nativeId) {
24
+ return /^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i.test(nativeId);
25
+ }
26
+ launch({ operationId, directory, worktree, yolo, resumeNativeId, projectDirectory, }) {
27
+ if (resumeNativeId !== undefined && !this.isValidNativeId(resumeNativeId))
28
+ throw new Error("Invalid resume identity");
29
+ return this.tmux.launch(operationId, directory, [
30
+ "env",
31
+ `CODEX_HOME=${this.home}`,
32
+ "codex",
33
+ ...(resumeNativeId ? ["resume", resumeNativeId] : []),
34
+ "--no-daemon",
35
+ "--no-alt-screen",
36
+ ...(worktree ? ["--worktree"] : []),
37
+ ...(yolo ? ["--yolo"] : []),
38
+ codexBindingCommand(operationId, projectDirectory),
39
+ ]);
40
+ }
41
+ exitSteps() {
42
+ return [
43
+ { keys: ["C-c"], delayMs: 0 },
44
+ { keys: ["C-c"], delayMs: 1_000 },
45
+ { keys: ["C-c"], delayMs: 1_000 },
46
+ ];
47
+ }
48
+ async archive(nativeId) {
49
+ await this.#command(["archive", nativeId]);
50
+ }
51
+ async delete(nativeId) {
52
+ await this.#command(["delete", nativeId, "--force"]);
53
+ }
54
+ async #command(args) {
55
+ if (!this.isValidNativeId(args[1]))
56
+ throw new Error("Session has no valid native UUID");
57
+ await execute("codex", args, {
58
+ env: { ...process.env, CODEX_HOME: this.home },
59
+ timeout: 25_000,
60
+ maxBuffer: 64 * 1024,
61
+ });
62
+ }
63
+ }
64
+ export const createCodexSessionManager = (home, socket) => new CodexSessionManager(home, new TmuxLauncher(socket));
65
+ //# sourceMappingURL=session-manager.js.map