lark-remote 0.1.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 (201) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +162 -0
  4. package/README.md +161 -0
  5. package/dist/binder.d.ts +57 -0
  6. package/dist/binder.js +105 -0
  7. package/dist/bridge/index.d.ts +338 -0
  8. package/dist/bridge/index.js +1281 -0
  9. package/dist/bridge/queue-manager.d.ts +277 -0
  10. package/dist/bridge/queue-manager.js +729 -0
  11. package/dist/card/bash-card-session.d.ts +78 -0
  12. package/dist/card/bash-card-session.js +174 -0
  13. package/dist/card/bash-renderer.d.ts +24 -0
  14. package/dist/card/bash-renderer.js +187 -0
  15. package/dist/card/card-budget.d.ts +31 -0
  16. package/dist/card/card-budget.js +280 -0
  17. package/dist/card/card-session.d.ts +83 -0
  18. package/dist/card/card-session.js +185 -0
  19. package/dist/card/card-shared.d.ts +28 -0
  20. package/dist/card/card-shared.js +99 -0
  21. package/dist/card/collapsible.d.ts +26 -0
  22. package/dist/card/collapsible.js +87 -0
  23. package/dist/card/run-card-session.d.ts +78 -0
  24. package/dist/card/run-card-session.js +165 -0
  25. package/dist/card/run-renderer.d.ts +66 -0
  26. package/dist/card/run-renderer.js +841 -0
  27. package/dist/card/run-state.d.ts +103 -0
  28. package/dist/card/run-state.js +313 -0
  29. package/dist/card/text-truncate.d.ts +56 -0
  30. package/dist/card/text-truncate.js +184 -0
  31. package/dist/card/time.d.ts +1 -0
  32. package/dist/card/time.js +14 -0
  33. package/dist/card/tool-render.d.ts +25 -0
  34. package/dist/card/tool-render.js +176 -0
  35. package/dist/cli.d.ts +28 -0
  36. package/dist/cli.js +89 -0
  37. package/dist/common/guards.d.ts +37 -0
  38. package/dist/common/guards.js +65 -0
  39. package/dist/config/codex-config.d.ts +113 -0
  40. package/dist/config/codex-config.js +428 -0
  41. package/dist/config/dir.d.ts +17 -0
  42. package/dist/config/dir.js +81 -0
  43. package/dist/config/index.d.ts +219 -0
  44. package/dist/config/index.js +388 -0
  45. package/dist/config/kimi-config.d.ts +33 -0
  46. package/dist/config/kimi-config.js +111 -0
  47. package/dist/config/opencode-config.d.ts +20 -0
  48. package/dist/config/opencode-config.js +124 -0
  49. package/dist/config/pi-config.d.ts +49 -0
  50. package/dist/config/pi-config.js +151 -0
  51. package/dist/config/wizard.d.ts +12 -0
  52. package/dist/config/wizard.js +84 -0
  53. package/dist/connector/file-limits.d.ts +7 -0
  54. package/dist/connector/file-limits.js +8 -0
  55. package/dist/connector/index.d.ts +86 -0
  56. package/dist/connector/index.js +340 -0
  57. package/dist/error-classification.d.ts +15 -0
  58. package/dist/error-classification.js +24 -0
  59. package/dist/index.d.ts +1 -0
  60. package/dist/index.js +580 -0
  61. package/dist/instance-lock.d.ts +30 -0
  62. package/dist/instance-lock.js +184 -0
  63. package/dist/logger/index.d.ts +57 -0
  64. package/dist/logger/index.js +178 -0
  65. package/dist/order/index.d.ts +27 -0
  66. package/dist/order/index.js +90 -0
  67. package/dist/persistence/atomic-write.d.ts +10 -0
  68. package/dist/persistence/atomic-write.js +65 -0
  69. package/dist/persistence/load-json-file.d.ts +9 -0
  70. package/dist/persistence/load-json-file.js +23 -0
  71. package/dist/restart.d.ts +24 -0
  72. package/dist/restart.js +73 -0
  73. package/dist/router/card-helpers.d.ts +28 -0
  74. package/dist/router/card-helpers.js +65 -0
  75. package/dist/router/config/claude.d.ts +14 -0
  76. package/dist/router/config/claude.js +72 -0
  77. package/dist/router/config/codex.d.ts +22 -0
  78. package/dist/router/config/codex.js +100 -0
  79. package/dist/router/config/common/render.d.ts +16 -0
  80. package/dist/router/config/common/render.js +158 -0
  81. package/dist/router/config/index.d.ts +18 -0
  82. package/dist/router/config/index.js +38 -0
  83. package/dist/router/config/kimi.d.ts +21 -0
  84. package/dist/router/config/kimi.js +63 -0
  85. package/dist/router/config/opencode.d.ts +12 -0
  86. package/dist/router/config/opencode.js +44 -0
  87. package/dist/router/config/pi.d.ts +12 -0
  88. package/dist/router/config/pi.js +50 -0
  89. package/dist/router/config/types.d.ts +43 -0
  90. package/dist/router/config/types.js +8 -0
  91. package/dist/router/index.d.ts +318 -0
  92. package/dist/router/index.js +2715 -0
  93. package/dist/router/order-exec-dispatch.d.ts +30 -0
  94. package/dist/router/order-exec-dispatch.js +48 -0
  95. package/dist/router/utils.d.ts +44 -0
  96. package/dist/router/utils.js +116 -0
  97. package/dist/runner/bash/index.d.ts +4 -0
  98. package/dist/runner/bash/index.js +5 -0
  99. package/dist/runner/bash/runner.d.ts +55 -0
  100. package/dist/runner/bash/runner.js +213 -0
  101. package/dist/runner/claude/index.d.ts +4 -0
  102. package/dist/runner/claude/index.js +5 -0
  103. package/dist/runner/claude/runner.d.ts +29 -0
  104. package/dist/runner/claude/runner.js +83 -0
  105. package/dist/runner/codex/argv.d.ts +33 -0
  106. package/dist/runner/codex/argv.js +40 -0
  107. package/dist/runner/codex/index.d.ts +1 -0
  108. package/dist/runner/codex/index.js +2 -0
  109. package/dist/runner/codex/jsonl.d.ts +37 -0
  110. package/dist/runner/codex/jsonl.js +221 -0
  111. package/dist/runner/codex/runner.d.ts +65 -0
  112. package/dist/runner/codex/runner.js +124 -0
  113. package/dist/runner/common/exec-translator.d.ts +73 -0
  114. package/dist/runner/common/exec-translator.js +105 -0
  115. package/dist/runner/common/jsonl-stream.d.ts +39 -0
  116. package/dist/runner/common/jsonl-stream.js +203 -0
  117. package/dist/runner/common/process-stopper.d.ts +28 -0
  118. package/dist/runner/common/process-stopper.js +79 -0
  119. package/dist/runner/common/runner-utils.d.ts +21 -0
  120. package/dist/runner/common/runner-utils.js +65 -0
  121. package/dist/runner/common/spawn-heartbeat.d.ts +33 -0
  122. package/dist/runner/common/spawn-heartbeat.js +51 -0
  123. package/dist/runner/common/spawning-runner.d.ts +321 -0
  124. package/dist/runner/common/spawning-runner.js +634 -0
  125. package/dist/runner/common/usage.d.ts +31 -0
  126. package/dist/runner/common/usage.js +29 -0
  127. package/dist/runner/index.d.ts +19 -0
  128. package/dist/runner/index.js +24 -0
  129. package/dist/runner/kimi/index.d.ts +1 -0
  130. package/dist/runner/kimi/index.js +2 -0
  131. package/dist/runner/kimi/jsonl.d.ts +28 -0
  132. package/dist/runner/kimi/jsonl.js +143 -0
  133. package/dist/runner/kimi/runner.d.ts +50 -0
  134. package/dist/runner/kimi/runner.js +105 -0
  135. package/dist/runner/opencode/argv.d.ts +32 -0
  136. package/dist/runner/opencode/argv.js +35 -0
  137. package/dist/runner/opencode/index.d.ts +1 -0
  138. package/dist/runner/opencode/index.js +2 -0
  139. package/dist/runner/opencode/jsonl.d.ts +68 -0
  140. package/dist/runner/opencode/jsonl.js +295 -0
  141. package/dist/runner/opencode/runner.d.ts +63 -0
  142. package/dist/runner/opencode/runner.js +93 -0
  143. package/dist/runner/pi/index.d.ts +1 -0
  144. package/dist/runner/pi/index.js +2 -0
  145. package/dist/runner/pi/jsonl.d.ts +128 -0
  146. package/dist/runner/pi/jsonl.js +249 -0
  147. package/dist/runner/pi/runner.d.ts +37 -0
  148. package/dist/runner/pi/runner.js +76 -0
  149. package/dist/runner/registry.d.ts +54 -0
  150. package/dist/runner/registry.js +75 -0
  151. package/dist/runner/resolve-agent-choices.d.ts +7 -0
  152. package/dist/runner/resolve-agent-choices.js +76 -0
  153. package/dist/runner/sync-agent-choices.d.ts +6 -0
  154. package/dist/runner/sync-agent-choices.js +73 -0
  155. package/dist/runner/types.d.ts +250 -0
  156. package/dist/runner/types.js +8 -0
  157. package/dist/session/claude/index.d.ts +1 -0
  158. package/dist/session/claude/index.js +2 -0
  159. package/dist/session/claude/session-reader.d.ts +34 -0
  160. package/dist/session/claude/session-reader.js +46 -0
  161. package/dist/session/claude/sessions.d.ts +53 -0
  162. package/dist/session/claude/sessions.js +528 -0
  163. package/dist/session/codex/index.d.ts +1 -0
  164. package/dist/session/codex/index.js +2 -0
  165. package/dist/session/codex/rollout-reader.d.ts +88 -0
  166. package/dist/session/codex/rollout-reader.js +473 -0
  167. package/dist/session/codex/sessions.d.ts +21 -0
  168. package/dist/session/codex/sessions.js +40 -0
  169. package/dist/session/common/catch-up.d.ts +12 -0
  170. package/dist/session/common/catch-up.js +31 -0
  171. package/dist/session/common/constants.d.ts +11 -0
  172. package/dist/session/common/constants.js +12 -0
  173. package/dist/session/common/content-blocks.d.ts +37 -0
  174. package/dist/session/common/content-blocks.js +68 -0
  175. package/dist/session/common/jsonl.d.ts +77 -0
  176. package/dist/session/common/jsonl.js +351 -0
  177. package/dist/session/common/usage-accumulator.d.ts +58 -0
  178. package/dist/session/common/usage-accumulator.js +72 -0
  179. package/dist/session/index.d.ts +7 -0
  180. package/dist/session/index.js +11 -0
  181. package/dist/session/kimi/index.d.ts +1 -0
  182. package/dist/session/kimi/index.js +2 -0
  183. package/dist/session/kimi/sessions.d.ts +81 -0
  184. package/dist/session/kimi/sessions.js +527 -0
  185. package/dist/session/opencode/index.d.ts +1 -0
  186. package/dist/session/opencode/index.js +2 -0
  187. package/dist/session/opencode/sessions.d.ts +81 -0
  188. package/dist/session/opencode/sessions.js +388 -0
  189. package/dist/session/pi/index.d.ts +1 -0
  190. package/dist/session/pi/index.js +2 -0
  191. package/dist/session/pi/sessions.d.ts +27 -0
  192. package/dist/session/pi/sessions.js +399 -0
  193. package/dist/session/registry.d.ts +12 -0
  194. package/dist/session/registry.js +20 -0
  195. package/dist/session/session-store.d.ts +86 -0
  196. package/dist/session/session-store.js +450 -0
  197. package/dist/startup-contact.d.ts +18 -0
  198. package/dist/startup-contact.js +51 -0
  199. package/dist/workspace/index.d.ts +19 -0
  200. package/dist/workspace/index.js +53 -0
  201. package/package.json +93 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Parameterized content-block extraction shared across session readers.
