lark-remote 0.4.5 → 0.5.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 +18 -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 +9 -9
- package/dist/bridge/index.js +41 -41
- package/dist/bridge/queue-manager.d.ts +12 -12
- package/dist/bridge/queue-manager.js +19 -19
- 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/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 +9 -9
- 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 +9 -9
- package/dist/router/index.js +34 -34
- 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.js +3 -3
- 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 +1 -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
|
@@ -163,7 +163,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
163
163
|
*/
|
|
164
164
|
terminator = createTerminator({ graceMs: 2_000, agent: 'kimi' });
|
|
165
165
|
/**
|
|
166
|
-
* shell seam:terminal/create
|
|
166
|
+
* shell seam:terminal/create 的可执行解析走这里。kimi 的
|
|
167
167
|
* ACP 服务端固定要 `bash -c`,win32 上没有 Git Bash 时必须在 seam 里变成
|
|
168
168
|
* 明确错误,而不是一个语焉不详的 spawn ENOENT。
|
|
169
169
|
*/
|
|
@@ -224,7 +224,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
224
224
|
return new KimiAcpTranslator();
|
|
225
225
|
}
|
|
226
226
|
async applyTurnSettings(client, sessionId) {
|
|
227
|
-
//
|
|
227
|
+
// 下发配置的模型(provider/model)。session/new|resume 只带 cwd/mcpServers,
|
|
228
228
|
// 不带 model;不主动下发则实际跑 kimi 服务端默认模型。
|
|
229
229
|
// 仿 opencode 用 session/set_config_option。失败仅告警不阻断。
|
|
230
230
|
if (this.model) {
|
|
@@ -239,7 +239,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
239
239
|
getLogger().warn(`[${this.logTag}] set_config_option model failed (non-fatal): ${err.message}`);
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
//
|
|
242
|
+
// 下发配置的思考强度。configId 与取值不是猜的——kimi 真机(0.43.1)
|
|
243
243
|
// `session/new` 就通告 `{id:'thinking', category:'thought_level',
|
|
244
244
|
// options:[low|high|max]}`,set 成功后响应里 `currentValue` 变成新值;
|
|
245
245
|
// 未知 configId / 非法取值回 -32602(不静默接受),所以失败告警即可判定。
|
|
@@ -283,7 +283,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
283
283
|
* before producing the result. There is NO total timeout — silence beyond
|
|
284
284
|
* compactIdleTimeoutMs yields 'unknown' (never a fake success). Keeps the
|
|
285
285
|
* connection alive while the background compaction finishes — otherwise
|
|
286
|
-
* dispose/exit kills it and the record never lands (
|
|
286
|
+
* dispose/exit kills it and the record never lands (打回复现 /
|
|
287
287
|
* 2026-08-31 事故).
|
|
288
288
|
*/
|
|
289
289
|
compactBaseline(sessionId, opts) {
|
|
@@ -300,7 +300,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
300
300
|
const baselineTerminalCount = this.compactBaselineRecords.filter(isCompactionTerminalLike).length;
|
|
301
301
|
let promptRejected = false;
|
|
302
302
|
// prompt 结算只供两样东西:成功 result 的翻译素材(stopReason/usage)与 §5.4
|
|
303
|
-
//
|
|
303
|
+
// 探针。终态判定不再挂在它后面。
|
|
304
304
|
const promptSettled = promptPromise.then((result) => {
|
|
305
305
|
this.promptSettled = true;
|
|
306
306
|
if (compactionReader) {
|
|
@@ -326,7 +326,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
326
326
|
});
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
|
-
//
|
|
329
|
+
// 语义等待在 prompt 触发的那一刻武装。引擎静默超过 compactIdleTimeoutMs
|
|
330
330
|
// 就给出 compact 语义终态,不等 session/prompt 结算——否则挂住的 prompt 会把
|
|
331
331
|
// 用户拖到通用 turnIdleTimeout(默认 30min),拿到的还是一个语义泛化的 turn
|
|
332
332
|
// 超时,而不是「压缩状态未知 / 可重试」。
|
|
@@ -372,7 +372,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
372
372
|
};
|
|
373
373
|
}
|
|
374
374
|
/**
|
|
375
|
-
* Hot-apply a permission-mode change to the live ACP session
|
|
375
|
+
* Hot-apply a permission-mode change to the live ACP session.
|
|
376
376
|
*
|
|
377
377
|
* The local cache is always updated so `getStatusInfo()` (and therefore
|
|
378
378
|
* `/s`) reflects the new mode immediately. When a session is connected,
|
|
@@ -553,14 +553,14 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
553
553
|
: undefined;
|
|
554
554
|
const cwd = params.cwd ?? this.activeCwd ?? undefined;
|
|
555
555
|
const outputLimit = params.outputByteLimit ?? TERMINAL_DEFAULT_OUTPUT_LIMIT;
|
|
556
|
-
// 走 shell seam
|
|
556
|
+
// 走 shell seam:argv 逐 token 保留,只把「可执行文件在哪」
|
|
557
557
|
// 收口——win32 上 Git Bash 缺失时在这里抛 ShellUnavailableError,由
|
|
558
558
|
// handleTerminalRequest 转成明确的 Terminal request failed 回执,而不是一个
|
|
559
559
|
// 语焉不详的 spawn ENOENT。
|
|
560
560
|
const proc = this.shell.spawnArgv(params.command, params.args ?? [], {
|
|
561
561
|
cwd,
|
|
562
562
|
options: {
|
|
563
|
-
// env 覆盖大小写不敏感合并(win32 PATH/Path
|
|
563
|
+
// env 覆盖大小写不敏感合并(win32 PATH/Path 双键防护)
|
|
564
564
|
env: env ? mergeProcessEnv(process.env, env) : undefined,
|
|
565
565
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
566
566
|
// Terminator 用负 PID 杀进程组(kill(-pgid));子进程必须是组长
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* accumulates text/thinking deltas and emits turn_diff snapshots (NOT
|
|
9
9
|
* assistant deltas — see handleAgentMessageChunk), plus question elicitation.
|
|
10
10
|
*
|
|
11
|
-
* Wire envelope (
|
|
11
|
+
* Wire envelope (source: kimi-code packages/acp-server/src/events-map.ts,
|
|
12
12
|
* verified live 2026-08-15/16 against kimi 0.36.0):
|
|
13
13
|
* session/update params = {sessionId, update: {sessionUpdate: '<kind>', ...}}
|
|
14
14
|
* — the discriminator is `update.sessionUpdate`, NOT a nested `event.type`.
|
|
15
15
|
*
|
|
16
|
-
* Event mapping
|
|
16
|
+
* Event mapping:
|
|
17
17
|
* agent_message_chunk → turn_diff text snapshot (content.text accumulation)
|
|
18
18
|
* agent_thought_chunk → turn_diff reasoning snapshot (content.text accumulation)
|
|
19
19
|
* tool_call / tool_call_update / usage_update / plan / control-plane noise →
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* accumulates text/thinking deltas and emits turn_diff snapshots (NOT
|
|
9
9
|
* assistant deltas — see handleAgentMessageChunk), plus question elicitation.
|
|
10
10
|
*
|
|
11
|
-
* Wire envelope (
|
|
11
|
+
* Wire envelope (source: kimi-code packages/acp-server/src/events-map.ts,
|
|
12
12
|
* verified live 2026-08-15/16 against kimi 0.36.0):
|
|
13
13
|
* session/update params = {sessionId, update: {sessionUpdate: '<kind>', ...}}
|
|
14
14
|
* — the discriminator is `update.sessionUpdate`, NOT a nested `event.type`.
|
|
15
15
|
*
|
|
16
|
-
* Event mapping
|
|
16
|
+
* Event mapping:
|
|
17
17
|
* agent_message_chunk → turn_diff text snapshot (content.text accumulation)
|
|
18
18
|
* agent_thought_chunk → turn_diff reasoning snapshot (content.text accumulation)
|
|
19
19
|
* tool_call / tool_call_update / usage_update / plan / control-plane noise →
|
|
@@ -53,7 +53,7 @@ export class KimiAcpTranslator extends BaseAcpTranslator {
|
|
|
53
53
|
// Use a stable key for the current text stream. Kimi ACP doesn't
|
|
54
54
|
// provide an itemId in agent_message_chunk; we use 'text' as the
|
|
55
55
|
// single logical text accumulator (one text stream per turn).
|
|
56
|
-
//
|
|
56
|
+
// wire chunk text lives at content.text and is a DELTA
|
|
57
57
|
// (kimi-code events-map.ts assistantDeltaToSessionUpdate:
|
|
58
58
|
// content.text = event.delta), so we accumulate here into a full-text
|
|
59
59
|
// snapshot and emit it as turn_diff. The card reducer's turn_diff path
|
|
@@ -145,7 +145,7 @@ export class KimiAcpTranslator extends BaseAcpTranslator {
|
|
|
145
145
|
: summary
|
|
146
146
|
? truncateWithEllipsis(summary, 200)
|
|
147
147
|
: undefined,
|
|
148
|
-
//
|
|
148
|
+
// 从服务端 options kind 派生——带 approve_always(或 allow_always)
|
|
149
149
|
// 才提供「本会话总是允许」(acceptForSession);否则与旧行为一致。
|
|
150
150
|
availableDecisions: deriveAcpAvailableDecisions(params.options ?? []),
|
|
151
151
|
};
|
|
@@ -51,7 +51,7 @@ export interface OpencodeAcpRunnerOptions {
|
|
|
51
51
|
errorMonitorPollIntervalMs?: number;
|
|
52
52
|
}
|
|
53
53
|
export declare class OpencodeAcpRunner extends BaseAcpRunner<OpencodeAcpTranslator> {
|
|
54
|
-
/** Configured session mode applied via session/set_mode
|
|
54
|
+
/** Configured session mode applied via session/set_mode. */
|
|
55
55
|
private configuredMode;
|
|
56
56
|
/** Current session mode id, parsed from session/new|resume configOptions. */
|
|
57
57
|
private currentModeId?;
|
|
@@ -86,7 +86,7 @@ export declare class OpencodeAcpRunner extends BaseAcpRunner<OpencodeAcpTranslat
|
|
|
86
86
|
protected buildApprovalOutcome(action: string, pending: AcpPendingApproval, _response: unknown): unknown;
|
|
87
87
|
getStatusInfo(): AgentStatusInfo;
|
|
88
88
|
/**
|
|
89
|
-
* Hot-apply a mode change to the live ACP session
|
|
89
|
+
* Hot-apply a mode change to the live ACP session.
|
|
90
90
|
*
|
|
91
91
|
* opencode's session/set_mode sends no notification — the client refreshes
|
|
92
92
|
* the local mode view itself. The local mode is always updated so
|
|
@@ -30,7 +30,7 @@ import { OpencodeLogErrorMonitor, resolveOpencodeLogPath } from './error-monitor
|
|
|
30
30
|
// Runner
|
|
31
31
|
// =============================================================================
|
|
32
32
|
export class OpencodeAcpRunner extends BaseAcpRunner {
|
|
33
|
-
/** Configured session mode applied via session/set_mode
|
|
33
|
+
/** Configured session mode applied via session/set_mode. */
|
|
34
34
|
configuredMode;
|
|
35
35
|
/** Current session mode id, parsed from session/new|resume configOptions. */
|
|
36
36
|
currentModeId;
|
|
@@ -103,7 +103,7 @@ export class OpencodeAcpRunner extends BaseAcpRunner {
|
|
|
103
103
|
return new OpencodeAcpTranslator();
|
|
104
104
|
}
|
|
105
105
|
async applyTurnSettings(client, sessionId) {
|
|
106
|
-
//
|
|
106
|
+
// apply the configured mode (build/plan). If the session already
|
|
107
107
|
// runs it (from session/new|resume configOptions), skip the wire call;
|
|
108
108
|
// otherwise send session/set_mode and refresh the local view (opencode
|
|
109
109
|
// sends no notification for set_mode).
|
|
@@ -160,7 +160,7 @@ export class OpencodeAcpRunner extends BaseAcpRunner {
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
/**
|
|
163
|
-
* Hot-apply a mode change to the live ACP session
|
|
163
|
+
* Hot-apply a mode change to the live ACP session.
|
|
164
164
|
*
|
|
165
165
|
* opencode's session/set_mode sends no notification — the client refreshes
|
|
166
166
|
* the local mode view itself. The local mode is always updated so
|
|
@@ -176,7 +176,7 @@ export class OpencodeAcpTranslator extends BaseAcpTranslator {
|
|
|
176
176
|
kind: 'command',
|
|
177
177
|
command: params.toolCall.title ?? undefined,
|
|
178
178
|
reason: rawInput ? truncateWithEllipsis(stringify(rawInput), 200) : undefined,
|
|
179
|
-
//
|
|
179
|
+
// 从服务端 options kind 派生——带 allow_always(或 approve_always)
|
|
180
180
|
// 才提供「本会话总是允许」(acceptForSession)。
|
|
181
181
|
availableDecisions: deriveAcpAvailableDecisions(params.options ?? []),
|
|
182
182
|
};
|
|
@@ -53,7 +53,7 @@ export class PiRpcRunner extends ConnectionBasedRunner {
|
|
|
53
53
|
idleTtlMs: opts.idleTtlMs,
|
|
54
54
|
logTag: 'pi-rpc-connection-manager',
|
|
55
55
|
});
|
|
56
|
-
//
|
|
56
|
+
// 协议停止通道:pi 的 cancel 是 `{type:'abort'}` 通知,已由
|
|
57
57
|
// cancelCurrentTurn 实现;登记后 win32 优雅停止才有通道可用(否则一路树杀)。
|
|
58
58
|
this.manager.stopper = ({ client }) => this.buildCooperativeStop(client);
|
|
59
59
|
}
|
|
@@ -139,7 +139,7 @@ export class PiRpcRunner extends ConnectionBasedRunner {
|
|
|
139
139
|
this.promptSettled = false;
|
|
140
140
|
const response = await client.request({ type: 'prompt', message });
|
|
141
141
|
if (!response.success) {
|
|
142
|
-
//
|
|
142
|
+
// pi 返回 success:false(无效模型/provider 错误/忙)时立即失败,
|
|
143
143
|
// 不能当成成功 ACK 继续等 agent_settled(否则最长 turn idle timeout)。
|
|
144
144
|
throw new Error(`pi prompt failed: ${response.error ?? 'unknown error'}`);
|
|
145
145
|
}
|
|
@@ -32,7 +32,7 @@ export declare class PiRpcTranslator {
|
|
|
32
32
|
private accReasoning;
|
|
33
33
|
private lastTotalTokens;
|
|
34
34
|
private hasUsage;
|
|
35
|
-
/** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use
|
|
35
|
+
/** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use。 */
|
|
36
36
|
private pendingToolCall;
|
|
37
37
|
/** Provider failure (stopReason="error") on the final assistant message. */
|
|
38
38
|
private lastAssistantStopReason;
|
|
@@ -23,7 +23,7 @@ export class PiRpcTranslator {
|
|
|
23
23
|
accReasoning = 0;
|
|
24
24
|
lastTotalTokens;
|
|
25
25
|
hasUsage = false;
|
|
26
|
-
/** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use
|
|
26
|
+
/** toolcall_start bookkeeping:占位不发事件,end 时补发完整 tool_use。 */
|
|
27
27
|
pendingToolCall = false;
|
|
28
28
|
/** Provider failure (stopReason="error") on the final assistant message. */
|
|
29
29
|
lastAssistantStopReason;
|
|
@@ -98,7 +98,7 @@ export class PiRpcTranslator {
|
|
|
98
98
|
return [];
|
|
99
99
|
}
|
|
100
100
|
case 'compaction_start': {
|
|
101
|
-
// 信息保真
|
|
101
|
+
// 信息保真 压缩过程用户可见(info 通知),不再静默。
|
|
102
102
|
const c = evt;
|
|
103
103
|
return [
|
|
104
104
|
{
|
|
@@ -125,7 +125,7 @@ export class PiRpcTranslator {
|
|
|
125
125
|
}
|
|
126
126
|
default:
|
|
127
127
|
// turn_start/turn_end, agent_start/agent_end, tool_execution_*, and
|
|
128
|
-
// unknown types are ignored. (compaction_start/end
|
|
128
|
+
// unknown types are ignored. (compaction_start/end 映射为
|
|
129
129
|
// notice 事件,不再静默。)
|
|
130
130
|
return [];
|
|
131
131
|
}
|
|
@@ -269,7 +269,7 @@ export class PiRpcTranslator {
|
|
|
269
269
|
}
|
|
270
270
|
break;
|
|
271
271
|
case 'toolcall_start':
|
|
272
|
-
// 信息保真
|
|
272
|
+
// 信息保真 不再 push 空壳 tool_use(空 id/name 会被渲染成
|
|
273
273
|
// 无意义工具块)。只保留 bookkeeping,toolcall_end 补发完整事件。
|
|
274
274
|
this.pendingToolCall = true;
|
|
275
275
|
break;
|
package/dist/runner/types.d.ts
CHANGED
|
@@ -395,7 +395,7 @@ export interface Runner {
|
|
|
395
395
|
killOrphan(): void;
|
|
396
396
|
registerExitHandlers(): void;
|
|
397
397
|
/**
|
|
398
|
-
* Remove the runner from the process-level exit dispatcher
|
|
398
|
+
* Remove the runner from the process-level exit dispatcher. Called by
|
|
399
399
|
* the bridge when the (cwd, kind) cache slot is evicted so the instance is
|
|
400
400
|
* not retained by the singleton dispatcher. Optional so test stubs that only
|
|
401
401
|
* satisfy `registerExitHandlers` remain structurally valid.
|
|
@@ -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 } : {}),
|