lark-remote 0.5.19 → 0.5.21
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 +29 -0
- package/dist/bridge/approval-actions.d.ts +1 -1
- package/dist/bridge/approval-actions.js +1 -1
- package/dist/bridge/bash-run.d.ts +71 -0
- package/dist/bridge/bash-run.js +217 -0
- package/dist/bridge/compact.d.ts +155 -0
- package/dist/bridge/compact.js +450 -0
- package/dist/bridge/final-usage.js +4 -4
- package/dist/bridge/inbound-media.js +72 -46
- package/dist/bridge/index.d.ts +106 -153
- package/dist/bridge/index.js +400 -1000
- package/dist/bridge/job-watch.d.ts +68 -0
- package/dist/bridge/job-watch.js +146 -0
- package/dist/bridge/queue-manager.d.ts +3 -3
- package/dist/bridge/queue-manager.js +10 -29
- package/dist/card/approval-render.d.ts +2 -2
- package/dist/card/approval-render.js +141 -130
- package/dist/card/card-budget.js +123 -98
- package/dist/card/card-helpers.d.ts +3 -3
- package/dist/card/card-helpers.js +6 -6
- package/dist/card/final-answer-file.js +1 -1
- package/dist/card/run-renderer.d.ts +3 -3
- package/dist/card/run-renderer.js +114 -159
- package/dist/card/run-state.d.ts +1 -15
- package/dist/card/run-state.js +358 -292
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +21 -17
- package/dist/card/usage-format.d.ts +22 -7
- package/dist/card/usage-format.js +119 -100
- package/dist/cli.js +15 -3
- package/dist/clone.js +2 -2
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/config/codex-config.js +130 -106
- package/dist/config/index.d.ts +16 -2
- package/dist/config/index.js +23 -8
- package/dist/connector/index.js +6 -6
- package/dist/error-classification.js +3 -2
- package/dist/inbound/placeholder.d.ts +1 -1
- package/dist/inbound/placeholder.js +1 -1
- package/dist/index.js +387 -281
- package/dist/logger/index.js +6 -6
- package/dist/message-handlers.d.ts +0 -5
- package/dist/message-handlers.js +254 -237
- package/dist/model-catalog.d.ts +2 -2
- package/dist/model-catalog.js +3 -8
- package/dist/platform/identity.js +1 -1
- package/dist/platform/path.d.ts +1 -1
- package/dist/platform/path.js +1 -1
- package/dist/platform/sleep-blocker.js +1 -1
- package/dist/platform/terminator-win32.js +2 -1
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +190 -152
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/config/dsh.d.ts +4 -8
- package/dist/router/config/dsh.js +3 -10
- package/dist/router/config/kimi.js +1 -1
- package/dist/router/config/types.d.ts +1 -1
- package/dist/router/config-diff.js +1 -1
- package/dist/router/index.d.ts +56 -11
- package/dist/router/index.js +401 -391
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +6 -4
- package/dist/runner/codex/app-server/runner.d.ts +9 -2
- package/dist/runner/codex/app-server/runner.js +30 -25
- package/dist/runner/codex/app-server/translator.d.ts +2 -5
- package/dist/runner/codex/app-server/translator.js +291 -238
- package/dist/runner/common/acp/acp-approval.d.ts +1 -1
- package/dist/runner/common/acp/acp-approval.js +1 -1
- package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
- package/dist/runner/common/acp/base-acp-runner.js +26 -65
- package/dist/runner/common/acp/base-translator.d.ts +12 -2
- package/dist/runner/common/acp/base-translator.js +50 -26
- package/dist/runner/common/acp/protocol-types.d.ts +1 -1
- package/dist/runner/common/acp/protocol-types.js +2 -2
- package/dist/runner/common/connection-based-runner.d.ts +32 -1
- package/dist/runner/common/connection-based-runner.js +32 -2
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
- package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
- package/dist/runner/common/spawning-runner.d.ts +6 -1
- package/dist/runner/common/spawning-runner.js +9 -4
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/catalog-probe.d.ts +17 -0
- package/dist/runner/dsh/catalog-probe.js +10 -0
- package/dist/runner/dsh/client.d.ts +7 -11
- package/dist/runner/dsh/client.js +356 -289
- package/dist/runner/dsh/exit-cancel.d.ts +2 -2
- package/dist/runner/dsh/exit-cancel.js +2 -2
- package/dist/runner/dsh/inflight.d.ts +1 -1
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +44 -4
- package/dist/runner/dsh/runner.js +296 -199
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +1 -2
- package/dist/runner/kimi/acp/runner.js +82 -46
- package/dist/runner/kimi/acp/translator.d.ts +1 -1
- package/dist/runner/kimi/acp/translator.js +3 -4
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +4 -4
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
- package/dist/runner/pi/rpc/protocol-types.js +1 -1
- package/dist/runner/pi/rpc/runner.d.ts +3 -0
- package/dist/runner/pi/rpc/runner.js +27 -42
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/runner/types.d.ts +2 -2
- package/dist/runner/types.js +1 -1
- package/dist/session/claude/sessions.d.ts +45 -0
- package/dist/session/claude/sessions.js +160 -122
- package/dist/session/codex/rollout-reader.js +181 -182
- package/dist/session/common/throw-session-list-error.d.ts +10 -0
- package/dist/session/common/throw-session-list-error.js +16 -0
- package/dist/session/dsh/sessions.js +291 -235
- package/dist/session/kimi/sessions.d.ts +40 -8
- package/dist/session/kimi/sessions.js +373 -301
- package/dist/session/opencode/sessions.d.ts +142 -1
- package/dist/session/opencode/sessions.js +378 -321
- package/dist/session/pi/sessions.js +4 -5
- package/dist/session/session-store.d.ts +9 -1
- package/dist/session/session-store.js +99 -99
- package/dist/update/version-check.js +2 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.js +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { samePath, encodeProjectDirName } from '../../platform/path.js';
|
|
|
7
7
|
import { extractContentBlocks } from '../common/content-blocks.js';
|
|
8
8
|
import { scanJsonlOnce, tailOffsetAfter, scanTailEvents } from '../common/two-pass.js';
|
|
9
9
|
import { getLogger } from '../../logger/index.js';
|
|
10
|
+
import { logNonEnoentDebug } from '../session-store.js';
|
|
10
11
|
import { UsageAccumulator, contextWindowOccupancy, cumulativeUsageFields, } from '../common/usage-accumulator.js';
|
|
11
12
|
import { SessionIndex, extractText, isTaskNotificationText, CLAUDE_MAPPING, } from './session-index.js';
|
|
12
13
|
/**
|
|
@@ -19,7 +20,7 @@ import { SessionIndex, extractText, isTaskNotificationText, CLAUDE_MAPPING, } fr
|
|
|
19
20
|
* Note: this encoding is LOSSY (`my_data/foo` and `my-data/foo` both encode to
|
|
20
21
|
* `my-data-foo`). It is only safe to use the result to **locate** files; never
|
|
21
22
|
* to **decode** a directory name back to a cwd. The cwd must always be read
|
|
22
|
-
* from the JSONL content (regression
|
|
23
|
+
* from the JSONL content (regression: /resume & /cd 路径错乱).
|
|
23
24
|
*/
|
|
24
25
|
function projectDirForCwd(cwd, projectsDir) {
|
|
25
26
|
return path.join(projectsDir, encodeProjectDirName(cwd));
|
|
@@ -32,7 +33,7 @@ function projectDirForCwd(cwd, projectsDir) {
|
|
|
32
33
|
* MULTIPLE cwd values (pre- and post-relocate) — accept when ANY `cwd` field
|
|
33
34
|
* matches. Early-stops on the first match (findJsonlLine), keeping the
|
|
34
35
|
* single-pass parse-count anchors intact on the fallback path.
|
|
35
|
-
*
|
|
36
|
+
* Needed for EnterWorktree relocate.
|
|
36
37
|
*/
|
|
37
38
|
function fileContainsCwd(filePath, cwd) {
|
|
38
39
|
return (findJsonlLine(filePath, (l) => {
|
|
@@ -50,7 +51,7 @@ function defaultProjectsDir() {
|
|
|
50
51
|
return path.join(os.homedir(), '.claude', 'projects');
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
|
-
* EnterWorktree relocate support
|
|
54
|
+
* EnterWorktree relocate support: Claude Code MOVES the
|
|
54
55
|
* transcript file to the new cwd's project dir mid-session, so the file may
|
|
55
56
|
* not exist under the requested cwd's encoded dir. Fall back to locating
|
|
56
57
|
* `<sessionId>.jsonl` across all project dirs. Deterministic: entries are
|
|
@@ -148,7 +149,7 @@ export function getNewestSession(cwd, opts = {}) {
|
|
|
148
149
|
* JSONL output continuously. A file whose mtime is older than
|
|
149
150
|
* `STALE_MS` cannot be from a running process. This catches the
|
|
150
151
|
* common Claude CLI 2.x case where the tail is `last-prompt` / `mode`
|
|
151
|
-
* (no `type:"result"`) and the process exited long ago
|
|
152
|
+
* (no `type:"result"`) and the process exited long ago.
|
|
152
153
|
*
|
|
153
154
|
* 2. **last-line terminal marker**: if the last JSON line is `result`
|
|
154
155
|
* (older CLI) or any explicit terminal event, the session is done
|
|
@@ -176,7 +177,7 @@ function hasRecentFileActivity(filePath, mtimeMs, now = Date.now()) {
|
|
|
176
177
|
// Explicit terminal markers — regardless of mtime. Claude CLI 2.x uses
|
|
177
178
|
// `last-prompt` / `mode` (no `result` event) to mark turn end, so a
|
|
178
179
|
// 24-minute-old session with a last-prompt tail is already done
|
|
179
|
-
// (
|
|
180
|
+
// (previously falsely reported as active).
|
|
180
181
|
if (typeof obj.type === 'string' && CLAUDE_TERMINAL_TYPES.has(obj.type))
|
|
181
182
|
return false;
|
|
182
183
|
}
|
|
@@ -190,9 +191,7 @@ function hasRecentFileActivity(filePath, mtimeMs, now = Date.now()) {
|
|
|
190
191
|
* 否则「文件不可读」会被当成「会话不活跃」。
|
|
191
192
|
*/
|
|
192
193
|
function logStatFailure(filePath, err) {
|
|
193
|
-
|
|
194
|
-
return;
|
|
195
|
-
getLogger().debug(`[claude-session-reader] stat ${filePath} failed: ${err?.message ?? String(err)}`);
|
|
194
|
+
logNonEnoentDebug(err, (m) => `[claude-session-reader] stat ${filePath} failed: ${m}`);
|
|
196
195
|
}
|
|
197
196
|
/**
|
|
198
197
|
* Check if a Claude session (by sessionId + cwd) is still active.
|
|
@@ -273,115 +272,42 @@ function isRealUserPrompt(obj) {
|
|
|
273
272
|
return true;
|
|
274
273
|
}
|
|
275
274
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
275
|
+
* 两条采样逐字段取 max(纯函数;UsageAccumulator 的 per-field max 语义)。
|
|
276
|
+
*
|
|
277
|
+
* RED LINE(token 口径单源的一部分):一条 streaming assistant 消息跨多行
|
|
278
|
+
* jsonl、共享同一 message id;部分后端(第三方网关,如 DeepSeek)在早期占位
|
|
279
|
+
* 行(空 thinking 块)写全零 usage,真实用量只在带 stop_reason 的末行。
|
|
280
|
+
* first-occurrence-wins 会把零记录钉死、丢掉真实用量(regression 2026-08-19:
|
|
281
|
+
* auto-resume 卡 per-run 统计全 0)。逐字段 max 与行序无关地重建完整用量;
|
|
282
|
+
* 官方 API 每行重复相同 usage,max 是 no-op。
|
|
283
|
+
*
|
|
284
|
+
* 产生:合并后的一条采样。依赖:入参。不改任何外部状态。
|
|
282
285
|
*/
|
|
283
|
-
function
|
|
286
|
+
export function maxUsageRecord(prev, usage, turnSeq) {
|
|
287
|
+
return {
|
|
288
|
+
input: Math.max(prev?.input ?? 0, usage.input_tokens ?? 0),
|
|
289
|
+
output: Math.max(prev?.output ?? 0, usage.output_tokens ?? 0),
|
|
290
|
+
cacheRead: Math.max(prev?.cacheRead ?? 0, usage.cache_read_input_tokens ?? 0),
|
|
291
|
+
cacheCreation: Math.max(prev?.cacheCreation ?? 0, usage.cache_creation_input_tokens ?? 0),
|
|
292
|
+
turn: prev?.turn ?? turnSeq,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* usage 装配(纯函数):每个 message id 一条最优记录、按首次出现顺序喂
|
|
297
|
+
* UsageAccumulator(Map 保序),acc.last = 末条 message 的完整用量,totals
|
|
298
|
+
* 让每条 API 响应恰好多计一次。per-run flow 只取末个真人 turn 的记录——
|
|
299
|
+
* 工具往返的 turn 有多条 assistant 消息(一次模型请求一条),只取 `last`
|
|
300
|
+
* 会低报。
|
|
301
|
+
*
|
|
302
|
+
* 产生:AgentSessionUsage。依赖:入参 records + 标量。不改任何外部状态
|
|
303
|
+
* (UsageAccumulator 在函数内新建)。
|
|
304
|
+
*/
|
|
305
|
+
export function buildClaudeUsage(records, opts) {
|
|
284
306
|
const acc = new UsageAccumulator();
|
|
285
|
-
// Per-message-id best usage record (per-field max). A streaming assistant
|
|
286
|
-
// message spans multiple jsonl lines sharing the same message id; some
|
|
287
|
-
// backends (third-party gateways, e.g. DeepSeek) write all-zero usage on
|
|
288
|
-
// the early placeholder lines (empty thinking block) and the real usage
|
|
289
|
-
// only on the final line carrying stop_reason. First-occurrence-wins dedup
|
|
290
|
-
// would pin the zero record and discard the real one (regression
|
|
291
|
-
// 2026-08-19: auto-resume card per-run stats all 0). Per-field max
|
|
292
|
-
// reconstructs the full usage regardless of line order — on the official
|
|
293
|
-
// API every line repeats identical usage, so max is a no-op there.
|
|
294
|
-
const usageByMessageId = new Map();
|
|
295
|
-
let lastPostTokens = 0;
|
|
296
|
-
let tailOffset = -1;
|
|
297
|
-
/** 真人 prompt 序号;末个 turn = 当前值。sidechain/注入/工具结果不递增。 */
|
|
298
|
-
let turnSeq = 0;
|
|
299
|
-
let lastAiTitle;
|
|
300
|
-
let lastRecap;
|
|
301
|
-
let lastUserMessage;
|
|
302
|
-
scanJsonlOnce(filePath, (obj, line, offset) => {
|
|
303
|
-
// --- usage aggregation (was aggregateSessionUsage) ---
|
|
304
|
-
if (obj.type === 'assistant') {
|
|
305
|
-
const msg = obj.message;
|
|
306
|
-
const usage = msg?.usage;
|
|
307
|
-
if (usage) {
|
|
308
|
-
const msgId = msg?.id;
|
|
309
|
-
if (msgId) {
|
|
310
|
-
const prev = usageByMessageId.get(msgId);
|
|
311
|
-
usageByMessageId.set(msgId, {
|
|
312
|
-
input: Math.max(prev?.input ?? 0, usage.input_tokens ?? 0),
|
|
313
|
-
output: Math.max(prev?.output ?? 0, usage.output_tokens ?? 0),
|
|
314
|
-
cacheRead: Math.max(prev?.cacheRead ?? 0, usage.cache_read_input_tokens ?? 0),
|
|
315
|
-
cacheCreation: Math.max(prev?.cacheCreation ?? 0, usage.cache_creation_input_tokens ?? 0),
|
|
316
|
-
turn: prev?.turn ?? turnSeq,
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (obj.type === 'system' && obj.subtype === 'compact_boundary') {
|
|
322
|
-
acc.bumpCompact();
|
|
323
|
-
const cm = obj.compactMetadata;
|
|
324
|
-
if (cm?.postTokens) {
|
|
325
|
-
lastPostTokens = cm.postTokens;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
// --- last user message offset ---
|
|
329
|
-
// Record the byte offset where the tail should start: the beginning of
|
|
330
|
-
// the NEXT line after this user message (see tailOffsetAfter).
|
|
331
|
-
if (obj.type === 'user') {
|
|
332
|
-
const msg = obj.message;
|
|
333
|
-
if (msg?.role === 'user') {
|
|
334
|
-
tailOffset = tailOffsetAfter(line, offset);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
// --- ai-title extraction ---
|
|
338
|
-
if (obj.type === 'ai-title' && typeof obj.aiTitle === 'string') {
|
|
339
|
-
lastAiTitle = obj.aiTitle;
|
|
340
|
-
}
|
|
341
|
-
// --- recap extraction (last isCompactSummary wins) ---
|
|
342
|
-
if (obj.type === 'user' && obj.isCompactSummary === true) {
|
|
343
|
-
const msg = obj.message;
|
|
344
|
-
if (msg?.content) {
|
|
345
|
-
const recapContent = extractText(msg.content);
|
|
346
|
-
if (recapContent) {
|
|
347
|
-
lastRecap = recapContent;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
// --- last user message for displayTitle ---
|
|
352
|
-
// Skip: 1) tool_result content, 2) isCompactSummary (recap),
|
|
353
|
-
// 3) isMeta (skill injection / command echo / project rules)
|
|
354
|
-
if (obj.type === 'user' && !obj.isCompactSummary && !obj.isMeta) {
|
|
355
|
-
const msg = obj.message;
|
|
356
|
-
if (msg?.role === 'user' && msg.content) {
|
|
357
|
-
const content = msg.content;
|
|
358
|
-
const isToolResult = Array.isArray(content) &&
|
|
359
|
-
content.length > 0 &&
|
|
360
|
-
content[0]?.type === 'tool_result';
|
|
361
|
-
if (!isToolResult) {
|
|
362
|
-
const userContent = extractText(content);
|
|
363
|
-
if (userContent && !isTaskNotificationText(userContent)) {
|
|
364
|
-
lastUserMessage = userContent;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
// --- turn boundary for per-run usage ---
|
|
370
|
-
// 真人 prompt 开启新 turn;注入/工具结果/sidechain 不递增。usage 记录在
|
|
371
|
-
// 解析到 assistant 行时已经带上当前 turnSeq,末尾只汇总末个 turn。
|
|
372
|
-
if (isRealUserPrompt(obj))
|
|
373
|
-
turnSeq++;
|
|
374
|
-
});
|
|
375
|
-
// Feed one best record per message id, in first-appearance order (Map
|
|
376
|
-
// preserves insertion order), so acc.last is the LAST message's full usage
|
|
377
|
-
// and totals sum each API response exactly once. Per-run flow is only the
|
|
378
|
-
// records belonging to the last real-user turn — a tool-looping turn has
|
|
379
|
-
// several assistant messages (one per model request), so `last` alone
|
|
380
|
-
// under-reports it.
|
|
381
307
|
const turn = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
382
|
-
for (const record of
|
|
308
|
+
for (const record of records) {
|
|
383
309
|
acc.add(record);
|
|
384
|
-
if (turnSeq > 0 && record.turn === turnSeq) {
|
|
310
|
+
if (opts.turnSeq > 0 && record.turn === opts.turnSeq) {
|
|
385
311
|
turn.input += record.input;
|
|
386
312
|
turn.output += record.output;
|
|
387
313
|
turn.cacheRead += record.cacheRead;
|
|
@@ -393,7 +319,7 @@ function scalarScan(filePath) {
|
|
|
393
319
|
const l = acc.last;
|
|
394
320
|
// 没有真人 user prompt(旧数据/截断文件)→ 无法划分 turn,回退末条采样,
|
|
395
321
|
// 与 dsh/codex「无 turn 标记退回末条」一致;不能把整份文件当成一轮。
|
|
396
|
-
if (turnSeq === 0 && l) {
|
|
322
|
+
if (opts.turnSeq === 0 && l) {
|
|
397
323
|
turn.input = l.input;
|
|
398
324
|
turn.output = l.output;
|
|
399
325
|
turn.cacheRead = l.cacheRead;
|
|
@@ -406,7 +332,7 @@ function scalarScan(filePath) {
|
|
|
406
332
|
// the input window). Take the max so a pre-compact turn larger than the
|
|
407
333
|
// post-compact size is not under-reported.
|
|
408
334
|
const lastWindow = l ? contextWindowOccupancy(l) : 0;
|
|
409
|
-
let contextLength = lastPostTokens;
|
|
335
|
+
let contextLength = opts.lastPostTokens;
|
|
410
336
|
if (contextLength === 0 || lastWindow > contextLength) {
|
|
411
337
|
contextLength = lastWindow;
|
|
412
338
|
}
|
|
@@ -415,22 +341,134 @@ function scalarScan(filePath) {
|
|
|
415
341
|
// `total_token_usage`=session 累计)——否则 jsonl 兜底路径会把 session 累计
|
|
416
342
|
// 当成"本 run"显示,与卡片"本 run ≤ 累计"不变量冲突(累计反而更小)。
|
|
417
343
|
const lastTotal = turn.input + turn.output + turn.cacheRead + turn.cacheCreation;
|
|
418
|
-
|
|
344
|
+
return {
|
|
419
345
|
inputTokens: turn.input,
|
|
420
346
|
outputTokens: turn.output,
|
|
421
347
|
contextLength,
|
|
422
|
-
compactCount:
|
|
348
|
+
compactCount: opts.compactCount,
|
|
423
349
|
cacheReadTokens: turn.cacheRead > 0 ? turn.cacheRead : undefined,
|
|
424
350
|
cacheCreationTokens: turn.cacheCreation > 0 ? turn.cacheCreation : undefined,
|
|
425
351
|
totalTokens: lastTotal,
|
|
426
352
|
...cumulativeUsageFields(t),
|
|
427
353
|
};
|
|
354
|
+
}
|
|
355
|
+
function newScalarScanState() {
|
|
356
|
+
// Per-message-id best usage record (per-field max). A streaming assistant
|
|
357
|
+
// message spans multiple jsonl lines sharing the same message id; some
|
|
358
|
+
// backends (third-party gateways, e.g. DeepSeek) write all-zero usage on
|
|
359
|
+
// the early placeholder lines (empty thinking block) and the real usage
|
|
360
|
+
// only on the final line carrying stop_reason. First-occurrence-wins dedup
|
|
361
|
+
// would pin the zero record and discard the real one (auto-resume card
|
|
362
|
+
// per-run stats all 0). Per-field max reconstructs the full usage
|
|
363
|
+
// regardless of line order — on the official API every line repeats
|
|
364
|
+
// identical usage, so max is a no-op there.
|
|
428
365
|
return {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
366
|
+
usageByMessageId: new Map(),
|
|
367
|
+
lastPostTokens: 0,
|
|
368
|
+
compactCount: 0,
|
|
369
|
+
tailOffset: -1,
|
|
370
|
+
turnSeq: 0,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/** assistant 行 → 按 message id 逐字段 max 合并 usage(was aggregateSessionUsage)。 */
|
|
374
|
+
function collectAssistantUsage(state, obj) {
|
|
375
|
+
if (obj.type !== 'assistant')
|
|
376
|
+
return;
|
|
377
|
+
const msg = obj.message;
|
|
378
|
+
const usage = msg?.usage;
|
|
379
|
+
if (!usage)
|
|
380
|
+
return;
|
|
381
|
+
const msgId = msg?.id;
|
|
382
|
+
if (!msgId)
|
|
383
|
+
return;
|
|
384
|
+
state.usageByMessageId.set(msgId, maxUsageRecord(state.usageByMessageId.get(msgId), usage, state.turnSeq));
|
|
385
|
+
}
|
|
386
|
+
/** compact_boundary → 计数 + 压缩后 token 水位。 */
|
|
387
|
+
function collectCompactBoundary(state, obj) {
|
|
388
|
+
if (obj.type !== 'system' || obj.subtype !== 'compact_boundary')
|
|
389
|
+
return;
|
|
390
|
+
state.compactCount++;
|
|
391
|
+
const cm = obj.compactMetadata;
|
|
392
|
+
if (cm?.postTokens) {
|
|
393
|
+
state.lastPostTokens = cm.postTokens;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/** 记录 tail 起点:本 user 行之后的下一行字节偏移(见 tailOffsetAfter)。 */
|
|
397
|
+
function collectTailOffset(state, obj, line, offset) {
|
|
398
|
+
if (obj.type !== 'user')
|
|
399
|
+
return;
|
|
400
|
+
const msg = obj.message;
|
|
401
|
+
if (msg?.role === 'user') {
|
|
402
|
+
state.tailOffset = tailOffsetAfter(line, offset);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/** ai-title:最后一条生效。 */
|
|
406
|
+
function collectAiTitle(state, obj) {
|
|
407
|
+
if (obj.type === 'ai-title' && typeof obj.aiTitle === 'string') {
|
|
408
|
+
state.lastAiTitle = obj.aiTitle;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/** recap:最后一条 isCompactSummary 生效。 */
|
|
412
|
+
function collectRecap(state, obj) {
|
|
413
|
+
if (obj.type !== 'user' || obj.isCompactSummary !== true)
|
|
414
|
+
return;
|
|
415
|
+
const msg = obj.message;
|
|
416
|
+
if (!msg?.content)
|
|
417
|
+
return;
|
|
418
|
+
const recapContent = extractText(msg.content);
|
|
419
|
+
if (recapContent) {
|
|
420
|
+
state.lastRecap = recapContent;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/** displayTitle 用的最近真人输入:跳过 tool_result / recap / isMeta / 任务通知。 */
|
|
424
|
+
function collectLastUserMessage(state, obj) {
|
|
425
|
+
if (obj.type !== 'user' || obj.isCompactSummary || obj.isMeta)
|
|
426
|
+
return;
|
|
427
|
+
const msg = obj.message;
|
|
428
|
+
if (msg?.role !== 'user' || !msg.content)
|
|
429
|
+
return;
|
|
430
|
+
const content = msg.content;
|
|
431
|
+
const isToolResult = Array.isArray(content) &&
|
|
432
|
+
content.length > 0 &&
|
|
433
|
+
content[0]?.type === 'tool_result';
|
|
434
|
+
if (isToolResult)
|
|
435
|
+
return;
|
|
436
|
+
const userContent = extractText(content);
|
|
437
|
+
if (userContent && !isTaskNotificationText(userContent)) {
|
|
438
|
+
state.lastUserMessage = userContent;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/** 真人 prompt 开启新 turn;注入/工具结果/sidechain 不递增。 */
|
|
442
|
+
function countRealUserPrompt(state, obj) {
|
|
443
|
+
if (isRealUserPrompt(obj))
|
|
444
|
+
state.turnSeq++;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* 单行分派:收集器调用顺序与原回调逐字一致(尤其 turnSeq 在最后递增,
|
|
448
|
+
* 保证 assistant usage 记录归属的是「本行之前」的 turn)。
|
|
449
|
+
*/
|
|
450
|
+
function scanScalarLine(state, obj, line, offset) {
|
|
451
|
+
collectAssistantUsage(state, obj);
|
|
452
|
+
collectCompactBoundary(state, obj);
|
|
453
|
+
collectTailOffset(state, obj, line, offset);
|
|
454
|
+
collectAiTitle(state, obj);
|
|
455
|
+
collectRecap(state, obj);
|
|
456
|
+
collectLastUserMessage(state, obj);
|
|
457
|
+
countRealUserPrompt(state, obj);
|
|
458
|
+
}
|
|
459
|
+
function scalarScan(filePath) {
|
|
460
|
+
const state = newScalarScanState();
|
|
461
|
+
scanJsonlOnce(filePath, scanScalarLine.bind(null, state));
|
|
462
|
+
return {
|
|
463
|
+
tailOffset: state.tailOffset,
|
|
464
|
+
usage: buildClaudeUsage(state.usageByMessageId.values(), {
|
|
465
|
+
turnSeq: state.turnSeq,
|
|
466
|
+
lastPostTokens: state.lastPostTokens,
|
|
467
|
+
compactCount: state.compactCount,
|
|
468
|
+
}),
|
|
469
|
+
aiTitle: state.lastAiTitle,
|
|
470
|
+
recap: state.lastRecap,
|
|
471
|
+
lastUserMessage: state.lastUserMessage,
|
|
434
472
|
};
|
|
435
473
|
}
|
|
436
474
|
/**
|