lark-remote 0.1.6 → 0.1.8
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 +51 -1
- package/README.en.md +9 -1
- package/README.md +14 -2
- package/dist/bridge/approval-coordinator.d.ts +71 -34
- package/dist/bridge/approval-coordinator.js +225 -41
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +97 -13
- package/dist/bridge/index.js +227 -87
- package/dist/bridge/queue-manager.d.ts +2 -1
- package/dist/bridge/queue-manager.js +1 -0
- package/dist/card/approval-render.js +294 -36
- package/dist/card/bash-card-session.d.ts +2 -24
- package/dist/card/bash-card-session.js +6 -73
- package/dist/card/bash-renderer.js +2 -3
- package/dist/card/card-budget.js +29 -75
- package/dist/card/card-session.d.ts +48 -0
- package/dist/card/card-session.js +86 -0
- package/dist/card/card-shared.d.ts +7 -16
- package/dist/card/card-shared.js +7 -30
- package/dist/card/run-card-session.d.ts +2 -55
- package/dist/card/run-card-session.js +6 -104
- package/dist/card/run-renderer.d.ts +7 -0
- package/dist/card/run-renderer.js +116 -238
- package/dist/card/run-state.d.ts +13 -0
- package/dist/card/run-state.js +25 -16
- package/dist/card/text-truncate.d.ts +13 -12
- package/dist/card/text-truncate.js +13 -63
- package/dist/common/fs.d.ts +8 -0
- package/dist/common/fs.js +17 -0
- package/dist/common/guards.d.ts +0 -20
- package/dist/common/guards.js +0 -43
- package/dist/common/sleep.d.ts +2 -0
- package/dist/common/sleep.js +5 -0
- package/dist/common/truncate.d.ts +39 -0
- package/dist/common/truncate.js +92 -0
- package/dist/config/codex-config.d.ts +1 -1
- package/dist/config/codex-config.js +1 -1
- package/dist/config/index.d.ts +112 -24
- package/dist/config/index.js +139 -37
- package/dist/connector/index.d.ts +69 -2
- package/dist/connector/index.js +107 -1
- package/dist/index.js +150 -118
- package/dist/instance-lock.js +4 -12
- package/dist/order/alias-resolve.d.ts +13 -0
- package/dist/order/alias-resolve.js +22 -0
- package/dist/order/alias-store.d.ts +38 -0
- package/dist/order/alias-store.js +106 -0
- package/dist/persistence/atomic-write.d.ts +8 -2
- package/dist/persistence/atomic-write.js +23 -7
- package/dist/restart.js +2 -1
- package/dist/router/card-action-payload.d.ts +30 -0
- package/dist/router/card-action-payload.js +39 -0
- package/dist/router/card-helpers.d.ts +63 -0
- package/dist/router/card-helpers.js +146 -0
- package/dist/router/config/claude.js +30 -1
- package/dist/router/config/codex.js +29 -50
- package/dist/router/config/common/model-patch.d.ts +22 -0
- package/dist/router/config/common/model-patch.js +26 -0
- package/dist/router/config/common/render.js +5 -4
- package/dist/router/config/dsh.d.ts +51 -0
- package/dist/router/config/dsh.js +179 -0
- package/dist/router/config/index.d.ts +12 -0
- package/dist/router/config/index.js +28 -1
- package/dist/router/config/kimi.d.ts +4 -1
- package/dist/router/config/kimi.js +27 -0
- package/dist/router/config/opencode.js +18 -5
- package/dist/router/config/pi.js +4 -5
- package/dist/router/config/types.d.ts +17 -2
- package/dist/router/index.d.ts +76 -22
- package/dist/router/index.js +479 -548
- package/dist/router/utils.d.ts +2 -0
- package/dist/router/utils.js +16 -2
- package/dist/runner/agent-choices-common.d.ts +16 -0
- package/dist/runner/agent-choices-common.js +34 -0
- package/dist/runner/claude/runner.d.ts +47 -10
- package/dist/runner/claude/runner.js +117 -46
- package/dist/runner/claude/session.d.ts +166 -0
- package/dist/runner/claude/session.js +768 -0
- package/dist/runner/codex/app-server/protocol-types.d.ts +66 -39
- package/dist/runner/codex/app-server/protocol-types.js +2 -13
- package/dist/runner/codex/app-server/runner.d.ts +52 -59
- package/dist/runner/codex/app-server/runner.js +216 -338
- package/dist/runner/codex/app-server/translator.d.ts +8 -21
- package/dist/runner/codex/app-server/translator.js +45 -18
- package/dist/runner/codex/index.d.ts +0 -2
- package/dist/runner/codex/index.js +0 -2
- package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
- package/dist/runner/common/acp/protocol-helpers.js +21 -0
- package/dist/runner/common/acp/protocol-types.d.ts +271 -0
- package/dist/runner/common/acp/protocol-types.js +77 -0
- package/dist/runner/common/connection-based-runner.d.ts +111 -0
- package/dist/runner/common/connection-based-runner.js +261 -0
- package/dist/runner/{codex/app-server → common/jsonrpc}/client.d.ts +14 -7
- package/dist/runner/{codex/app-server → common/jsonrpc}/client.js +27 -18
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
- package/dist/runner/{codex/app-server → common/jsonrpc}/connection-manager.js +63 -28
- package/dist/runner/{codex/app-server → common/jsonrpc}/transport.d.ts +5 -4
- package/dist/runner/{codex/app-server → common/jsonrpc}/transport.js +18 -19
- package/dist/runner/common/jsonrpc/types.d.ts +32 -0
- package/dist/runner/common/jsonrpc/types.js +9 -0
- package/dist/runner/common/process-stopper.d.ts +2 -1
- package/dist/runner/common/process-stopper.js +1 -0
- package/dist/runner/common/runner-utils.d.ts +6 -13
- package/dist/runner/common/runner-utils.js +6 -47
- package/dist/runner/common/spawning-runner.d.ts +24 -70
- package/dist/runner/common/spawning-runner.js +98 -152
- package/dist/runner/dsh/client.d.ts +95 -0
- package/dist/runner/dsh/client.js +418 -0
- package/dist/runner/dsh/fake-dsh-server.d.ts +60 -0
- package/dist/runner/dsh/fake-dsh-server.js +156 -0
- package/dist/runner/dsh/index.d.ts +3 -0
- package/dist/runner/dsh/index.js +4 -0
- package/dist/runner/dsh/runner.d.ts +57 -0
- package/dist/runner/dsh/runner.js +191 -0
- package/dist/runner/dsh/translator.d.ts +28 -0
- package/dist/runner/dsh/translator.js +98 -0
- package/dist/runner/dsh/types.d.ts +155 -0
- package/dist/runner/dsh/types.js +14 -0
- package/dist/runner/index.d.ts +5 -4
- package/dist/runner/index.js +5 -4
- package/dist/runner/kimi/acp/runner.d.ts +106 -0
- package/dist/runner/kimi/acp/runner.js +516 -0
- package/dist/runner/kimi/acp/translator.d.ts +138 -0
- package/dist/runner/kimi/acp/translator.js +441 -0
- package/dist/runner/kimi/index.d.ts +1 -1
- package/dist/runner/kimi/index.js +1 -1
- package/dist/runner/opencode/acp/index.d.ts +1 -0
- package/dist/runner/opencode/acp/index.js +2 -0
- package/dist/runner/opencode/acp/runner.d.ts +121 -0
- package/dist/runner/opencode/acp/runner.js +429 -0
- package/dist/runner/opencode/acp/translator.d.ts +119 -0
- package/dist/runner/opencode/acp/translator.js +345 -0
- package/dist/runner/opencode/index.d.ts +1 -1
- package/dist/runner/opencode/index.js +1 -1
- package/dist/runner/pi/index.d.ts +1 -1
- package/dist/runner/pi/index.js +1 -1
- package/dist/runner/pi/rpc/client.d.ts +52 -0
- package/dist/runner/pi/rpc/client.js +114 -0
- package/dist/runner/pi/rpc/index.d.ts +4 -0
- package/dist/runner/pi/rpc/index.js +4 -0
- package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
- package/dist/runner/pi/rpc/protocol-types.js +21 -0
- package/dist/runner/pi/rpc/runner.d.ts +74 -0
- package/dist/runner/pi/rpc/runner.js +190 -0
- package/dist/runner/pi/rpc/translator.d.ts +55 -0
- package/dist/runner/pi/rpc/translator.js +258 -0
- package/dist/runner/probe.js +6 -1
- package/dist/runner/question-common.d.ts +26 -0
- package/dist/runner/question-common.js +42 -0
- package/dist/runner/registry.d.ts +7 -8
- package/dist/runner/registry.js +3 -4
- package/dist/runner/resolve-agent-choices.js +18 -59
- package/dist/runner/sync-agent-choices.js +16 -67
- package/dist/runner/types.d.ts +97 -14
- package/dist/session/claude/session-index.js +2 -3
- package/dist/session/claude/session-reader.js +3 -6
- package/dist/session/claude/sessions.js +43 -28
- package/dist/session/codex/rollout-reader.d.ts +2 -2
- package/dist/session/codex/rollout-reader.js +14 -17
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +2 -2
- package/dist/session/common/content-blocks.d.ts +7 -0
- package/dist/session/common/content-blocks.js +13 -9
- package/dist/session/common/jsonl.d.ts +8 -25
- package/dist/session/common/jsonl.js +126 -261
- package/dist/session/common/pagination.d.ts +28 -0
- package/dist/session/common/pagination.js +33 -0
- package/dist/session/dsh/index.d.ts +1 -0
- package/dist/session/dsh/index.js +2 -0
- package/dist/session/dsh/sessions.d.ts +39 -0
- package/dist/session/dsh/sessions.js +237 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/kimi/sessions.d.ts +34 -1
- package/dist/session/kimi/sessions.js +208 -98
- package/dist/session/opencode/sessions.d.ts +17 -3
- package/dist/session/opencode/sessions.js +29 -29
- package/dist/session/pi/sessions.d.ts +5 -1
- package/dist/session/pi/sessions.js +12 -16
- package/dist/session/session-store.d.ts +11 -1
- package/dist/session/session-store.js +75 -178
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/install.d.ts +20 -2
- package/dist/update/install.js +51 -7
- package/dist/workspace/index.d.ts +4 -0
- package/dist/workspace/index.js +8 -1
- package/package.json +2 -1
- package/dist/runner/codex/app-server/connection-manager.d.ts +0 -56
- package/dist/runner/codex/argv.d.ts +0 -33
- package/dist/runner/codex/argv.js +0 -40
- package/dist/runner/codex/factory.d.ts +0 -29
- package/dist/runner/codex/factory.js +0 -39
- package/dist/runner/codex/jsonl.d.ts +0 -37
- package/dist/runner/codex/jsonl.js +0 -221
- package/dist/runner/codex/runner.d.ts +0 -66
- package/dist/runner/codex/runner.js +0 -129
- package/dist/runner/common/exec-translator.d.ts +0 -73
- package/dist/runner/common/exec-translator.js +0 -105
- package/dist/runner/kimi/jsonl.d.ts +0 -28
- package/dist/runner/kimi/jsonl.js +0 -143
- package/dist/runner/kimi/runner.d.ts +0 -49
- package/dist/runner/kimi/runner.js +0 -105
- package/dist/runner/opencode/argv.d.ts +0 -32
- package/dist/runner/opencode/argv.js +0 -35
- package/dist/runner/opencode/jsonl.d.ts +0 -68
- package/dist/runner/opencode/jsonl.js +0 -295
- package/dist/runner/opencode/runner.d.ts +0 -61
- package/dist/runner/opencode/runner.js +0 -93
- package/dist/runner/pi/jsonl.d.ts +0 -128
- package/dist/runner/pi/jsonl.js +0 -249
- package/dist/runner/pi/runner.d.ts +0 -36
- package/dist/runner/pi/runner.js +0 -76
- package/dist/session/common/catch-up.d.ts +0 -12
- package/dist/session/common/catch-up.js +0 -31
|
@@ -1,66 +1,135 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Stops reading as soon as a match is found.
|
|
3
|
+
* Stream a JSONL file line-by-line using chunked reads.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Opens the file, reads in 64KB chunks, splits on `\n`, and invokes `onLine`
|
|
6
|
+
* for each non-empty trimmed line. Handles files without a trailing newline.
|
|
7
|
+
*
|
|
8
|
+
* `onLine` may return `true` to **stop early** (no further reads); the file
|
|
9
|
+
* descriptor is always closed in a `finally` block.
|
|
10
|
+
*
|
|
11
|
+
* @returns `true` if the file was opened and scanned, `false` on open error.
|
|
9
12
|
*/
|
|
10
|
-
|
|
13
|
+
function forEachJsonlLine(filePath, onLine) {
|
|
14
|
+
return forEachJsonlLineCore(filePath, { onLine });
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Variant of `forEachJsonlLine` that also tracks **byte offsets** for each line.
|
|
18
|
+
*
|
|
19
|
+
* `onLine` receives `(line, offset)` where `offset` is the starting byte offset
|
|
20
|
+
* of the line within the file. May return `true` to stop early.
|
|
21
|
+
*
|
|
22
|
+
* @returns `true` if the file was opened and scanned, `false` on open error.
|
|
23
|
+
*/
|
|
24
|
+
function forEachJsonlLineWithOffset(filePath, onLine) {
|
|
25
|
+
return forEachJsonlLineCore(filePath, {
|
|
26
|
+
onLine: (line, offset) => onLine(line, offset ?? 0),
|
|
27
|
+
trackOffsets: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Variant of `forEachJsonlLine` that starts reading from a byte `offset`.
|
|
32
|
+
*
|
|
33
|
+
* @param filePath Absolute path to a `.jsonl` file.
|
|
34
|
+
* @param offset Byte offset to seek to before reading. **Must fall on a line
|
|
35
|
+
* boundary**; a mid-line offset will include a leading partial line.
|
|
36
|
+
* @param onLine Called for each non-empty trimmed line. May return `true` to stop early.
|
|
37
|
+
* @returns `true` if the file was opened and scanned, `false` on open error.
|
|
38
|
+
*/
|
|
39
|
+
function forEachJsonlLineFromOffset(filePath, offset, onLine) {
|
|
40
|
+
if (offset < 0)
|
|
41
|
+
return false;
|
|
42
|
+
return forEachJsonlLineCore(filePath, { startOffset: offset, onLine });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Core chunked JSONL reader shared by the three public variants. Reads the
|
|
46
|
+
* file in 64KB chunks, splitting on `\n` at the byte level (UTF-8 multi-byte
|
|
47
|
+
* characters never contain 0x0A, so this never truncates a multi-byte seq).
|
|
48
|
+
*
|
|
49
|
+
* @param opts.startOffset Byte offset to seek to before reading (0 = start).
|
|
50
|
+
* @param opts.trackOffsets When true, pass each line's starting byte offset to onLine.
|
|
51
|
+
* @param opts.onLine Called per non-empty trimmed line; may return `true` to stop.
|
|
52
|
+
*/
|
|
53
|
+
function forEachJsonlLineCore(filePath, opts) {
|
|
11
54
|
let fd;
|
|
12
55
|
try {
|
|
13
56
|
fd = fs.openSync(filePath, 'r');
|
|
14
57
|
}
|
|
15
58
|
catch {
|
|
16
|
-
return
|
|
59
|
+
return false;
|
|
17
60
|
}
|
|
18
|
-
const
|
|
61
|
+
const CHUNK_SIZE = 65536;
|
|
62
|
+
const chunk = Buffer.alloc(CHUNK_SIZE);
|
|
63
|
+
// Raw byte buffer holding the not-yet-decoded tail (everything after the
|
|
64
|
+
// last consumed `\n`). UTF-8 multi-byte characters never contain 0x0A,
|
|
65
|
+
// so splitting on `\n` at the byte level never truncates a multi-byte seq.
|
|
19
66
|
let buf = Buffer.alloc(0);
|
|
67
|
+
let position = opts.startOffset ?? 0;
|
|
68
|
+
let baseOffset = 0;
|
|
69
|
+
let stop = false;
|
|
20
70
|
try {
|
|
21
71
|
for (;;) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
72
|
+
const nread = fs.readSync(fd, chunk, 0, CHUNK_SIZE, position);
|
|
73
|
+
if (nread === 0)
|
|
24
74
|
break;
|
|
25
|
-
buf = Buffer.concat([buf, chunk.subarray(0,
|
|
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.
|
|
75
|
+
buf = Buffer.concat([buf, chunk.subarray(0, nread)]);
|
|
29
76
|
let nlIdx;
|
|
30
77
|
while ((nlIdx = buf.lastIndexOf(0x0a)) !== -1) {
|
|
31
|
-
|
|
32
|
-
const text =
|
|
33
|
-
// Keep the tail after the last `\n` as the new buffer.
|
|
78
|
+
const complete = buf.subarray(0, nlIdx);
|
|
79
|
+
const text = complete.toString('utf-8');
|
|
34
80
|
buf = buf.subarray(nlIdx + 1);
|
|
35
81
|
const parts = text.split('\n');
|
|
36
|
-
|
|
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).
|
|
82
|
+
let cursor = (opts.startOffset ?? 0) + baseOffset;
|
|
45
83
|
for (const part of parts) {
|
|
46
84
|
const trimmed = part.trim();
|
|
47
|
-
if (trimmed
|
|
48
|
-
|
|
85
|
+
if (trimmed) {
|
|
86
|
+
if (opts.onLine(trimmed, opts.trackOffsets ? cursor : undefined) === true) {
|
|
87
|
+
stop = true;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
49
90
|
}
|
|
91
|
+
cursor += Buffer.byteLength(part, 'utf-8') + 1;
|
|
50
92
|
}
|
|
51
|
-
|
|
93
|
+
baseOffset += complete.length + 1;
|
|
94
|
+
break; // only process up to last complete newline per chunk
|
|
52
95
|
}
|
|
96
|
+
if (stop)
|
|
97
|
+
break;
|
|
98
|
+
position += nread;
|
|
53
99
|
}
|
|
54
|
-
// Handle
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
100
|
+
// Handle remaining tail (file without trailing newline).
|
|
101
|
+
if (!stop) {
|
|
102
|
+
const tail = buf.toString('utf-8').trim();
|
|
103
|
+
if (tail) {
|
|
104
|
+
opts.onLine(tail, opts.trackOffsets ? (opts.startOffset ?? 0) + baseOffset : undefined);
|
|
105
|
+
}
|
|
58
106
|
}
|
|
59
107
|
}
|
|
60
108
|
finally {
|
|
61
109
|
fs.closeSync(fd);
|
|
62
110
|
}
|
|
63
|
-
return
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
// ===========================================================================
|
|
114
|
+
// Public API
|
|
115
|
+
// ===========================================================================
|
|
116
|
+
/**
|
|
117
|
+
* Find the first line in a JSONL file that matches a predicate using streaming reads.
|
|
118
|
+
* Stops reading as soon as a match is found.
|
|
119
|
+
*
|
|
120
|
+
* @param filePath Absolute path to a `.jsonl` file.
|
|
121
|
+
* @param pred Predicate applied to each trimmed non-empty line.
|
|
122
|
+
* @returns The matching trimmed line, or null if no match / file not found / read error.
|
|
123
|
+
*/
|
|
124
|
+
export function findJsonlLine(filePath, pred) {
|
|
125
|
+
let found = null;
|
|
126
|
+
forEachJsonlLine(filePath, (line) => {
|
|
127
|
+
if (pred(line)) {
|
|
128
|
+
found = line;
|
|
129
|
+
return true; // stop early
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return found;
|
|
64
133
|
}
|
|
65
134
|
/**
|
|
66
135
|
* Read the last non-empty line from a JSONL file using streaming reads.
|
|
@@ -69,58 +138,17 @@ export function findJsonlLine(filePath, pred) {
|
|
|
69
138
|
* @returns The last trimmed non-empty line, or null if the file is empty / not found.
|
|
70
139
|
*/
|
|
71
140
|
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
141
|
let last = null;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
}
|
|
142
|
+
forEachJsonlLine(filePath, (line) => {
|
|
143
|
+
last = line;
|
|
144
|
+
});
|
|
110
145
|
return last;
|
|
111
146
|
}
|
|
112
147
|
/**
|
|
113
148
|
* Read the last N non-empty lines from a JSONL file using streaming reads.
|
|
114
149
|
*
|
|
115
150
|
* 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).
|
|
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.
|
|
151
|
+
* this only retains the last `n` lines — memory is O(n), not O(whole file).
|
|
124
152
|
*
|
|
125
153
|
* @param filePath Absolute path to a `.jsonl` file.
|
|
126
154
|
* @param n Maximum number of trailing non-empty lines to retain.
|
|
@@ -130,222 +158,59 @@ export function readLastJsonlLine(filePath) {
|
|
|
130
158
|
export function readLastNJsonlLines(filePath, n) {
|
|
131
159
|
if (n <= 0)
|
|
132
160
|
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
161
|
const lines = [];
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
}
|
|
162
|
+
forEachJsonlLine(filePath, (line) => {
|
|
163
|
+
lines.push(line);
|
|
164
|
+
if (lines.length > n) {
|
|
165
|
+
lines.splice(0, lines.length - n);
|
|
164
166
|
}
|
|
165
|
-
|
|
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
|
-
}
|
|
167
|
+
});
|
|
176
168
|
return lines;
|
|
177
169
|
}
|
|
178
170
|
/**
|
|
179
171
|
* Stream-scan a JSONL file once, invoking `onLine` for each non-empty line
|
|
180
172
|
* with the line's raw string and its **starting byte offset** within the file.
|
|
181
173
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
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).
|
|
174
|
+
* Memory is O(chunk), not O(whole file). The callback receives the byte offset
|
|
175
|
+
* so callers can record where a line of interest begins, then re-read only the
|
|
176
|
+
* tail from that offset via `readJsonlLinesFromOffset`.
|
|
188
177
|
*
|
|
189
178
|
* @param filePath Absolute path to a `.jsonl` file.
|
|
190
|
-
* @param onLine Called for each non-empty line with `(line, offset)
|
|
191
|
-
* `offset` is the byte offset of the line's first character.
|
|
179
|
+
* @param onLine Called for each non-empty line with `(line, offset)`.
|
|
192
180
|
* @returns `true` if the file was opened and scanned, `false` on open error.
|
|
193
181
|
*/
|
|
194
182
|
export function scanJsonlLines(filePath, onLine) {
|
|
195
|
-
|
|
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;
|
|
183
|
+
return forEachJsonlLineWithOffset(filePath, onLine);
|
|
243
184
|
}
|
|
244
185
|
/**
|
|
245
186
|
* Read all non-empty lines starting from a byte `offset` in a JSONL file.
|
|
246
187
|
*
|
|
247
|
-
* Companion to `scanJsonlLines` for the
|
|
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).
|
|
188
|
+
* Companion to `scanJsonlLines` for the byte-offset two-pass pattern.
|
|
251
189
|
*
|
|
252
190
|
* @param filePath Absolute path to a `.jsonl` file.
|
|
253
191
|
* @param offset Byte offset to seek to before reading. **Must fall on a line
|
|
254
|
-
* boundary
|
|
255
|
-
* user message as recorded by `scanJsonlLines`); a mid-line
|
|
256
|
-
* offset will include a leading partial line.
|
|
192
|
+
* boundary**; a mid-line offset will include a leading partial line.
|
|
257
193
|
* @returns Array of raw line strings from `offset` to EOF (empty lines
|
|
258
194
|
* excluded). Empty array on open/seek error or `offset` past EOF.
|
|
259
195
|
*/
|
|
260
196
|
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
197
|
const lines = [];
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
}
|
|
198
|
+
forEachJsonlLineFromOffset(filePath, offset, (line) => {
|
|
199
|
+
lines.push(line);
|
|
200
|
+
});
|
|
302
201
|
return lines;
|
|
303
202
|
}
|
|
304
203
|
/**
|
|
305
204
|
* Read all non-empty lines from a JSONL file using streaming reads.
|
|
306
205
|
*
|
|
307
206
|
* @param filePath Absolute path to a `.jsonl` file.
|
|
308
|
-
* @param chunkSize Buffer size for each `fs.readSync` call (default 65536).
|
|
309
207
|
* @returns Array of raw line strings (empty lines excluded).
|
|
310
208
|
*/
|
|
311
|
-
export function readJsonlLines(filePath
|
|
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);
|
|
209
|
+
export function readJsonlLines(filePath) {
|
|
321
210
|
const lines = [];
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
}
|
|
211
|
+
forEachJsonlLine(filePath, (line) => {
|
|
212
|
+
lines.push(line);
|
|
213
|
+
});
|
|
349
214
|
return lines;
|
|
350
215
|
}
|
|
351
216
|
//# sourceMappingURL=jsonl.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared pagination / truncation helpers for agent session readers.
|
|
3
|
+
*
|
|
4
|
+
* The five readers (claude/codex/opencode/pi/kimi) all implement the same
|
|
5
|
+
* offset-clamp + slice + total pattern for `listSessions`, and the same
|
|
6
|
+
* maxEvents tail-cap for `readSessionContent`. These were duplicated (and had
|
|
7
|
+
* drifted on the default limit). Centralized here so the contract is defined
|
|
8
|
+
* once: default page limit is 20, matching the majority of readers.
|
|
9
|
+
*/
|
|
10
|
+
/** Default page size for listSessions. */
|
|
11
|
+
export declare const DEFAULT_LIST_LIMIT = 20;
|
|
12
|
+
/**
|
|
13
|
+
* Paginate a (already-ordered) full item list by offset + limit.
|
|
14
|
+
* Clamps negative offset to 0; caps limit to the remaining items.
|
|
15
|
+
*/
|
|
16
|
+
export declare function paginate<T>(items: T[], opts: {
|
|
17
|
+
limit?: number;
|
|
18
|
+
offset?: number;
|
|
19
|
+
}): {
|
|
20
|
+
items: T[];
|
|
21
|
+
total: number;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Apply the maxEvents tail-cap shared by readSessionContent:
|
|
25
|
+
* keep the LAST N events; maxEvents <= 0 returns [] (guards the
|
|
26
|
+
* `slice(-0) === slice(0)` full-array trap).
|
|
27
|
+
*/
|
|
28
|
+
export declare function capEvents<T>(events: T[], maxEvents?: number): T[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared pagination / truncation helpers for agent session readers.
|
|
3
|
+
*
|
|
4
|
+
* The five readers (claude/codex/opencode/pi/kimi) all implement the same
|
|
5
|
+
* offset-clamp + slice + total pattern for `listSessions`, and the same
|
|
6
|
+
* maxEvents tail-cap for `readSessionContent`. These were duplicated (and had
|
|
7
|
+
* drifted on the default limit). Centralized here so the contract is defined
|
|
8
|
+
* once: default page limit is 20, matching the majority of readers.
|
|
9
|
+
*/
|
|
10
|
+
/** Default page size for listSessions. */
|
|
11
|
+
export const DEFAULT_LIST_LIMIT = 20;
|
|
12
|
+
/**
|
|
13
|
+
* Paginate a (already-ordered) full item list by offset + limit.
|
|
14
|
+
* Clamps negative offset to 0; caps limit to the remaining items.
|
|
15
|
+
*/
|
|
16
|
+
export function paginate(items, opts) {
|
|
17
|
+
const limit = opts.limit ?? DEFAULT_LIST_LIMIT;
|
|
18
|
+
const offset = Math.max(0, opts.offset ?? 0);
|
|
19
|
+
return { items: items.slice(offset, offset + limit), total: items.length };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Apply the maxEvents tail-cap shared by readSessionContent:
|
|
23
|
+
* keep the LAST N events; maxEvents <= 0 returns [] (guards the
|
|
24
|
+
* `slice(-0) === slice(0)` full-array trap).
|
|
25
|
+
*/
|
|
26
|
+
export function capEvents(events, maxEvents) {
|
|
27
|
+
if (maxEvents === undefined || maxEvents > events.length)
|
|
28
|
+
return events;
|
|
29
|
+
if (maxEvents <= 0)
|
|
30
|
+
return [];
|
|
31
|
+
return events.slice(-maxEvents);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DshSessionReader } from './sessions.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DshSessionReader — AgentSessionReader backed by the DSH Web Host over HTTP.
|
|
3
|
+
*
|
|
4
|
+
* The AgentSessionReader contract is fully synchronous (file-style readers),
|
|
5
|
+
* but DSH exposes only async HTTP. The reader therefore bridges with
|
|
6
|
+
* `spawnSync('curl', ...)` for its unary calls (session.list / session.history)
|
|
7
|
+
* — a synchronous HTTP round-trip to the local DSH host. This keeps the
|
|
8
|
+
* router/bridge interface contract intact without touching any upstream logic.
|
|
9
|
+
*
|
|
10
|
+
* listSessions: session.list → filter by cwd → sort by updatedAt desc → local
|
|
11
|
+
* pagination (DSH v1 has no cursor pagination).
|
|
12
|
+
* readSessionContent: session.history → map SessionEvents to display events +
|
|
13
|
+
* session-wide usage accumulation (assistant/message.usage).
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentSession, AgentSessionReader, SessionContent } from '../../runner/index.js';
|
|
16
|
+
interface DshSessionReaderOptions {
|
|
17
|
+
host?: string;
|
|
18
|
+
/** Injectable synchronous unary transport. Defaults to spawnSync curl. */
|
|
19
|
+
syncRequest?: (baseUrl: string, method: string, payload: unknown) => unknown;
|
|
20
|
+
}
|
|
21
|
+
export declare class DshSessionReader implements AgentSessionReader {
|
|
22
|
+
private readonly baseUrl;
|
|
23
|
+
private readonly syncRequest;
|
|
24
|
+
constructor(opts?: DshSessionReaderOptions);
|
|
25
|
+
private unary;
|
|
26
|
+
listSessions(cwd: string, opts?: {
|
|
27
|
+
limit?: number;
|
|
28
|
+
offset?: number;
|
|
29
|
+
}): {
|
|
30
|
+
sessions: AgentSession[];
|
|
31
|
+
total: number;
|
|
32
|
+
};
|
|
33
|
+
getNewestSession(cwd: string): AgentSession | null;
|
|
34
|
+
readSessionContent(sessionId: string, cwd: string, opts?: {
|
|
35
|
+
maxEvents?: number;
|
|
36
|
+
}): SessionContent;
|
|
37
|
+
isSessionActive(sessionId: string, cwd: string): boolean;
|
|
38
|
+
}
|
|
39
|
+
export {};
|