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.
- package/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.en.md +162 -0
- package/README.md +161 -0
- package/dist/binder.d.ts +57 -0
- package/dist/binder.js +105 -0
- package/dist/bridge/index.d.ts +338 -0
- package/dist/bridge/index.js +1281 -0
- package/dist/bridge/queue-manager.d.ts +277 -0
- package/dist/bridge/queue-manager.js +729 -0
- package/dist/card/bash-card-session.d.ts +78 -0
- package/dist/card/bash-card-session.js +174 -0
- package/dist/card/bash-renderer.d.ts +24 -0
- package/dist/card/bash-renderer.js +187 -0
- package/dist/card/card-budget.d.ts +31 -0
- package/dist/card/card-budget.js +280 -0
- package/dist/card/card-session.d.ts +83 -0
- package/dist/card/card-session.js +185 -0
- package/dist/card/card-shared.d.ts +28 -0
- package/dist/card/card-shared.js +99 -0
- package/dist/card/collapsible.d.ts +26 -0
- package/dist/card/collapsible.js +87 -0
- package/dist/card/run-card-session.d.ts +78 -0
- package/dist/card/run-card-session.js +165 -0
- package/dist/card/run-renderer.d.ts +66 -0
- package/dist/card/run-renderer.js +841 -0
- package/dist/card/run-state.d.ts +103 -0
- package/dist/card/run-state.js +313 -0
- package/dist/card/text-truncate.d.ts +56 -0
- package/dist/card/text-truncate.js +184 -0
- package/dist/card/time.d.ts +1 -0
- package/dist/card/time.js +14 -0
- package/dist/card/tool-render.d.ts +25 -0
- package/dist/card/tool-render.js +176 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.js +89 -0
- package/dist/common/guards.d.ts +37 -0
- package/dist/common/guards.js +65 -0
- package/dist/config/codex-config.d.ts +113 -0
- package/dist/config/codex-config.js +428 -0
- package/dist/config/dir.d.ts +17 -0
- package/dist/config/dir.js +81 -0
- package/dist/config/index.d.ts +219 -0
- package/dist/config/index.js +388 -0
- package/dist/config/kimi-config.d.ts +33 -0
- package/dist/config/kimi-config.js +111 -0
- package/dist/config/opencode-config.d.ts +20 -0
- package/dist/config/opencode-config.js +124 -0
- package/dist/config/pi-config.d.ts +49 -0
- package/dist/config/pi-config.js +151 -0
- package/dist/config/wizard.d.ts +12 -0
- package/dist/config/wizard.js +84 -0
- package/dist/connector/file-limits.d.ts +7 -0
- package/dist/connector/file-limits.js +8 -0
- package/dist/connector/index.d.ts +86 -0
- package/dist/connector/index.js +340 -0
- package/dist/error-classification.d.ts +15 -0
- package/dist/error-classification.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +580 -0
- package/dist/instance-lock.d.ts +30 -0
- package/dist/instance-lock.js +184 -0
- package/dist/logger/index.d.ts +57 -0
- package/dist/logger/index.js +178 -0
- package/dist/order/index.d.ts +27 -0
- package/dist/order/index.js +90 -0
- package/dist/persistence/atomic-write.d.ts +10 -0
- package/dist/persistence/atomic-write.js +65 -0
- package/dist/persistence/load-json-file.d.ts +9 -0
- package/dist/persistence/load-json-file.js +23 -0
- package/dist/restart.d.ts +24 -0
- package/dist/restart.js +73 -0
- package/dist/router/card-helpers.d.ts +28 -0
- package/dist/router/card-helpers.js +65 -0
- package/dist/router/config/claude.d.ts +14 -0
- package/dist/router/config/claude.js +72 -0
- package/dist/router/config/codex.d.ts +22 -0
- package/dist/router/config/codex.js +100 -0
- package/dist/router/config/common/render.d.ts +16 -0
- package/dist/router/config/common/render.js +158 -0
- package/dist/router/config/index.d.ts +18 -0
- package/dist/router/config/index.js +38 -0
- package/dist/router/config/kimi.d.ts +21 -0
- package/dist/router/config/kimi.js +63 -0
- package/dist/router/config/opencode.d.ts +12 -0
- package/dist/router/config/opencode.js +44 -0
- package/dist/router/config/pi.d.ts +12 -0
- package/dist/router/config/pi.js +50 -0
- package/dist/router/config/types.d.ts +43 -0
- package/dist/router/config/types.js +8 -0
- package/dist/router/index.d.ts +318 -0
- package/dist/router/index.js +2715 -0
- package/dist/router/order-exec-dispatch.d.ts +30 -0
- package/dist/router/order-exec-dispatch.js +48 -0
- package/dist/router/utils.d.ts +44 -0
- package/dist/router/utils.js +116 -0
- package/dist/runner/bash/index.d.ts +4 -0
- package/dist/runner/bash/index.js +5 -0
- package/dist/runner/bash/runner.d.ts +55 -0
- package/dist/runner/bash/runner.js +213 -0
- package/dist/runner/claude/index.d.ts +4 -0
- package/dist/runner/claude/index.js +5 -0
- package/dist/runner/claude/runner.d.ts +29 -0
- package/dist/runner/claude/runner.js +83 -0
- package/dist/runner/codex/argv.d.ts +33 -0
- package/dist/runner/codex/argv.js +40 -0
- package/dist/runner/codex/index.d.ts +1 -0
- package/dist/runner/codex/index.js +2 -0
- package/dist/runner/codex/jsonl.d.ts +37 -0
- package/dist/runner/codex/jsonl.js +221 -0
- package/dist/runner/codex/runner.d.ts +65 -0
- package/dist/runner/codex/runner.js +124 -0
- package/dist/runner/common/exec-translator.d.ts +73 -0
- package/dist/runner/common/exec-translator.js +105 -0
- package/dist/runner/common/jsonl-stream.d.ts +39 -0
- package/dist/runner/common/jsonl-stream.js +203 -0
- package/dist/runner/common/process-stopper.d.ts +28 -0
- package/dist/runner/common/process-stopper.js +79 -0
- package/dist/runner/common/runner-utils.d.ts +21 -0
- package/dist/runner/common/runner-utils.js +65 -0
- package/dist/runner/common/spawn-heartbeat.d.ts +33 -0
- package/dist/runner/common/spawn-heartbeat.js +51 -0
- package/dist/runner/common/spawning-runner.d.ts +321 -0
- package/dist/runner/common/spawning-runner.js +634 -0
- package/dist/runner/common/usage.d.ts +31 -0
- package/dist/runner/common/usage.js +29 -0
- package/dist/runner/index.d.ts +19 -0
- package/dist/runner/index.js +24 -0
- package/dist/runner/kimi/index.d.ts +1 -0
- package/dist/runner/kimi/index.js +2 -0
- package/dist/runner/kimi/jsonl.d.ts +28 -0
- package/dist/runner/kimi/jsonl.js +143 -0
- package/dist/runner/kimi/runner.d.ts +50 -0
- package/dist/runner/kimi/runner.js +105 -0
- package/dist/runner/opencode/argv.d.ts +32 -0
- package/dist/runner/opencode/argv.js +35 -0
- package/dist/runner/opencode/index.d.ts +1 -0
- package/dist/runner/opencode/index.js +2 -0
- package/dist/runner/opencode/jsonl.d.ts +68 -0
- package/dist/runner/opencode/jsonl.js +295 -0
- package/dist/runner/opencode/runner.d.ts +63 -0
- package/dist/runner/opencode/runner.js +93 -0
- package/dist/runner/pi/index.d.ts +1 -0
- package/dist/runner/pi/index.js +2 -0
- package/dist/runner/pi/jsonl.d.ts +128 -0
- package/dist/runner/pi/jsonl.js +249 -0
- package/dist/runner/pi/runner.d.ts +37 -0
- package/dist/runner/pi/runner.js +76 -0
- package/dist/runner/registry.d.ts +54 -0
- package/dist/runner/registry.js +75 -0
- package/dist/runner/resolve-agent-choices.d.ts +7 -0
- package/dist/runner/resolve-agent-choices.js +76 -0
- package/dist/runner/sync-agent-choices.d.ts +6 -0
- package/dist/runner/sync-agent-choices.js +73 -0
- package/dist/runner/types.d.ts +250 -0
- package/dist/runner/types.js +8 -0
- package/dist/session/claude/index.d.ts +1 -0
- package/dist/session/claude/index.js +2 -0
- package/dist/session/claude/session-reader.d.ts +34 -0
- package/dist/session/claude/session-reader.js +46 -0
- package/dist/session/claude/sessions.d.ts +53 -0
- package/dist/session/claude/sessions.js +528 -0
- package/dist/session/codex/index.d.ts +1 -0
- package/dist/session/codex/index.js +2 -0
- package/dist/session/codex/rollout-reader.d.ts +88 -0
- package/dist/session/codex/rollout-reader.js +473 -0
- package/dist/session/codex/sessions.d.ts +21 -0
- package/dist/session/codex/sessions.js +40 -0
- package/dist/session/common/catch-up.d.ts +12 -0
- package/dist/session/common/catch-up.js +31 -0
- package/dist/session/common/constants.d.ts +11 -0
- package/dist/session/common/constants.js +12 -0
- package/dist/session/common/content-blocks.d.ts +37 -0
- package/dist/session/common/content-blocks.js +68 -0
- package/dist/session/common/jsonl.d.ts +77 -0
- package/dist/session/common/jsonl.js +351 -0
- package/dist/session/common/usage-accumulator.d.ts +58 -0
- package/dist/session/common/usage-accumulator.js +72 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +11 -0
- package/dist/session/kimi/index.d.ts +1 -0
- package/dist/session/kimi/index.js +2 -0
- package/dist/session/kimi/sessions.d.ts +81 -0
- package/dist/session/kimi/sessions.js +527 -0
- package/dist/session/opencode/index.d.ts +1 -0
- package/dist/session/opencode/index.js +2 -0
- package/dist/session/opencode/sessions.d.ts +81 -0
- package/dist/session/opencode/sessions.js +388 -0
- package/dist/session/pi/index.d.ts +1 -0
- package/dist/session/pi/index.js +2 -0
- package/dist/session/pi/sessions.d.ts +27 -0
- package/dist/session/pi/sessions.js +399 -0
- package/dist/session/registry.d.ts +12 -0
- package/dist/session/registry.js +20 -0
- package/dist/session/session-store.d.ts +86 -0
- package/dist/session/session-store.js +450 -0
- package/dist/startup-contact.d.ts +18 -0
- package/dist/startup-contact.js +51 -0
- package/dist/workspace/index.d.ts +19 -0
- package/dist/workspace/index.js +53 -0
- 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';
|