3
+ *
4
+ * Different agents (Claude, pi, …) use different field names for tool_use /
5
+ * tool_result blocks. The {@link ContentBlockMapping} type captures those
6
+ * differences so a single implementation can serve all readers.
7
+ */
8
+ /**
9
+ * Extract content blocks from a message content array.
10
+ *
11
+ * Handles text, thinking, tool_use, and tool_result blocks. Blank text and
12
+ * whitespace-only tool_result content are skipped. Tool input is serialized
13
+ * to JSON and truncated at 200 characters.
14
+ *
15
+ * @param content The raw `content` value from a message (array expected;
16
+ * non-array values return an empty array).
17
+ * @param mapping Agent-specific field-name mapping.
18
+ * @returns Array of {@link ContentBlock} objects.
19
+ */
20
+ export function extractContentBlocks(content, mapping) {
21
+ const blocks = [];
22
+ if (!Array.isArray(content))
23
+ return blocks;
24
+ for (const part of content) {
25
+ if (!part || typeof part !== 'object')
26
+ continue;
27
+ const p = part;
28
+ if (p.type === 'text' && typeof p.text === 'string' && p.text.trim()) {
29
+ blocks.push({ type: 'text', content: p.text.trim() });
30
+ }
31
+ if (p.type === 'thinking' && typeof p.thinking === 'string' && p.thinking.trim()) {
32
+ blocks.push({ type: 'thinking', content: p.thinking.trim() });
33
+ }
34
+ // tool_use block (parameterized type name + input field)
35
+ if (p.type === mapping.toolUseType && typeof p.name === 'string') {
36
+ const rawInput = p[mapping.toolInputField] ?? {};
37
+ const inputStr = JSON.stringify(rawInput, null, 2);
38
+ const label = `🔧 ${p.name}`;
39
+ let body;
40
+ if (inputStr.length > 200) {
41
+ body = inputStr.replace(/\n/g, ' ').slice(0, 197) + '...';
42
+ }
43
+ else {
44
+ body = inputStr;
45
+ }
46
+ blocks.push({ type: 'tool_use', content: `${label}: ${body}` });
47
+ }
48
+ // tool_result block (parameterized type name + error field)
49
+ if (p.type === mapping.toolResultType) {
50
+ const toolContent = typeof p.content === 'string'
51
+ ? p.content
52
+ : Array.isArray(p.content)
53
+ ? p.content
54
+ .map((c) => typeof c === 'object' && c && 'text' in c
55
+ ? c.text
56
+ : String(c))
57
+ .join('')
58
+ : String(p.content ?? '');
59
+ if (toolContent.trim()) {
60
+ const isError = p[mapping.toolErrorField] === true;
61
+ const label = isError ? '🔴 tool_result' : '🟢 tool_result';
62
+ blocks.push({ type: 'tool_result', content: `${label}: ${toolContent.trim()}` });
63
+ }
64
+ }
65
+ }
66
+ return blocks;
67
+ }
68
+ //# sourceMappingURL=content-blocks.js.map
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Find the first line in a JSONL file that matches a predicate using streaming reads.
3
+ * Stops reading as soon as a match is found.
4
+ *
5
+ * @param filePath Absolute path to a `.jsonl` file.
6
+ * @param pred Predicate applied to each trimmed non-empty line.
7
+ * @returns The matching trimmed line, or null if no match / file not found / read error.
8
+ */
9
+ export declare function findJsonlLine(filePath: string, pred: (line: string) => boolean): string | null;
10
+ /**
11
+ * Read the last non-empty line from a JSONL file using streaming reads.
12
+ *
13
+ * @param filePath Absolute path to a `.jsonl` file.
14
+ * @returns The last trimmed non-empty line, or null if the file is empty / not found.
15
+ */
16
+ export declare function readLastJsonlLine(filePath: string): string | null;
17
+ /**
18
+ * Read the last N non-empty lines from a JSONL file using streaming reads.
19
+ *
20
+ * Unlike `readJsonlLines` (which materializes the entire file into a `string[]`),
21
+ * this only retains the last `n` lines — memory is O(n), not O(whole file). The
22
+ * file is still read front-to-back in chunks (no reverse seek), but earlier
23
+ * lines are discarded as soon as the buffer exceeds `n`, so a multi-MB file
24
+ * costs only ~`n` line strings of resident memory.
25
+ *
26
+ * Used by tail-only read paths (P2-5) such as `isSessionActive`, which only
27
+ * need to inspect the file's tail (e.g. the last ~20 loop events) but were
28
+ * previously full-slurping the whole wire log.
29
+ *
30
+ * @param filePath Absolute path to a `.jsonl` file.
31
+ * @param n Maximum number of trailing non-empty lines to retain.
32
+ * @returns Array of the last ≤`n` raw line strings (empty lines excluded),
33
+ * in file order. Empty array if the file is empty / not found.
34
+ */
35
+ export declare function readLastNJsonlLines(filePath: string, n: number): string[];
36
+ /**
37
+ * Stream-scan a JSONL file once, invoking `onLine` for each non-empty line
38
+ * with the line's raw string and its **starting byte offset** within the file.
39
+ *
40
+ * Unlike `readJsonlLines` this retains NOTHING — memory is O(chunk), not
41
+ * O(whole file). The callback receives the byte offset so callers can record
42
+ * where a line of interest (e.g. the last user message) begins, then re-read
43
+ * only the tail from that offset via `readJsonlLinesFromOffset` (P2-5
44
+ * byte-offset two-pass: full-file scalar scan without materializing a
45
+ * `string[]`, then tail-only re-parse for events).
46
+ *
47
+ * @param filePath Absolute path to a `.jsonl` file.
48
+ * @param onLine Called for each non-empty line with `(line, offset)` where
49
+ * `offset` is the byte offset of the line's first character.
50
+ * @returns `true` if the file was opened and scanned, `false` on open error.
51
+ */
52
+ export declare function scanJsonlLines(filePath: string, onLine: (line: string, offset: number) => void): boolean;
53
+ /**
54
+ * Read all non-empty lines starting from a byte `offset` in a JSONL file.
55
+ *
56
+ * Companion to `scanJsonlLines` for the P2-5 byte-offset two-pass pattern:
57
+ * pass 1 records the offset where the tail begins (e.g. the byte right after
58
+ * the last user message); pass 2 seeks there and reads/parses only the tail,
59
+ * so raw line-string memory is O(tail) instead of O(whole file).
60
+ *
61
+ * @param filePath Absolute path to a `.jsonl` file.
62
+ * @param offset Byte offset to seek to before reading. **Must fall on a line
63
+ * boundary** (e.g. 0, or the start of the line after the last
64
+ * user message as recorded by `scanJsonlLines`); a mid-line
65
+ * offset will include a leading partial line.
66
+ * @returns Array of raw line strings from `offset` to EOF (empty lines
67
+ * excluded). Empty array on open/seek error or `offset` past EOF.
68
+ */
69
+ export declare function readJsonlLinesFromOffset(filePath: string, offset: number): string[];
70
+ /**
71
+ * Read all non-empty lines from a JSONL file using streaming reads.
72
+ *
73
+ * @param filePath Absolute path to a `.jsonl` file.
74
+ * @param chunkSize Buffer size for each `fs.readSync` call (default 65536).
75
+ * @returns Array of raw line strings (empty lines excluded).
76
+ */
77
+ export declare function readJsonlLines(filePath: string, chunkSize?: number): string[];
@@ -0,0 +1,351 @@
1
+ import fs from 'node:fs';
2
+ /**
3
+ * Find the first line in a JSONL file that matches a predicate using streaming reads.
4
+ * Stops reading as soon as a match is found.
5
+ *
6
+ * @param filePath Absolute path to a `.jsonl` file.
7
+ * @param pred Predicate applied to each trimmed non-empty line.
8
+ * @returns The matching trimmed line, or null if no match / file not found / read error.
9
+ */
10
+ export function findJsonlLine(filePath, pred) {
11
+ let fd;
12
+ try {
13
+ fd = fs.openSync(filePath, 'r');
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ const chunk = Buffer.alloc(65536);
19
+ let buf = Buffer.alloc(0);
20
+ try {
21
+ for (;;) {
22
+ const n = fs.readSync(fd, chunk, 0, chunk.length, null);
23
+ if (n === 0)
24
+ break;
25
+ buf = Buffer.concat([buf, chunk.subarray(0, n)]);
26
+ // Find the last newline so we only decode complete lines. UTF-8 multi-byte
27
+ // characters never contain the 0x0A byte, so splitting on `\n` at the raw
28
+ // byte level is always safe and never truncates a multi-byte sequence.
29
+ let nlIdx;
30
+ while ((nlIdx = buf.lastIndexOf(0x0a)) !== -1) {
31
+ // Decode everything before the last `\n` and split into lines.
32
+ const text = buf.subarray(0, nlIdx).toString('utf-8');
33
+ // Keep the tail after the last `\n` as the new buffer.
34
+ buf = buf.subarray(nlIdx + 1);
35
+ const parts = text.split('\n');
36
+ // All elements except the last are guaranteed complete (they had their
37
+ // own trailing `\n`). The last element is whatever followed the final
38
+ // `\n` in `text`; it may be empty or a partial line — re-emit it as part
39
+ // of the next buffer by prepending it back. But since we sliced on the
40
+ // *last* newline in buf, everything before it is fully complete lines
41
+ // joined by `\n`; the trailing fragment is already excluded by keeping
42
+ // only buf.subarray(nlIdx+1). So `parts` here is the complete lines,
43
+ // and `parts[parts.length-1]` is the final segment before the last `\n`
44
+ // — which IS complete (the last `\n` we split on was its terminator).
45
+ for (const part of parts) {
46
+ const trimmed = part.trim();
47
+ if (trimmed && pred(trimmed)) {
48
+ return trimmed;
49
+ }
50
+ }
51
+ break;
52
+ }
53
+ }
54
+ // Handle last remaining line (file without trailing newline).
55
+ const trimmed = buf.toString('utf-8').trim();
56
+ if (trimmed && pred(trimmed)) {
57
+ return trimmed;
58
+ }
59
+ }
60
+ finally {
61
+ fs.closeSync(fd);
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * Read the last non-empty line from a JSONL file using streaming reads.
67
+ *
68
+ * @param filePath Absolute path to a `.jsonl` file.
69
+ * @returns The last trimmed non-empty line, or null if the file is empty / not found.
70
+ */
71
+ export function readLastJsonlLine(filePath) {
72
+ let fd;
73
+ try {
74
+ fd = fs.openSync(filePath, 'r');
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ const chunk = Buffer.alloc(65536);
80
+ let buf = Buffer.alloc(0);
81
+ let last = null;
82
+ try {
83
+ for (;;) {
84
+ const n = fs.readSync(fd, chunk, 0, chunk.length, null);
85
+ if (n === 0)
86
+ break;
87
+ buf = Buffer.concat([buf, chunk.subarray(0, n)]);
88
+ let nlIdx;
89
+ while ((nlIdx = buf.lastIndexOf(0x0a)) !== -1) {
90
+ const text = buf.subarray(0, nlIdx).toString('utf-8');
91
+ buf = buf.subarray(nlIdx + 1);
92
+ const parts = text.split('\n');
93
+ for (const part of parts) {
94
+ const trimmed = part.trim();
95
+ if (trimmed) {
96
+ last = trimmed;
97
+ }
98
+ }
99
+ break;
100
+ }
101
+ }
102
+ const trimmed = buf.toString('utf-8').trim();
103
+ if (trimmed) {
104
+ last = trimmed;
105
+ }
106
+ }
107
+ finally {
108
+ fs.closeSync(fd);
109
+ }
110
+ return last;
111
+ }
112
+ /**
113
+ * Read the last N non-empty lines from a JSONL file using streaming reads.
114
+ *
115
+ * Unlike `readJsonlLines` (which materializes the entire file into a `string[]`),
116
+ * this only retains the last `n` lines — memory is O(n), not O(whole file). The
117
+ * file is still read front-to-back in chunks (no reverse seek), but earlier
118
+ * lines are discarded as soon as the buffer exceeds `n`, so a multi-MB file
119
+ * costs only ~`n` line strings of resident memory.
120
+ *
121
+ * Used by tail-only read paths (P2-5) such as `isSessionActive`, which only
122
+ * need to inspect the file's tail (e.g. the last ~20 loop events) but were
123
+ * previously full-slurping the whole wire log.
124
+ *
125
+ * @param filePath Absolute path to a `.jsonl` file.
126
+ * @param n Maximum number of trailing non-empty lines to retain.
127
+ * @returns Array of the last ≤`n` raw line strings (empty lines excluded),
128
+ * in file order. Empty array if the file is empty / not found.
129
+ */
130
+ export function readLastNJsonlLines(filePath, n) {
131
+ if (n <= 0)
132
+ return [];
133
+ let fd;
134
+ try {
135
+ fd = fs.openSync(filePath, 'r');
136
+ }
137
+ catch {
138
+ return [];
139
+ }
140
+ const chunk = Buffer.alloc(65536);
141
+ let buf = Buffer.alloc(0);
142
+ const lines = [];
143
+ try {
144
+ for (;;) {
145
+ const nread = fs.readSync(fd, chunk, 0, chunk.length, null);
146
+ if (nread === 0)
147
+ break;
148
+ buf = Buffer.concat([buf, chunk.subarray(0, nread)]);
149
+ let nlIdx;
150
+ while ((nlIdx = buf.lastIndexOf(0x0a)) !== -1) {
151
+ const text = buf.subarray(0, nlIdx).toString('utf-8');
152
+ buf = buf.subarray(nlIdx + 1);
153
+ const parts = text.split('\n');
154
+ for (const part of parts) {
155
+ if (part.trim()) {
156
+ lines.push(part);
157
+ if (lines.length > n) {
158
+ lines.splice(0, lines.length - n);
159
+ }
160
+ }
161
+ }
162
+ break;
163
+ }
164
+ }
165
+ const tail = buf.toString('utf-8');
166
+ if (tail.trim()) {
167
+ lines.push(tail);
168
+ if (lines.length > n) {
169
+ lines.splice(0, lines.length - n);
170
+ }
171
+ }
172
+ }
173
+ finally {
174
+ fs.closeSync(fd);
175
+ }
176
+ return lines;
177
+ }
178
+ /**
179
+ * Stream-scan a JSONL file once, invoking `onLine` for each non-empty line
180
+ * with the line's raw string and its **starting byte offset** within the file.
181
+ *
182
+ * Unlike `readJsonlLines` this retains NOTHING — memory is O(chunk), not
183
+ * O(whole file). The callback receives the byte offset so callers can record
184
+ * where a line of interest (e.g. the last user message) begins, then re-read
185
+ * only the tail from that offset via `readJsonlLinesFromOffset` (P2-5
186
+ * byte-offset two-pass: full-file scalar scan without materializing a
187
+ * `string[]`, then tail-only re-parse for events).
188
+ *
189
+ * @param filePath Absolute path to a `.jsonl` file.
190
+ * @param onLine Called for each non-empty line with `(line, offset)` where
191
+ * `offset` is the byte offset of the line's first character.
192
+ * @returns `true` if the file was opened and scanned, `false` on open error.
193
+ */
194
+ export function scanJsonlLines(filePath, onLine) {
195
+ let fd;
196
+ try {
197
+ fd = fs.openSync(filePath, 'r');
198
+ }
199
+ catch {
200
+ return false;
201
+ }
202
+ const chunk = Buffer.alloc(65536);
203
+ // Raw byte buffer holding the not-yet-decoded tail (everything after the
204
+ // last consumed `\n`). Byte offsets are tracked against the real file, so
205
+ // they are immune to multi-byte UTF-8 decode artifacts.
206
+ let buf = Buffer.alloc(0);
207
+ let baseOffset = 0;
208
+ try {
209
+ for (;;) {
210
+ const nread = fs.readSync(fd, chunk, 0, chunk.length, null);
211
+ if (nread === 0)
212
+ break;
213
+ buf = Buffer.concat([buf, chunk.subarray(0, nread)]);
214
+ // Decode all complete lines (everything up to the last `\n`). The tail
215
+ // after the last `\n` stays in `buf` for the next iteration.
216
+ let nlIdx;
217
+ while ((nlIdx = buf.lastIndexOf(0x0a)) !== -1) {
218
+ const complete = buf.subarray(0, nlIdx);
219
+ const text = complete.toString('utf-8');
220
+ buf = buf.subarray(nlIdx + 1);
221
+ const parts = text.split('\n');
222
+ let cursor = baseOffset;
223
+ for (const part of parts) {
224
+ if (part.trim()) {
225
+ onLine(part, cursor);
226
+ }
227
+ // advance past this line + its '\n' (1 byte)
228
+ cursor += Buffer.byteLength(part, 'utf-8') + 1;
229
+ }
230
+ baseOffset += complete.length + 1;
231
+ break;
232
+ }
233
+ }
234
+ const tail = buf.toString('utf-8');
235
+ if (tail.trim()) {
236
+ onLine(tail, baseOffset);
237
+ }
238
+ }
239
+ finally {
240
+ fs.closeSync(fd);
241
+ }
242
+ return true;
243
+ }
244
+ /**
245
+ * Read all non-empty lines starting from a byte `offset` in a JSONL file.
246
+ *
247
+ * Companion to `scanJsonlLines` for the P2-5 byte-offset two-pass pattern:
248
+ * pass 1 records the offset where the tail begins (e.g. the byte right after
249
+ * the last user message); pass 2 seeks there and reads/parses only the tail,
250
+ * so raw line-string memory is O(tail) instead of O(whole file).
251
+ *
252
+ * @param filePath Absolute path to a `.jsonl` file.
253
+ * @param offset Byte offset to seek to before reading. **Must fall on a line
254
+ * boundary** (e.g. 0, or the start of the line after the last
255
+ * user message as recorded by `scanJsonlLines`); a mid-line
256
+ * offset will include a leading partial line.
257
+ * @returns Array of raw line strings from `offset` to EOF (empty lines
258
+ * excluded). Empty array on open/seek error or `offset` past EOF.
259
+ */
260
+ export function readJsonlLinesFromOffset(filePath, offset) {
261
+ if (offset < 0)
262
+ return [];
263
+ let fd;
264
+ try {
265
+ fd = fs.openSync(filePath, 'r');
266
+ }
267
+ catch {
268
+ return [];
269
+ }
270
+ const chunk = Buffer.alloc(65536);
271
+ let buf = Buffer.alloc(0);
272
+ const lines = [];
273
+ try {
274
+ let position = offset;
275
+ for (;;) {
276
+ const nread = fs.readSync(fd, chunk, 0, chunk.length, position);
277
+ if (nread === 0)
278
+ break;
279
+ buf = Buffer.concat([buf, chunk.subarray(0, nread)]);
280
+ let nlIdx;
281
+ while ((nlIdx = buf.lastIndexOf(0x0a)) !== -1) {
282
+ const text = buf.subarray(0, nlIdx).toString('utf-8');
283
+ buf = buf.subarray(nlIdx + 1);
284
+ const parts = text.split('\n');
285
+ for (const part of parts) {
286
+ if (part.trim()) {
287
+ lines.push(part);
288
+ }
289
+ }
290
+ break;
291
+ }
292
+ position += nread;
293
+ }
294
+ const tail = buf.toString('utf-8');
295
+ if (tail.trim()) {
296
+ lines.push(tail);
297
+ }
298
+ }
299
+ finally {
300
+ fs.closeSync(fd);
301
+ }
302
+ return lines;
303
+ }
304
+ /**
305
+ * Read all non-empty lines from a JSONL file using streaming reads.
306
+ *
307
+ * @param filePath Absolute path to a `.jsonl` file.
308
+ * @param chunkSize Buffer size for each `fs.readSync` call (default 65536).
309
+ * @returns Array of raw line strings (empty lines excluded).
310
+ */
311
+ export function readJsonlLines(filePath, chunkSize = 65536) {
312
+ let fd;
313
+ try {
314
+ fd = fs.openSync(filePath, 'r');
315
+ }
316
+ catch {
317
+ return [];
318
+ }
319
+ const chunk = Buffer.alloc(chunkSize);
320
+ let buf = Buffer.alloc(0);
321
+ const lines = [];
322
+ try {
323
+ for (;;) {
324
+ const n = fs.readSync(fd, chunk, 0, chunk.length, null);
325
+ if (n === 0)
326
+ break;
327
+ buf = Buffer.concat([buf, chunk.subarray(0, n)]);
328
+ let nlIdx;
329
+ while ((nlIdx = buf.lastIndexOf(0x0a)) !== -1) {
330
+ const text = buf.subarray(0, nlIdx).toString('utf-8');
331
+ buf = buf.subarray(nlIdx + 1);
332
+ const parts = text.split('\n');
333
+ for (const part of parts) {
334
+ if (part.trim()) {
335
+ lines.push(part);
336
+ }
337
+ }
338
+ break;
339
+ }
340
+ }
341
+ const tail = buf.toString('utf-8');
342
+ if (tail.trim()) {
343
+ lines.push(tail);
344
+ }
345
+ }
346
+ finally {
347
+ fs.closeSync(fd);
348
+ }
349
+ return lines;
350
+ }
351
+ //# sourceMappingURL=jsonl.js.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Token usage accumulator for session readers.
3
+ *
4
+ * Aggregates per-turn token component records into cumulative totals
5
+ * and exposes the last record for "current turn" semantics.
6
+ * All public getters return deep copies to prevent accidental mutation.
7
+ */
8
+ /** Components of a single usage record. Optional fields default to 0. */
9
+ interface TokenComponents {
10
+ input: number;
11
+ output: number;
12
+ cacheRead: number;
13
+ cacheCreation: number;
14
+ reasoning?: number;
15
+ /** Agent-declared total; when absent, total is the sum of the four required fields. */
16
+ total?: number;
17
+ }
18
+ export declare class UsageAccumulator {
19
+ private readonly _totals;
20
+ private _count;
21
+ private _last;
22
+ private _compactCount;
23
+ /** Number of records added. */
24
+ get count(): number;
25
+ /** Number of compact/compaction events observed. */
26
+ get compactCount(): number;
27
+ /** Cumulative totals across all added records (deep copy). */
28
+ get totals(): {
29
+ input: number;
30
+ output: number;
31
+ cacheRead: number;
32
+ cacheCreation: number;
33
+ reasoning: number;
34
+ total: number;
35
+ };
36
+ /** Last added record (deep copy), or null if none. */
37
+ get last(): {
38
+ input: number;
39
+ output: number;
40
+ cacheRead: number;
41
+ cacheCreation: number;
42
+ reasoning: number;
43
+ total: number;
44
+ } | null;
45
+ /**
46
+ * Add a usage record. Optional fields default to 0.
47
+ * The `total` field is **not** summed — the caller decides how to
48
+ * derive it (cumulative sum of parts vs. last-declared vs. max).
49
+ * Here we simply store the per-record total (overwriting previous).
50
+ */
51
+ add(record: TokenComponents): void;
52
+ /**
53
+ * Record a compact/compaction event.
54
+ * Call once per compact_boundary / compaction line encountered.
55
+ */
56
+ bumpCompact(): void;
57
+ }
58
+ export {};
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Token usage accumulator for session readers.
3
+ *
4
+ * Aggregates per-turn token component records into cumulative totals
5
+ * and exposes the last record for "current turn" semantics.
6
+ * All public getters return deep copies to prevent accidental mutation.
7
+ */
8
+ function emptyTotals() {
9
+ return { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0, total: 0 };
10
+ }
11
+ function cloneTotals(t) {
12
+ return {
13
+ input: t.input,
14
+ output: t.output,
15
+ cacheRead: t.cacheRead,
16
+ cacheCreation: t.cacheCreation,
17
+ reasoning: t.reasoning,
18
+ total: t.total,
19
+ };
20
+ }
21
+ export class UsageAccumulator {
22
+ _totals = emptyTotals();
23
+ _count = 0;
24
+ _last = null;
25
+ _compactCount = 0;
26
+ /** Number of records added. */
27
+ get count() {
28
+ return this._count;
29
+ }
30
+ /** Number of compact/compaction events observed. */
31
+ get compactCount() {
32
+ return this._compactCount;
33
+ }
34
+ /** Cumulative totals across all added records (deep copy). */
35
+ get totals() {
36
+ return cloneTotals(this._totals);
37
+ }
38
+ /** Last added record (deep copy), or null if none. */
39
+ get last() {
40
+ return this._last ? cloneTotals(this._last) : null;
41
+ }
42
+ /**
43
+ * Add a usage record. Optional fields default to 0.
44
+ * The `total` field is **not** summed — the caller decides how to
45
+ * derive it (cumulative sum of parts vs. last-declared vs. max).
46
+ * Here we simply store the per-record total (overwriting previous).
47
+ */
48
+ add(record) {
49
+ const input = record.input;
50
+ const output = record.output;
51
+ const cacheRead = record.cacheRead;
52
+ const cacheCreation = record.cacheCreation;
53
+ const reasoning = record.reasoning ?? 0;
54
+ const total = record.total ?? 0;
55
+ this._totals.input += input;
56
+ this._totals.output += output;
57
+ this._totals.cacheRead += cacheRead;
58
+ this._totals.cacheCreation += cacheCreation;
59
+ this._totals.reasoning += reasoning;
60
+ this._totals.total += total;
61
+ this._last = { input, output, cacheRead, cacheCreation, reasoning, total };
62
+ this._count++;
63
+ }
64
+ /**
65
+ * Record a compact/compaction event.
66
+ * Call once per compact_boundary / compaction line encountered.
67
+ */
68
+ bumpCompact() {
69
+ this._compactCount++;
70
+ }
71
+ }
72
+ //# sourceMappingURL=usage-accumulator.js.map
@@ -0,0 +1,7 @@
1
+ export { SessionStore } from './session-store.js';
2
+ export { SessionReaderRegistry } from './registry.js';
3
+ export { ClaudeSessionReader } from './claude/index.js';
4
+ export { CodexSessionReader } from './codex/index.js';
5
+ export { OpencodeSessionReader } from './opencode/index.js';
6
+ export { PiSessionReader } from './pi/index.js';
7
+ export { KimiSessionReader } from './kimi/index.js';
@@ -0,0 +1,11 @@
1
+ // Re-export the public session API. Only the classes below are imported
2
+ // from production code; everything else is imported directly from the
3
+ // concrete submodules (claude/, codex/, opencode/, pi/, kimi/, common/).
4
+ export { SessionStore } from './session-store.js';
5
+ export { SessionReaderRegistry } from './registry.js';
6
+ export { ClaudeSessionReader } from './claude/index.js';
7
+ export { CodexSessionReader } from './codex/index.js';
8
+ export { OpencodeSessionReader } from './opencode/index.js';
9
+ export { PiSessionReader } from './pi/index.js';
10
+ export { KimiSessionReader } from './kimi/index.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ export { KimiSessionReader } from './sessions.js';
@@ -0,0 +1,2 @@
1
+ export { KimiSessionReader } from './sessions.js';
2
+ //# sourceMappingURL=index.js.map