lark-remote 0.4.5 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/bridge/approval-coordinator.d.ts +1 -1
- package/dist/bridge/approval-coordinator.js +3 -3
- package/dist/bridge/inbound-media.js +10 -7
- package/dist/bridge/index.d.ts +55 -17
- package/dist/bridge/index.js +145 -74
- package/dist/bridge/queue-manager.d.ts +71 -20
- package/dist/bridge/queue-manager.js +139 -57
- package/dist/card/approval-render.d.ts +1 -1
- package/dist/card/approval-render.js +6 -6
- package/dist/card/bash-card-session.d.ts +3 -3
- package/dist/card/bash-card-session.js +3 -3
- package/dist/card/bash-renderer.js +1 -1
- package/dist/card/card-session.d.ts +4 -4
- package/dist/card/card-session.js +6 -6
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.d.ts +1 -1
- package/dist/card/run-renderer.js +14 -14
- package/dist/card/run-state.d.ts +2 -2
- package/dist/card/run-state.js +3 -3
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +6 -6
- package/dist/cli.js +2 -1
- package/dist/clone.d.ts +5 -3
- package/dist/clone.js +8 -4
- package/dist/common/truncate.d.ts +1 -1
- package/dist/common/truncate.js +2 -2
- package/dist/common/ttl-cache.d.ts +2 -2
- package/dist/common/ttl-cache.js +2 -2
- package/dist/config/codex-config.d.ts +9 -9
- package/dist/config/codex-config.js +18 -18
- package/dist/config/dir.js +5 -2
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +6 -6
- package/dist/config/opencode-config.js +3 -3
- package/dist/connector/index.d.ts +5 -5
- package/dist/connector/index.js +24 -11
- package/dist/inbound/event.d.ts +1 -1
- package/dist/inbound/event.js +1 -1
- package/dist/inbound/placeholder.d.ts +2 -2
- package/dist/inbound/placeholder.js +2 -2
- package/dist/index.js +23 -12
- package/dist/instance-lock.js +4 -3
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.js +4 -4
- package/dist/persistence/atomic-write.d.ts +1 -1
- package/dist/persistence/atomic-write.js +6 -2
- package/dist/platform/agent-stopper.d.ts +1 -1
- package/dist/platform/identity.d.ts +7 -1
- package/dist/platform/identity.js +2 -2
- package/dist/platform/path.d.ts +6 -0
- package/dist/platform/path.js +14 -8
- package/dist/platform/select.d.ts +1 -1
- package/dist/platform/select.js +1 -1
- package/dist/platform/shell.d.ts +2 -3
- package/dist/platform/spawn.d.ts +2 -2
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/terminator-posix.js +1 -1
- package/dist/platform/terminator-win32.js +2 -2
- package/dist/platform/terminator.d.ts +3 -3
- package/dist/platform/terminator.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/restart.d.ts +1 -1
- package/dist/restart.js +5 -3
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +2 -2
- package/dist/router/config/opencode.js +1 -1
- package/dist/router/index.d.ts +65 -16
- package/dist/router/index.js +141 -47
- package/dist/router/order-exec-dispatch.js +2 -2
- package/dist/runner/bash/runner.d.ts +2 -2
- package/dist/runner/bash/runner.js +6 -6
- package/dist/runner/claude/runner.js +1 -1
- package/dist/runner/claude/session.d.ts +2 -2
- package/dist/runner/claude/session.js +14 -14
- package/dist/runner/codex/app-server/runner.d.ts +28 -0
- package/dist/runner/codex/app-server/runner.js +57 -4
- package/dist/runner/codex/app-server/translator.d.ts +2 -2
- package/dist/runner/codex/app-server/translator.js +5 -5
- package/dist/runner/common/acp/acp-approval.d.ts +2 -2
- package/dist/runner/common/acp/acp-approval.js +3 -3
- package/dist/runner/common/acp/base-acp-runner.js +1 -1
- package/dist/runner/common/acp/base-translator.d.ts +3 -3
- package/dist/runner/common/acp/base-translator.js +4 -4
- package/dist/runner/common/acp/protocol-types.js +1 -1
- package/dist/runner/common/connection-based-runner.d.ts +1 -1
- package/dist/runner/common/connection-based-runner.js +2 -2
- package/dist/runner/common/jsonl-stream.d.ts +2 -2
- package/dist/runner/common/jsonl-stream.js +12 -12
- package/dist/runner/common/jsonrpc/client.d.ts +1 -1
- package/dist/runner/common/jsonrpc/client.js +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.js +1 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
- package/dist/runner/common/jsonrpc/transport.js +2 -2
- package/dist/runner/common/spawning-runner.d.ts +21 -8
- package/dist/runner/common/spawning-runner.js +41 -15
- package/dist/runner/dsh/runner.js +2 -2
- package/dist/runner/kimi/acp/runner.d.ts +3 -3
- package/dist/runner/kimi/acp/runner.js +9 -9
- package/dist/runner/kimi/acp/translator.d.ts +2 -2
- package/dist/runner/kimi/acp/translator.js +4 -4
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +3 -3
- package/dist/runner/opencode/acp/translator.js +1 -1
- package/dist/runner/pi/rpc/runner.js +2 -2
- package/dist/runner/pi/rpc/translator.d.ts +1 -1
- package/dist/runner/pi/rpc/translator.js +4 -4
- package/dist/runner/types.d.ts +11 -2
- package/dist/runner/types.js +12 -1
- package/dist/session/claude/session-index.d.ts +12 -0
- package/dist/session/claude/session-index.js +20 -5
- package/dist/session/claude/sessions.js +10 -10
- package/dist/session/codex/rollout-reader.d.ts +4 -4
- package/dist/session/codex/rollout-reader.js +12 -12
- package/dist/session/common/two-pass.js +2 -2
- package/dist/session/common/usage-accumulator.d.ts +2 -2
- package/dist/session/common/usage-accumulator.js +2 -2
- package/dist/session/dsh/sessions.js +2 -2
- package/dist/session/kimi/sessions.d.ts +2 -2
- package/dist/session/kimi/sessions.js +11 -11
- package/dist/session/opencode/sessions.d.ts +3 -3
- package/dist/session/opencode/sessions.js +12 -10
- package/dist/session/pi/sessions.js +13 -12
- package/dist/session/session-store.d.ts +1 -1
- package/dist/session/session-store.js +1 -1
- package/package.json +1 -1
package/dist/runner/types.js
CHANGED
|
@@ -4,5 +4,16 @@
|
|
|
4
4
|
* 本文件包含所有 Agent 相关的类型定义,从 runner/index.ts 提取。
|
|
5
5
|
* 所有下游模块应从本文件 import 类型,而非直接依赖实现。
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Multi-Agent Adapter Types
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Agent 名单单源(运行时数组 + 类型同源,二者不可能漂移)。
|
|
12
|
+
*
|
|
13
|
+
* 历史上这份名单被手抄成多份(router 的 VALID_AGENTS、bridge 的 resume.compact
|
|
14
|
+
* 校验、各 agent 的 config builder…),漂移过:dsh 加入后 bridge 的
|
|
15
|
+
* resume.compact 仍只有 5 个,带 dsh 的 resume 卡片会静默退回 defaultAgent。
|
|
16
|
+
* 新增 agent 只改这里。
|
|
17
|
+
*/
|
|
18
|
+
export const AGENT_KINDS = ['claude', 'codex', 'opencode', 'pi', 'kimi', 'dsh'];
|
|
8
19
|
//# sourceMappingURL=types.js.map
|
|
@@ -5,11 +5,22 @@ export interface SessionIndexEntry {
|
|
|
5
5
|
fingerprint: string;
|
|
6
6
|
mtimeMs: number;
|
|
7
7
|
summary: string;
|
|
8
|
+
/**
|
|
9
|
+
* cwd 判等键集合(PathKit.pathKey 归一形态,win32/darwin 大小写+分隔符
|
|
10
|
+
* 不敏感):索引的 byCwd 倒排与 cwdSet.has 查询都按这个形态比对。注意与
|
|
11
|
+
* {@link parseSessionJsonl} 返回的 raw cwdSet 区分——后者保持 JSONL 原样,
|
|
12
|
+
* 归一只发生在索引边界(见 normalizeCwdSet)。
|
|
13
|
+
*/
|
|
8
14
|
cwdSet: Set<string>;
|
|
9
15
|
}
|
|
10
16
|
export interface SessionIndexOptions {
|
|
11
17
|
/** Minimum interval between refresh scans (ms). Default 5000. */
|
|
12
18
|
refreshIntervalMs?: number;
|
|
19
|
+
/**
|
|
20
|
+
* 平台注入(测试用);默认当前宿主。cwd 判等键(pathKey)按它取归一口径,
|
|
21
|
+
* win32 语义(大小写 + `\`/`/` 分隔符不敏感)可在任意宿主上单测。
|
|
22
|
+
*/
|
|
23
|
+
platform?: NodeJS.Platform;
|
|
13
24
|
}
|
|
14
25
|
/** Claude-specific field-name mapping for content block extraction. */
|
|
15
26
|
export declare const CLAUDE_MAPPING: ContentBlockMapping;
|
|
@@ -57,6 +68,7 @@ export declare function buildFingerprint(filePath: string): string;
|
|
|
57
68
|
export declare class SessionIndex {
|
|
58
69
|
private readonly projectsDir;
|
|
59
70
|
private readonly refreshIntervalMs;
|
|
71
|
+
private readonly platform;
|
|
60
72
|
private readonly byPath;
|
|
61
73
|
private readonly bySessionId;
|
|
62
74
|
private readonly byCwd;
|
|
@@ -3,6 +3,8 @@ import path from 'node:path';
|
|
|
3
3
|
import { scanJsonlLines } from '../common/jsonl.js';
|
|
4
4
|
import { sortByRecencyDesc } from '../common/recency.js';
|
|
5
5
|
import { extractContentBlocks } from '../common/content-blocks.js';
|
|
6
|
+
import { pathKey } from '../../platform/path.js';
|
|
7
|
+
import { currentPlatform } from '../../platform/select.js';
|
|
6
8
|
import { getLogger } from '../../logger/index.js';
|
|
7
9
|
// ─── Claude content block mapping (shared with sessions.ts) ─
|
|
8
10
|
/** Claude-specific field-name mapping for content block extraction. */
|
|
@@ -115,6 +117,17 @@ export function buildFingerprint(filePath) {
|
|
|
115
117
|
return parts.join('|');
|
|
116
118
|
}
|
|
117
119
|
// ─── SessionIndex ───────────────────────────────────────────
|
|
120
|
+
/**
|
|
121
|
+
* raw cwd → pathKey 判等键。只允许在「解析结果进入索引」的边界调用:
|
|
122
|
+
* JSONL 里的 cwd 形态(大小写/分隔符/尾斜杠)由写方决定,与查询侧
|
|
123
|
+
* pathKey(cwd) 不保证逐字符一致(win32 尤甚),索引键控必须两侧同口径归一。
|
|
124
|
+
*/
|
|
125
|
+
function normalizeCwdSet(raw, platform) {
|
|
126
|
+
const out = new Set();
|
|
127
|
+
for (const cwd of raw)
|
|
128
|
+
out.add(pathKey(cwd, { platform }));
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
118
131
|
/**
|
|
119
132
|
* In-memory session index with three query views:
|
|
120
133
|
* byPath: path → entry
|
|
@@ -127,6 +140,7 @@ export function buildFingerprint(filePath) {
|
|
|
127
140
|
export class SessionIndex {
|
|
128
141
|
projectsDir;
|
|
129
142
|
refreshIntervalMs;
|
|
143
|
+
platform;
|
|
130
144
|
byPath = new Map();
|
|
131
145
|
bySessionId = new Map();
|
|
132
146
|
byCwd = new Map();
|
|
@@ -139,6 +153,7 @@ export class SessionIndex {
|
|
|
139
153
|
constructor(projectsDir, opts = {}) {
|
|
140
154
|
this.projectsDir = projectsDir;
|
|
141
155
|
this.refreshIntervalMs = opts.refreshIntervalMs ?? 5000;
|
|
156
|
+
this.platform = opts.platform ?? currentPlatform;
|
|
142
157
|
}
|
|
143
158
|
/** Whether a full scan has ever completed successfully (readdir succeeded). */
|
|
144
159
|
get isBuilt() {
|
|
@@ -162,7 +177,7 @@ export class SessionIndex {
|
|
|
162
177
|
}
|
|
163
178
|
// ── Query: listByCwd ────────────────────────────────────
|
|
164
179
|
listByCwd(cwd, opts = {}) {
|
|
165
|
-
const paths = this.byCwd.get(cwd);
|
|
180
|
+
const paths = this.byCwd.get(pathKey(cwd, { platform: this.platform }));
|
|
166
181
|
if (!paths || paths.size === 0)
|
|
167
182
|
return [];
|
|
168
183
|
// Gather entries, deduplicate by sessionId (keep mtime newest)
|
|
@@ -211,7 +226,7 @@ export class SessionIndex {
|
|
|
211
226
|
}
|
|
212
227
|
if (currentFp === entry.fingerprint) {
|
|
213
228
|
// Fingerprint matches → trust index
|
|
214
|
-
if (entry.cwdSet.has(cwd))
|
|
229
|
+
if (entry.cwdSet.has(pathKey(cwd, { platform: this.platform })))
|
|
215
230
|
return entry;
|
|
216
231
|
continue;
|
|
217
232
|
}
|
|
@@ -226,11 +241,11 @@ export class SessionIndex {
|
|
|
226
241
|
fingerprint: currentFp,
|
|
227
242
|
mtimeMs: Number(st.mtimeMs),
|
|
228
243
|
summary: parsed.summary.replace(/\s+/g, ' ').trim(),
|
|
229
|
-
cwdSet: parsed.cwdSet,
|
|
244
|
+
cwdSet: normalizeCwdSet(parsed.cwdSet, this.platform),
|
|
230
245
|
};
|
|
231
246
|
this.updateEntry(p, updated);
|
|
232
247
|
// Re-check cwd after update
|
|
233
|
-
if (updated.cwdSet.has(cwd))
|
|
248
|
+
if (updated.cwdSet.has(pathKey(cwd, { platform: this.platform })))
|
|
234
249
|
return updated;
|
|
235
250
|
}
|
|
236
251
|
catch {
|
|
@@ -304,7 +319,7 @@ export class SessionIndex {
|
|
|
304
319
|
fingerprint: fpAfter,
|
|
305
320
|
mtimeMs: Number(fileStat.mtimeMs),
|
|
306
321
|
summary: parsed.summary.replace(/\s+/g, ' ').trim(),
|
|
307
|
-
cwdSet: parsed.cwdSet,
|
|
322
|
+
cwdSet: normalizeCwdSet(parsed.cwdSet, this.platform),
|
|
308
323
|
};
|
|
309
324
|
this.updateEntry(fullPath, newEntry);
|
|
310
325
|
}
|
|
@@ -30,7 +30,7 @@ function projectDirForCwd(cwd, projectsDir) {
|
|
|
30
30
|
* to actually belong to the requested cwd. A relocated session contains
|
|
31
31
|
* MULTIPLE cwd values (pre- and post-relocate) — accept when ANY `cwd` field
|
|
32
32
|
* matches. Early-stops on the first match (findJsonlLine), keeping the
|
|
33
|
-
*
|
|
33
|
+
* single-pass parse-count anchors intact on the fallback path.
|
|
34
34
|
* Regression 2026-08-04 EnterWorktree relocate.
|
|
35
35
|
*/
|
|
36
36
|
function fileContainsCwd(filePath, cwd) {
|
|
@@ -109,8 +109,8 @@ function ensureSessionIndex(projectsDir) {
|
|
|
109
109
|
* the project directory doesn't exist or no jsonl has a matching cwd field.
|
|
110
110
|
*/
|
|
111
111
|
export function listClaudeSessions(cwd, opts = {}) {
|
|
112
|
-
// 不传 limit = 返回全集不切片;仅显式传值才 slice
|
|
113
|
-
// reader 拿全集算 total 后再自己按 offset/limit
|
|
112
|
+
// 不传 limit = 返回全集不切片;仅显式传值才 slice
|
|
113
|
+
// reader 拿全集算 total 后再自己按 offset/limit 切片。
|
|
114
114
|
const limit = opts.limit;
|
|
115
115
|
const projectsDir = opts.projectsDir ?? defaultProjectsDir();
|
|
116
116
|
const index = ensureSessionIndex(projectsDir);
|
|
@@ -218,7 +218,7 @@ export function isClaudeSessionActive(sessionId, cwd, opts = {}) {
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
|
-
*
|
|
221
|
+
* first-pass scan: stream the file once via `scanJsonlLines`
|
|
222
222
|
* (no `string[]` materialized), parse each line exactly once, and collect
|
|
223
223
|
* all metadata needed by readSessionContent (usage, tailOffset, aiTitle,
|
|
224
224
|
* recap, lastUserMessage). Does NOT retain parsed objects — the events tail
|
|
@@ -320,7 +320,7 @@ function scalarScan(filePath) {
|
|
|
320
320
|
const l = acc.last;
|
|
321
321
|
// contextLength = current context-window occupancy. Prefer compact_boundary
|
|
322
322
|
// postTokens (the precise post-compact size); otherwise fall back to the
|
|
323
|
-
// last turn's input + cacheRead + cacheCreation (
|
|
323
|
+
// last turn's input + cacheRead + cacheCreation (unified
|
|
324
324
|
// contract — excludes output/reasoning, which are generated, not part of
|
|
325
325
|
// the input window). Take the max so a pre-compact turn larger than the
|
|
326
326
|
// post-compact size is not under-reported.
|
|
@@ -353,12 +353,12 @@ function scalarScan(filePath) {
|
|
|
353
353
|
};
|
|
354
354
|
}
|
|
355
355
|
/**
|
|
356
|
-
*
|
|
356
|
+
* second-pass tail mapper: expand one parsed tail line into
|
|
357
357
|
* content-block events. Re-parses only tail lines (scanTailEvents does the
|
|
358
358
|
* offset re-read + line filtering), so both raw line-string memory and
|
|
359
|
-
* parsed-object memory are O(tail), not O(whole file)
|
|
359
|
+
* parsed-object memory are O(tail), not O(whole file) —.
|
|
360
360
|
*
|
|
361
|
-
* `maxEvents` capping (
|
|
361
|
+
* `maxEvents` capping (unified "last N events" contract across
|
|
362
362
|
* all readers) is applied by scanTailEvents.
|
|
363
363
|
*/
|
|
364
364
|
function mapClaudeTailLine(obj) {
|
|
@@ -411,7 +411,7 @@ export function readSessionContent(sessionId, cwd, opts = {}) {
|
|
|
411
411
|
return { events: [] };
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
|
-
//
|
|
414
|
+
// First pass STREAMS the file once via
|
|
415
415
|
// `scanJsonlLines` (no `string[]` materialized), parsing each line once to
|
|
416
416
|
// collect scalars (usage, tailOffset, aiTitle, recap, lastUserMessage)
|
|
417
417
|
// while retaining NO parsed objects and NO whole-file line array. The
|
|
@@ -425,7 +425,7 @@ export function readSessionContent(sessionId, cwd, opts = {}) {
|
|
|
425
425
|
// full-file scan + tail re-parse of the usually-short post-user lines).
|
|
426
426
|
// When the session has NO user message, tailOffset stays -1 and the tail
|
|
427
427
|
// IS the whole file → scan(N) + tail(N) = 2.0× — the known asymptotic
|
|
428
|
-
// upper bound of the body's two-phase design (still better than pre-
|
|
428
|
+
// upper bound of the body's two-phase design (still better than pre-'s
|
|
429
429
|
// ~3×). Total parse count adds the index-miss fallback guard's fixed
|
|
430
430
|
// early-stop overhead (~2 parses, independent of line count).
|
|
431
431
|
const scan = scalarScan(filePath);
|
|
@@ -53,10 +53,10 @@ export declare function readCodexRollout(filePath: string): CodexRolloutEntry |
|
|
|
53
53
|
* Returns `{ entries, total }`: `total` is the size of the full cwd-matched
|
|
54
54
|
* set before pagination; `entries` are the newest `limit` entries by mtime.
|
|
55
55
|
*
|
|
56
|
-
* A global order over the full set is established first (
|
|
56
|
+
* A global order over the full set is established first (no early
|
|
57
57
|
* termination before ordering) using the lightweight session index — full
|
|
58
58
|
* walk + first-line session_meta parse + stat mtime, 5s TTL cache. Only the
|
|
59
|
-
* returned page is fully parsed for summary/usage
|
|
59
|
+
* returned page is fully parsed for summary/usage.
|
|
60
60
|
*/
|
|
61
61
|
export declare function listCodexRollouts(opts?: ListCodexRolloutsOptions): {
|
|
62
62
|
entries: CodexRolloutEntry[];
|
|
@@ -65,7 +65,7 @@ export declare function listCodexRollouts(opts?: ListCodexRolloutsOptions): {
|
|
|
65
65
|
/**
|
|
66
66
|
* Read the full content of a specific session by its threadId.
|
|
67
67
|
*
|
|
68
|
-
*
|
|
68
|
+
* Uses session index for O(1) file lookup instead of walking the
|
|
69
69
|
* entire directory tree and fully parsing every file. Only the matching
|
|
70
70
|
* file is fully parsed.
|
|
71
71
|
*
|
|
@@ -94,7 +94,7 @@ export declare function readCodexSessionSummary(sessionId: string, opts?: {
|
|
|
94
94
|
/**
|
|
95
95
|
* Check if a session is still active (recently updated).
|
|
96
96
|
*
|
|
97
|
-
*
|
|
97
|
+
* Uses session index for direct file lookup, then checks mtime
|
|
98
98
|
* via statSync — no full rollout parse required. This reduces the
|
|
99
99
|
* cost from "walk entire directory + full-parse every file" to a single
|
|
100
100
|
* statSync call for the matching file.
|
|
@@ -148,7 +148,7 @@ function scanCodexRollout(filePath, includeEvents) {
|
|
|
148
148
|
// token_count 增量是 input/cached/output 全 0、只有 total_tokens 有值
|
|
149
149
|
// (窗口被摘要+replacement history 占据),现有 zero-filter 会跳过它,
|
|
150
150
|
// 因此这里必须单独捕获,不能复用 lastRawUsage。
|
|
151
|
-
//
|
|
151
|
+
// 单位口径:前后两侧统一用 total_tokens —— 压缩前取最近一次
|
|
152
152
|
// 非零 token_count 的 total_tokens(input+output,即该 turn 在窗口内的全部
|
|
153
153
|
// token),压缩后取收尾事件的 total_tokens(压缩后窗口)。不得一边 input
|
|
154
154
|
// 一边 total,否则「压缩前 X → 压缩后 Y」两边单位不一致。
|
|
@@ -279,7 +279,7 @@ function scanCodexRollout(filePath, includeEvents) {
|
|
|
279
279
|
* (display-only, never added to total).
|
|
280
280
|
* contextLength = input_tokens (= (input_tokens-cached) + cached + 0),
|
|
281
281
|
* i.e. input + cacheRead + cacheCreation — the unified context-window
|
|
282
|
-
* occupancy contract across all five readers (
|
|
282
|
+
* occupancy contract across all five readers (excludes
|
|
283
283
|
* output/reasoning).
|
|
284
284
|
*/
|
|
285
285
|
function buildUsageFromScan(scan) {
|
|
@@ -325,10 +325,10 @@ function buildUsageFromScan(scan) {
|
|
|
325
325
|
* Returns `{ entries, total }`: `total` is the size of the full cwd-matched
|
|
326
326
|
* set before pagination; `entries` are the newest `limit` entries by mtime.
|
|
327
327
|
*
|
|
328
|
-
* A global order over the full set is established first (
|
|
328
|
+
* A global order over the full set is established first (no early
|
|
329
329
|
* termination before ordering) using the lightweight session index — full
|
|
330
330
|
* walk + first-line session_meta parse + stat mtime, 5s TTL cache. Only the
|
|
331
|
-
* returned page is fully parsed for summary/usage
|
|
331
|
+
* returned page is fully parsed for summary/usage.
|
|
332
332
|
*/
|
|
333
333
|
export function listCodexRollouts(opts = {}) {
|
|
334
334
|
const codexHome = resolveCodexHome(opts.codexHome);
|
|
@@ -338,7 +338,7 @@ export function listCodexRollouts(opts = {}) {
|
|
|
338
338
|
const matched = [];
|
|
339
339
|
for (const entry of index.values()) {
|
|
340
340
|
// Subagent thread rollouts are part of a thread tree, not standalone
|
|
341
|
-
// sessions: exclude them explicitly
|
|
341
|
+
// sessions: exclude them explicitly so a pure-subagent
|
|
342
342
|
// session (main file missing) never pollutes the list or total.
|
|
343
343
|
if (entry.isSubagent)
|
|
344
344
|
continue;
|
|
@@ -361,15 +361,15 @@ export function listCodexRollouts(opts = {}) {
|
|
|
361
361
|
return { entries, total };
|
|
362
362
|
}
|
|
363
363
|
/**
|
|
364
|
-
*
|
|
364
|
+
* 单源:三处(readCodexSessionContent / readCodexSessionSummary /
|
|
365
365
|
* isCodexSessionActive)共用的「索引查找 + miss 强制刷新重查」。
|
|
366
366
|
* cwd 守卫与 subagent 过滤语义各异,留在调用方。
|
|
367
367
|
*/
|
|
368
368
|
function resolveRolloutEntry(sessionId, codexHome) {
|
|
369
|
-
//
|
|
369
|
+
// Use session index for direct file lookup
|
|
370
370
|
let index = getSessionIndex(codexHome);
|
|
371
371
|
let entry = index.get(sessionId);
|
|
372
|
-
//
|
|
372
|
+
// On miss, the index may be stale (a rollout file created after the
|
|
373
373
|
// cache was built but within the 5s TTL). Force a refresh and recheck so a
|
|
374
374
|
// brand-new session is found without waiting for TTL expiry.
|
|
375
375
|
if (!entry) {
|
|
@@ -381,7 +381,7 @@ function resolveRolloutEntry(sessionId, codexHome) {
|
|
|
381
381
|
/**
|
|
382
382
|
* Read the full content of a specific session by its threadId.
|
|
383
383
|
*
|
|
384
|
-
*
|
|
384
|
+
* Uses session index for O(1) file lookup instead of walking the
|
|
385
385
|
* entire directory tree and fully parsing every file. Only the matching
|
|
386
386
|
* file is fully parsed.
|
|
387
387
|
*
|
|
@@ -457,7 +457,7 @@ export function readCodexSessionSummary(sessionId, opts = {}) {
|
|
|
457
457
|
/**
|
|
458
458
|
* Check if a session is still active (recently updated).
|
|
459
459
|
*
|
|
460
|
-
*
|
|
460
|
+
* Uses session index for direct file lookup, then checks mtime
|
|
461
461
|
* via statSync — no full rollout parse required. This reduces the
|
|
462
462
|
* cost from "walk entire directory + full-parse every file" to a single
|
|
463
463
|
* statSync call for the matching file.
|
|
@@ -467,14 +467,14 @@ export function isCodexSessionActive(sessionId, opts = {}) {
|
|
|
467
467
|
// Unify with the other readers' 1-hour stale window (claude/pi/opencode/kimi
|
|
468
468
|
// all use STALE_MS). Previously codex used a divergent 10-minute default.
|
|
469
469
|
const threshold = opts.activeThresholdMs ?? STALE_MS;
|
|
470
|
-
//
|
|
470
|
+
// Use session index for direct file lookup + mtime check
|
|
471
471
|
const entry = resolveRolloutEntry(sessionId, codexHome);
|
|
472
472
|
if (!entry) {
|
|
473
473
|
return false;
|
|
474
474
|
}
|
|
475
475
|
// Subagent thread rollouts are never "active sessions": their mtime
|
|
476
476
|
// reflects thread-tree activity, not the parent session's liveness
|
|
477
|
-
//
|
|
477
|
+
// A pure-subagent session must report inactive.
|
|
478
478
|
if (entry.isSubagent) {
|
|
479
479
|
return false;
|
|
480
480
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Two-pass scan skeleton shared by the claude / pi session readers (
|
|
3
|
-
*
|
|
2
|
+
* Two-pass scan skeleton shared by the claude / pi session readers (
|
|
3
|
+
* lineage): pass 1 STREAMS the whole file once via `scanJsonlLines`
|
|
4
4
|
* (no `string[]` materialized), parsing each line exactly once to collect
|
|
5
5
|
* scalars (usage, tail offset, title) while retaining NO parsed objects; pass
|
|
6
6
|
* 2 re-reads ONLY the tail from the recorded offset and re-parses it for
|
|
@@ -55,13 +55,13 @@ export declare class UsageAccumulator {
|
|
|
55
55
|
*/
|
|
56
56
|
bumpCompact(): void;
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
58
|
+
/** 统一契约:上下文窗口占用 = 末轮 input + cacheRead + cacheCreation(不含 output/reasoning)。 */
|
|
59
59
|
export declare function contextWindowOccupancy(l: {
|
|
60
60
|
input: number;
|
|
61
61
|
cacheRead: number;
|
|
62
62
|
cacheCreation: number;
|
|
63
63
|
}): number;
|
|
64
|
-
/**
|
|
64
|
+
/** 统一契约:session 全量累计字段组装(4 个 reader 共用:claude/dsh/kimi/pi;
|
|
65
65
|
* codex 走 last_token_usage 口径、opencode 保留自己的条件赋值,原为逐字段复制的 4 连拷贝)。 */
|
|
66
66
|
export declare function cumulativeUsageFields(t: {
|
|
67
67
|
input: number;
|
|
@@ -69,11 +69,11 @@ export class UsageAccumulator {
|
|
|
69
69
|
this._compactCount++;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
72
|
+
/** 统一契约:上下文窗口占用 = 末轮 input + cacheRead + cacheCreation(不含 output/reasoning)。 */
|
|
73
73
|
export function contextWindowOccupancy(l) {
|
|
74
74
|
return l.input + l.cacheRead + l.cacheCreation;
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
76
|
+
/** 统一契约:session 全量累计字段组装(4 个 reader 共用:claude/dsh/kimi/pi;
|
|
77
77
|
* codex 走 last_token_usage 口径、opencode 保留自己的条件赋值,原为逐字段复制的 4 连拷贝)。 */
|
|
78
78
|
export function cumulativeUsageFields(t) {
|
|
79
79
|
return {
|
|
@@ -21,7 +21,7 @@ import { samePath } from '../../platform/path.js';
|
|
|
21
21
|
import { capEvents, paginate } from '../common/pagination.js';
|
|
22
22
|
import { truncateUtf8, TOOL_RESULT_MAX_BYTES } from '../../common/truncate.js';
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* history 拉取上限。DSH session.history 的 maxMessages 同时限制返回条数,
|
|
25
25
|
* 若用它同时充当展示截断,>50 事件的会话累计 usage 会少算。这里拉取一个足够大的
|
|
26
26
|
* 上限保证 usage 累加覆盖整个会话;展示仍由 capEvents(opts?.maxEvents ?? 50) 截断。
|
|
27
27
|
* (若 DSH 支持分页 cursor 可改成分页,当前用有界大拉取)
|
|
@@ -159,7 +159,7 @@ export class DshSessionReader {
|
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
return {
|
|
162
|
-
//
|
|
162
|
+
// 展示仍按 maxEvents 尾截断(默认 50,保持原行为),usage 已在上面基于
|
|
163
163
|
// 完整 history 累加。
|
|
164
164
|
events: capEvents(events, opts?.maxEvents ?? 50),
|
|
165
165
|
...(sessionUsage ? { usage: sessionUsage } : {}),
|
|
@@ -104,7 +104,7 @@ export declare class KimiSessionReader implements AgentSessionReader {
|
|
|
104
104
|
readCompactionRecords(sessionId: string, cwd: string): KimiCompactionRecord[];
|
|
105
105
|
/**
|
|
106
106
|
* 由 begin/terminal 记录重建压缩状态机:最后一次 begin 无更新的 terminal
|
|
107
|
-
* 记录 =
|
|
107
|
+
* 记录 = 在途。崩溃墓碑由 kimi resume
|
|
108
108
|
* 时补写 full_compaction.cancel,天然闭合。
|
|
109
109
|
*/
|
|
110
110
|
readCompactionState(sessionId: string, cwd: string): {
|
|
@@ -114,7 +114,7 @@ export declare class KimiSessionReader implements AgentSessionReader {
|
|
|
114
114
|
records: KimiCompactionRecord[];
|
|
115
115
|
};
|
|
116
116
|
/**
|
|
117
|
-
*
|
|
117
|
+
* 单源:resolveSessionWirePath 与 isSessionActive 共用的 cwd 守卫
|
|
118
118
|
* (realpathSync → state.json workDir/cwd 三态 → index workDir 兜底 →
|
|
119
119
|
* fail-closed)。返回 sessionDir;会话未知或守卫不过返回 null。
|
|
120
120
|
*/
|
|
@@ -78,7 +78,7 @@ function readJsonlLines(filePath) {
|
|
|
78
78
|
try {
|
|
79
79
|
const stat = fs.statSync(filePath);
|
|
80
80
|
const mtime = stat.mtimeMs;
|
|
81
|
-
//
|
|
81
|
+
// TTL 由 TtlCache 按缓存写入时间判定(不是文件 mtime),命中即 LRU 刷新;
|
|
82
82
|
// mtime 变化视为失效(文件被改写后不得返回旧行)。
|
|
83
83
|
const cached = jsonlCache.get(filePath);
|
|
84
84
|
if (cached && mtime === cached.mtime) {
|
|
@@ -204,7 +204,7 @@ export class KimiSessionReader {
|
|
|
204
204
|
const realCwd = fs.realpathSync(cwd);
|
|
205
205
|
const sessions = [];
|
|
206
206
|
for (const entry of this.scanSessionIndex()) {
|
|
207
|
-
//
|
|
207
|
+
// state.json 一次读取共用(cwd 兜底 + summary),I/O 减半。
|
|
208
208
|
// 两次 catch 语义保留:解析失败 = 无法判 cwd → 跳过(continue);
|
|
209
209
|
// 解析成功但字段缺失 = summary 落 wire.jsonl 提取。
|
|
210
210
|
const statePath = path.join(entry.sessionDir, 'state.json');
|
|
@@ -242,7 +242,7 @@ export class KimiSessionReader {
|
|
|
242
242
|
const wirePath = path.join(entry.sessionDir, 'agents', 'main', 'wire.jsonl');
|
|
243
243
|
summary = this.extractLastPromptFromWire(wirePath) || 'New Session';
|
|
244
244
|
}
|
|
245
|
-
// Get mtime.
|
|
245
|
+
// Get mtime. the session DIRECTORY mtime only changes when
|
|
246
246
|
// entries are created/deleted/renamed inside it — kimi appending to
|
|
247
247
|
// agents/main/wire.jsonl during a run does NOT touch it, so dir-mtime
|
|
248
248
|
// sorting lags real activity by minutes. Use the wire.jsonl mtime
|
|
@@ -332,7 +332,7 @@ export class KimiSessionReader {
|
|
|
332
332
|
cacheCreation: inputCacheCreation,
|
|
333
333
|
});
|
|
334
334
|
if (updateContextLength) {
|
|
335
|
-
//
|
|
335
|
+
// unified contract: input + cacheRead + cacheCreation
|
|
336
336
|
// (excludes output — generated, not part of the input window).
|
|
337
337
|
mainContextLength = inputOther + inputCacheRead + inputCacheCreation;
|
|
338
338
|
}
|
|
@@ -350,7 +350,7 @@ export class KimiSessionReader {
|
|
|
350
350
|
// 展示计数只计 context.apply_compaction:一次手动 /compact 会同时写
|
|
351
351
|
// full_compaction.complete + context.apply_compaction 两条(2026-08-16
|
|
352
352
|
// 实弹 wire.jsonl),双计会把一次压缩虚报成 2 次。full_compaction.complete
|
|
353
|
-
// 仅作
|
|
353
|
+
// 仅作 runCompact 轮询的完成信号(readCompactionRecords 仍返回两种)。
|
|
354
354
|
if (isCompactionComplete(entry) && entry.type === 'context.apply_compaction') {
|
|
355
355
|
compactCount++;
|
|
356
356
|
// Capture context length before this compaction as the pre-compact
|
|
@@ -360,7 +360,7 @@ export class KimiSessionReader {
|
|
|
360
360
|
}
|
|
361
361
|
continue;
|
|
362
362
|
}
|
|
363
|
-
//
|
|
363
|
+
// displayTitle + display events now scan ALL lines (not a
|
|
364
364
|
// line-slice), then `events.slice(-maxEvents)` keeps the LAST N
|
|
365
365
|
// EVENTS. Previously `lines.slice(-maxEvents)` cut raw lines, but
|
|
366
366
|
// usage.record / step.begin / turn.prompt lines produce no display
|
|
@@ -402,7 +402,7 @@ export class KimiSessionReader {
|
|
|
402
402
|
}
|
|
403
403
|
else if (evt.type === 'tool.result') {
|
|
404
404
|
// result is { output }, { output, note }, or { isError, output }
|
|
405
|
-
// 对齐 opencode reader
|
|
405
|
+
// 对齐 opencode reader 的预折叠:单条 tool_result 上限
|
|
406
406
|
// TOOL_RESULT_MAX_BYTES,病理级输出(如 51KB 文件读取)不会
|
|
407
407
|
// 撑爆 resume 卡,也约束 events[] 内存占用。
|
|
408
408
|
const rawOut = evt.result?.output ?? `[tool result for ${evt.toolCallId}]`;
|
|
@@ -523,7 +523,7 @@ export class KimiSessionReader {
|
|
|
523
523
|
}
|
|
524
524
|
/**
|
|
525
525
|
* 由 begin/terminal 记录重建压缩状态机:最后一次 begin 无更新的 terminal
|
|
526
|
-
* 记录 =
|
|
526
|
+
* 记录 = 在途。崩溃墓碑由 kimi resume
|
|
527
527
|
* 时补写 full_compaction.cancel,天然闭合。
|
|
528
528
|
*/
|
|
529
529
|
readCompactionState(sessionId, cwd) {
|
|
@@ -545,7 +545,7 @@ export class KimiSessionReader {
|
|
|
545
545
|
};
|
|
546
546
|
}
|
|
547
547
|
/**
|
|
548
|
-
*
|
|
548
|
+
* 单源:resolveSessionWirePath 与 isSessionActive 共用的 cwd 守卫
|
|
549
549
|
* (realpathSync → state.json workDir/cwd 三态 → index workDir 兜底 →
|
|
550
550
|
* fail-closed)。返回 sessionDir;会话未知或守卫不过返回 null。
|
|
551
551
|
*/
|
|
@@ -626,7 +626,7 @@ export class KimiSessionReader {
|
|
|
626
626
|
* Check if a session is currently active
|
|
627
627
|
*/
|
|
628
628
|
isSessionActive(sessionId, cwd) {
|
|
629
|
-
//
|
|
629
|
+
// cwd 守卫与 resolveSessionWirePath 共用 verifySessionCwd(单源)。
|
|
630
630
|
const verified = this.verifySessionCwd(sessionId, cwd);
|
|
631
631
|
if (!verified)
|
|
632
632
|
return false;
|
|
@@ -644,7 +644,7 @@ export class KimiSessionReader {
|
|
|
644
644
|
// Read the tail to check for active indicators. kimi appends
|
|
645
645
|
// usage.record after step.end at turn completion, so the last loop
|
|
646
646
|
// event (not the last line) decides whether the session is complete.
|
|
647
|
-
//
|
|
647
|
+
// `lastLoopEventIsStepEnd` only scans backwards up to
|
|
648
648
|
// LOOP_EVENT_SCAN_LIMIT lines, so we read only that many trailing lines
|
|
649
649
|
// via the tail-only helper instead of full-slurping the whole wire log
|
|
650
650
|
// (which can be multi-MB) into a string[].
|
|
@@ -20,7 +20,7 @@ interface OpencodeSessionReaderOptions {
|
|
|
20
20
|
/**
|
|
21
21
|
* Override the raw `opencode export <id>` capture (returns the JSON string).
|
|
22
22
|
* Default routes stdout to a temp file fd to bypass opencode's ~128KB pipe
|
|
23
|
-
* truncation on large sessions
|
|
23
|
+
* truncation on large sessions. Injectable for testing.
|
|
24
24
|
*/
|
|
25
25
|
captureExport?: (sessionId: string) => string;
|
|
26
26
|
}
|
|
@@ -65,12 +65,12 @@ export declare class OpencodeSessionReader implements AgentSessionReader {
|
|
|
65
65
|
/**
|
|
66
66
|
* Capture raw `opencode export <sessionId>` output (JSON string). Injectable
|
|
67
67
|
* via the constructor; the default routes stdout to a temp file fd to bypass
|
|
68
|
-
* opencode's ~128KB pipe truncation on large sessions
|
|
68
|
+
* opencode's ~128KB pipe truncation on large sessions.
|
|
69
69
|
*/
|
|
70
70
|
/** 导出会话内容(public:测试直接调用,替代 as unknown as)。 */
|
|
71
71
|
captureExport(sessionId: string): string;
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* default transport: run `opencode export` with stdout pointed at a
|
|
74
74
|
* regular-file fd (NOT a pipe). opencode truncates piped stdout at ~128KB for
|
|
75
75
|
* large sessions (huge tool_results), breaking JSON.parse; a file fd receives
|
|
76
76
|
* the full output (verified: 1.3MB parses OK). execFileSync returns null when
|
|
@@ -57,7 +57,7 @@ export class OpencodeSessionReader {
|
|
|
57
57
|
sessions: items.map((e) => ({
|
|
58
58
|
sessionId: e.id,
|
|
59
59
|
// CLI title 字段形状不保证恒为 string(null/缺字段时 router 的
|
|
60
|
-
// s.summary.trim() 会 TypeError)——非 string 给空串(Review
|
|
60
|
+
// s.summary.trim() 会 TypeError)——非 string 给空串(Review)。
|
|
61
61
|
summary: typeof e.title === 'string' ? e.title : '',
|
|
62
62
|
mtime: e.updated,
|
|
63
63
|
})),
|
|
@@ -75,7 +75,7 @@ export class OpencodeSessionReader {
|
|
|
75
75
|
*/
|
|
76
76
|
readSessionContent(sessionId, cwd, opts) {
|
|
77
77
|
const realCwd = this.realpath(cwd);
|
|
78
|
-
//
|
|
78
|
+
// capture raw export via the injectable seam, then parse. Distinguish
|
|
79
79
|
// a truly-missing session (empty capture) from a truncated/corrupt one
|
|
80
80
|
// (non-empty but unparseable) so we never show the misleading "未找到" for
|
|
81
81
|
// a session whose content is merely too large to pipe through stdout.
|
|
@@ -100,7 +100,9 @@ export class OpencodeSessionReader {
|
|
|
100
100
|
return { events: [] };
|
|
101
101
|
}
|
|
102
102
|
try {
|
|
103
|
-
|
|
103
|
+
// 与 listSessions 的过滤同口径(PathKit.samePath):export 里的 directory
|
|
104
|
+
// 形态由 opencode 自行记录,裸 !== 会让会话列得出却读不出(win32 常态)
|
|
105
|
+
if (!samePath(data.info.directory, realCwd)) {
|
|
104
106
|
return { events: [] };
|
|
105
107
|
}
|
|
106
108
|
const events = [];
|
|
@@ -160,7 +162,7 @@ export class OpencodeSessionReader {
|
|
|
160
162
|
const rawOut = isError
|
|
161
163
|
? (part.state.error ?? part.state.output)
|
|
162
164
|
: (part.state.output ?? '');
|
|
163
|
-
//
|
|
165
|
+
// pre-fold pathological tool_results (e.g. a 500KB file
|
|
164
166
|
// listing) via the shared truncateUtf8 primitive, reusing the
|
|
165
167
|
// same …(已截断) suffix as the live renderer / card budget.
|
|
166
168
|
events.push({
|
|
@@ -183,7 +185,7 @@ export class OpencodeSessionReader {
|
|
|
183
185
|
// ccusage-aligned: cache.write -> cacheCreationTokens.
|
|
184
186
|
// contextLength = input + cacheRead + cacheCreation (excludes
|
|
185
187
|
// output/reasoning) — the unified context-window occupancy
|
|
186
|
-
// contract across all five readers
|
|
188
|
+
// contract across all five readers.
|
|
187
189
|
if ((part.tokens.total ?? 0) !== 0) {
|
|
188
190
|
usage = {
|
|
189
191
|
inputTokens: part.tokens.input,
|
|
@@ -317,7 +319,7 @@ export class OpencodeSessionReader {
|
|
|
317
319
|
const res = spawnProcessSync(this.binary, ['session', 'list', '--format', 'json'], {
|
|
318
320
|
encoding: 'utf-8',
|
|
319
321
|
timeout: 10000,
|
|
320
|
-
//
|
|
322
|
+
// default maxBuffer is 1MiB — a large session list (>1MiB JSON)
|
|
321
323
|
// throws ENOBUFS, which the catch below silently turns into [] and the
|
|
322
324
|
// user sees "no sessions" even though the CLI returned healthy data.
|
|
323
325
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -345,7 +347,7 @@ export class OpencodeSessionReader {
|
|
|
345
347
|
return data;
|
|
346
348
|
}
|
|
347
349
|
catch (err) {
|
|
348
|
-
//
|
|
350
|
+
// 区分「读取失败」与「真空/陈旧 cwd」。
|
|
349
351
|
// - ENOENT(cwd 已删除或 binary 缺失):沿用既有契约返回 []——/resume 对
|
|
350
352
|
// 陈旧 cwd 保持优雅(sessions.test.ts「Stale-cwd coverage gap」+ round10
|
|
351
353
|
// 探针的跨 reader 空目录不变量都钉死此行为)。
|
|
@@ -362,7 +364,7 @@ export class OpencodeSessionReader {
|
|
|
362
364
|
/**
|
|
363
365
|
* Capture raw `opencode export <sessionId>` output (JSON string). Injectable
|
|
364
366
|
* via the constructor; the default routes stdout to a temp file fd to bypass
|
|
365
|
-
* opencode's ~128KB pipe truncation on large sessions
|
|
367
|
+
* opencode's ~128KB pipe truncation on large sessions.
|
|
366
368
|
*/
|
|
367
369
|
/** 导出会话内容(public:测试直接调用,替代 as unknown as)。 */
|
|
368
370
|
captureExport(sessionId) {
|
|
@@ -371,7 +373,7 @@ export class OpencodeSessionReader {
|
|
|
371
373
|
return this.captureExportViaFileFd(sessionId);
|
|
372
374
|
}
|
|
373
375
|
/**
|
|
374
|
-
*
|
|
376
|
+
* default transport: run `opencode export` with stdout pointed at a
|
|
375
377
|
* regular-file fd (NOT a pipe). opencode truncates piped stdout at ~128KB for
|
|
376
378
|
* large sessions (huge tool_results), breaking JSON.parse; a file fd receives
|
|
377
379
|
* the full output (verified: 1.3MB parses OK). execFileSync returns null when
|
|
@@ -386,7 +388,7 @@ export class OpencodeSessionReader {
|
|
|
386
388
|
encoding: 'utf-8',
|
|
387
389
|
timeout: 30000,
|
|
388
390
|
stdio: ['ignore', fd, 'pipe'],
|
|
389
|
-
//
|
|
391
|
+
// stderr is piped here; without maxBuffer a chatty CLI (>1MiB
|
|
390
392
|
// stderr) would throw ENOBUFS and lose the export. Keep the default
|
|
391
393
|
// stdout file-fd transport unaffected by raising the pipe cap.
|
|
392
394
|
maxBuffer: 64 * 1024 * 1024,
|