lark-remote 0.5.20 → 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 +10 -0
- package/dist/bridge/inbound-media.js +71 -45
- package/dist/bridge/index.d.ts +9 -0
- package/dist/bridge/index.js +230 -203
- package/dist/card/approval-render.js +32 -33
- package/dist/card/card-budget.js +123 -98
- package/dist/card/run-renderer.js +82 -68
- package/dist/card/run-state.d.ts +0 -14
- package/dist/card/run-state.js +335 -207
- package/dist/card/tool-render.js +20 -16
- package/dist/card/usage-format.d.ts +12 -9
- package/dist/card/usage-format.js +111 -97
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/index.js +226 -190
- package/dist/message-handlers.js +253 -236
- 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 +189 -133
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/index.js +192 -201
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +287 -221
- package/dist/runner/common/acp/base-translator.js +19 -17
- 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/spawning-runner.d.ts +5 -0
- package/dist/runner/common/spawning-runner.js +8 -3
- 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/client.d.ts +7 -9
- package/dist/runner/dsh/client.js +356 -287
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +4 -3
- package/dist/runner/dsh/runner.js +88 -64
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/kimi/acp/runner.js +81 -44
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/session/claude/sessions.js +113 -101
- package/dist/session/codex/rollout-reader.js +179 -180
- package/dist/session/dsh/sessions.js +281 -231
- package/dist/session/kimi/sessions.d.ts +0 -7
- package/dist/session/kimi/sessions.js +129 -109
- package/dist/session/opencode/sessions.d.ts +0 -11
- package/dist/session/opencode/sessions.js +161 -137
- package/dist/session/session-store.d.ts +8 -0
- package/dist/session/session-store.js +98 -98
- package/package.json +1 -1
|
@@ -32,6 +32,49 @@ import { summarizeToolInput } from '../../common/tool-summary.js';
|
|
|
32
32
|
* (若 DSH 支持分页 cursor 可改成分页,当前用有界大拉取)
|
|
33
33
|
*/
|
|
34
34
|
const DSH_HISTORY_FETCH_LIMIT = 10_000;
|
|
35
|
+
/** curl 传输层失败(spawn error / 非零退出)→ 统一错误;成功返回 undefined。 */
|
|
36
|
+
function dshSpawnFailure(prefix, res) {
|
|
37
|
+
if (res.error || res.status !== 0) {
|
|
38
|
+
return new Error(`${prefix}: ${(res.error?.message ?? res.stderr) || 'unknown'}`);
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
/** 从 curl stdout 拆出末尾 HTTP 状态码与 body(`--write-out \n%{http_code}`)。 */
|
|
43
|
+
function splitDshHttpResponse(raw) {
|
|
44
|
+
const statusMatch = /\n(\d{3})$/.exec(raw);
|
|
45
|
+
const httpStatus = statusMatch ? Number(statusMatch[1]) : undefined;
|
|
46
|
+
const bodyText = statusMatch ? raw.slice(0, statusMatch.index) : raw;
|
|
47
|
+
return { httpStatus, bodyText };
|
|
48
|
+
}
|
|
49
|
+
/** 解析 result 信封;非 JSON / ok!==true 按原文案抛错。 */
|
|
50
|
+
function parseDshResult(method, bodyText) {
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = JSON.parse(bodyText);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
throw new Error(`DSH ${method} returned non-JSON: ${err.message}`, {
|
|
57
|
+
cause: err,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (parsed.result?.ok !== true) {
|
|
61
|
+
throw new Error(parsed.result?.error?.message ?? `DSH ${method} failed`);
|
|
62
|
+
}
|
|
63
|
+
return parsed.result.value;
|
|
64
|
+
}
|
|
65
|
+
/** 从 `-D -` 响应头里取第一条 set-cookie 的 name=value 段。 */
|
|
66
|
+
function firstSetCookie(header) {
|
|
67
|
+
const match = header.match(/^set-cookie:\s*(.+)$/im);
|
|
68
|
+
return match?.[1]?.split(';')[0]?.trim();
|
|
69
|
+
}
|
|
70
|
+
/** 重试仍失败:二次错为 cause,首错(cookie 失效)作为根因写进 message。 */
|
|
71
|
+
function cookieRefreshFailure(method, first, second) {
|
|
72
|
+
const firstMsg = first?.message ?? String(first);
|
|
73
|
+
const secondMsg = second?.message ?? String(second);
|
|
74
|
+
// 重试仍失败:首错(cookie 失效)才是根因。`cause` 必须是本次捕获的 err
|
|
75
|
+
// (lint preserve-caught-error),所以根因写进 message 一并留痕。
|
|
76
|
+
return new Error(`DSH ${method} failed after cookie refresh: ${secondMsg} (root cause: ${firstMsg})`, { cause: second });
|
|
77
|
+
}
|
|
35
78
|
/** Synchronous unary POST via curl (the reader interface is synchronous). */
|
|
36
79
|
function defaultSyncRequest(baseUrl, method, payload, cookie) {
|
|
37
80
|
const body = JSON.stringify({
|
|
@@ -54,40 +97,23 @@ function defaultSyncRequest(baseUrl, method, payload, cookie) {
|
|
|
54
97
|
'--write-out',
|
|
55
98
|
'\n%{http_code}',
|
|
56
99
|
], { encoding: 'utf-8', timeout: 15_000, maxBuffer: 64 * 1024 * 1024 });
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
const statusMatch = /\n(\d{3})$/.exec(raw);
|
|
62
|
-
const httpStatus = statusMatch ? Number(statusMatch[1]) : undefined;
|
|
63
|
-
const bodyText = statusMatch ? raw.slice(0, statusMatch.index) : raw;
|
|
100
|
+
const failure = dshSpawnFailure(`DSH ${method} failed`, res);
|
|
101
|
+
if (failure)
|
|
102
|
+
throw failure;
|
|
103
|
+
const { httpStatus, bodyText } = splitDshHttpResponse(String(res.stdout));
|
|
64
104
|
if (httpStatus === 401 || httpStatus === 403) {
|
|
65
105
|
throw new Error(`DSH ${method} unauthorized (HTTP ${httpStatus})`);
|
|
66
106
|
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
parsed = JSON.parse(bodyText);
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
throw new Error(`DSH ${method} returned non-JSON: ${err.message}`, {
|
|
73
|
-
cause: err,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
if (parsed.result?.ok !== true) {
|
|
77
|
-
throw new Error(parsed.result?.error?.message ?? `DSH ${method} failed`);
|
|
78
|
-
}
|
|
79
|
-
return parsed.result.value;
|
|
107
|
+
return parseDshResult(method, bodyText);
|
|
80
108
|
}
|
|
81
109
|
/** Synchronous index bootstrap; returns the v1 `name=value` cookie pair. */
|
|
82
110
|
function defaultSyncConnect(baseUrl, token) {
|
|
83
111
|
const url = `${baseUrl}/?token=${encodeURIComponent(token)}`;
|
|
84
112
|
const res = spawnProcessSync('curl', ['-sS', '-D', '-', '-o', isWin32() ? 'NUL' : '/dev/null', '--max-time', '5', url], { encoding: 'utf-8', timeout: 10_000, maxBuffer: 1024 * 1024 });
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const match = header.match(/^set-cookie:\s*(.+)$/im);
|
|
90
|
-
const cookie = match?.[1]?.split(';')[0]?.trim();
|
|
113
|
+
const failure = dshSpawnFailure('DSH auth failed', res);
|
|
114
|
+
if (failure)
|
|
115
|
+
throw failure;
|
|
116
|
+
const cookie = firstSetCookie(String(res.stdout));
|
|
91
117
|
if (!cookie)
|
|
92
118
|
throw new Error('DSH auth failed: index handshake did not return a cookie');
|
|
93
119
|
return cookie;
|
|
@@ -149,11 +175,7 @@ export class DshSessionReader {
|
|
|
149
175
|
return this.syncRequest(baseUrl, method, payload, this.resolveCookie());
|
|
150
176
|
}
|
|
151
177
|
catch (second) {
|
|
152
|
-
|
|
153
|
-
// (lint preserve-caught-error),所以根因写进 message 一并留痕。
|
|
154
|
-
const firstMsg = first?.message ?? String(first);
|
|
155
|
-
const secondMsg = second?.message ?? String(second);
|
|
156
|
-
throw new Error(`DSH ${method} failed after cookie refresh: ${secondMsg} (root cause: ${firstMsg})`, { cause: second });
|
|
178
|
+
throw cookieRefreshFailure(method, first, second);
|
|
157
179
|
}
|
|
158
180
|
}
|
|
159
181
|
}
|
|
@@ -241,125 +263,7 @@ export class DshSessionReader {
|
|
|
241
263
|
throughSeq,
|
|
242
264
|
maxMessages: DSH_HISTORY_FETCH_LIMIT,
|
|
243
265
|
});
|
|
244
|
-
|
|
245
|
-
let displayTitle;
|
|
246
|
-
let cumInput = 0;
|
|
247
|
-
let cumOutput = 0;
|
|
248
|
-
let cumCacheRead = 0;
|
|
249
|
-
let cumCacheWrite = 0;
|
|
250
|
-
let lastUsage;
|
|
251
|
-
// 末个 turn 内各采样用量之和("本 run / 末轮" scope)。DSH 每次模型调用
|
|
252
|
-
// 一条 usage,工具往返的 turn 会写多条 assistant/message;只取末条会把
|
|
253
|
-
// 多步 run 低报(实测 231/689 显示成整轮真实消耗)。turn 边界取
|
|
254
|
-
// turn/start–turn/end,与 codex `task_started` 汇总同一口径(§9.21)。
|
|
255
|
-
// null = 历史里没有 turn 标记(旧数据/截断)→ 退回末条 usage 语义。
|
|
256
|
-
let turnUsage = null;
|
|
257
|
-
let lastTurnUsage = null;
|
|
258
|
-
/** 等结果的工具调用(见 MappedEvent.pendingTool)。 */
|
|
259
|
-
let pendingTool;
|
|
260
|
-
for (const entry of value.records ?? []) {
|
|
261
|
-
const ev = entry.event;
|
|
262
|
-
if (!ev)
|
|
263
|
-
continue;
|
|
264
|
-
if (ev.type === 'turn/start') {
|
|
265
|
-
turnUsage = emptyUsageTotals();
|
|
266
|
-
}
|
|
267
|
-
else if (ev.type === 'turn/end') {
|
|
268
|
-
if (turnUsage)
|
|
269
|
-
lastTurnUsage = turnUsage;
|
|
270
|
-
turnUsage = null;
|
|
271
|
-
}
|
|
272
|
-
const contentEvents = mapEvent(ev);
|
|
273
|
-
if (contentEvents.pendingTool) {
|
|
274
|
-
// 未完成的工具调用最多同时一个(DSH 按 step 串行执行;并行调用时按
|
|
275
|
-
// 到达顺序配对,最坏情况是两次调用共用一个面板,不会丢信息)。
|
|
276
|
-
if (pendingTool)
|
|
277
|
-
events.push(pendingTool);
|
|
278
|
-
pendingTool = contentEvents.pendingTool;
|
|
279
|
-
}
|
|
280
|
-
else if (ev.type === 'tool/result' && pendingTool && contentEvents.events[0]) {
|
|
281
|
-
const result = contentEvents.events[0];
|
|
282
|
-
// 标题沿用调用的 `🔧 name — summary`,失败改成 ❌(折叠态一眼看出结果)。
|
|
283
|
-
const failed = result.title?.startsWith('🔴') === true;
|
|
284
|
-
events.push({
|
|
285
|
-
type: 'tool_use',
|
|
286
|
-
title: failed
|
|
287
|
-
? (pendingTool.title ?? '❌ 工具').replace('🔧', '❌')
|
|
288
|
-
: pendingTool.title,
|
|
289
|
-
content: result.content,
|
|
290
|
-
timestamp: pendingTool.timestamp,
|
|
291
|
-
});
|
|
292
|
-
pendingTool = undefined;
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
events.push(...contentEvents.events);
|
|
296
|
-
}
|
|
297
|
-
if (contentEvents.title)
|
|
298
|
-
displayTitle = contentEvents.title;
|
|
299
|
-
const u = contentEvents.usage;
|
|
300
|
-
if (u) {
|
|
301
|
-
// Cumulative (session-wide): sum across ALL assistant/message usage.
|
|
302
|
-
cumInput += u.inputTokens ?? 0;
|
|
303
|
-
cumOutput += u.outputTokens ?? 0;
|
|
304
|
-
cumCacheRead += u.cacheReadTokens ?? 0;
|
|
305
|
-
cumCacheWrite += u.cacheWriteTokens ?? 0;
|
|
306
|
-
// 本 run 的 flow 字段 = 末个 turn 的采样之和;contextLength 用末次采样
|
|
307
|
-
// 的窗口占用(input + cacheRead + cacheWrite,不含 output)。
|
|
308
|
-
if (turnUsage) {
|
|
309
|
-
turnUsage.input += u.inputTokens ?? 0;
|
|
310
|
-
turnUsage.output += u.outputTokens ?? 0;
|
|
311
|
-
turnUsage.cacheRead += u.cacheReadTokens ?? 0;
|
|
312
|
-
turnUsage.cacheWrite += u.cacheWriteTokens ?? 0;
|
|
313
|
-
}
|
|
314
|
-
lastUsage = u;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
let sessionUsage;
|
|
318
|
-
if (lastUsage) {
|
|
319
|
-
const input = lastUsage.inputTokens ?? 0;
|
|
320
|
-
const cacheRead = lastUsage.cacheReadTokens ?? 0;
|
|
321
|
-
const cacheWrite = lastUsage.cacheWriteTokens ?? 0;
|
|
322
|
-
// 在途 turn(turn/start 无 turn/end,run 进行中读历史)的采样优先于
|
|
323
|
-
// 上一轮:旧实现只认已结算的 lastTurnUsage,mid-run 读取会把当前 turn
|
|
324
|
-
// 已消耗的量显示成上一个完整 turn 的数字。
|
|
325
|
-
const flow = turnUsage ??
|
|
326
|
-
lastTurnUsage ?? {
|
|
327
|
-
input,
|
|
328
|
-
output: lastUsage.outputTokens ?? 0,
|
|
329
|
-
cacheRead,
|
|
330
|
-
cacheWrite,
|
|
331
|
-
};
|
|
332
|
-
sessionUsage = {
|
|
333
|
-
inputTokens: flow.input,
|
|
334
|
-
outputTokens: flow.output,
|
|
335
|
-
// §9.21 口径:context 是末次采样的窗口水位(input + 末次 output +
|
|
336
|
-
// cache),不把整 turn 的 output 求和(前几步 output 已进入后续 input)。
|
|
337
|
-
contextLength: input + (lastUsage.outputTokens ?? 0) + cacheRead + cacheWrite,
|
|
338
|
-
cacheReadTokens: flow.cacheRead,
|
|
339
|
-
cacheCreationTokens: flow.cacheWrite,
|
|
340
|
-
totalTokens: flow.input + flow.output + flow.cacheRead + flow.cacheWrite,
|
|
341
|
-
...cumulativeUsageFields({
|
|
342
|
-
input: cumInput,
|
|
343
|
-
output: cumOutput,
|
|
344
|
-
cacheRead: cumCacheRead,
|
|
345
|
-
cacheCreation: cumCacheWrite,
|
|
346
|
-
}),
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
// 中断/仍在执行的调用没有结果:把它当独立事件留下,别让这一条消失。
|
|
350
|
-
if (pendingTool)
|
|
351
|
-
events.push(pendingTool);
|
|
352
|
-
// 头部信息口径:最近输入优先用服务端末轮 prompt(事件里可能只是当前 turn
|
|
353
|
-
// 的一部分),标题用服务端 title;两者都缺时保持原行为。
|
|
354
|
-
const headerLastInput = preview.lastUserMessage ?? displayTitle;
|
|
355
|
-
return {
|
|
356
|
-
// 展示仍按 maxEvents 尾截断(默认 50,保持原行为),usage 已在上面基于
|
|
357
|
-
// 完整 history 累加。
|
|
358
|
-
events: capEvents(events, opts?.maxEvents ?? 50),
|
|
359
|
-
...(sessionUsage ? { usage: sessionUsage } : {}),
|
|
360
|
-
...(headerLastInput ? { displayTitle: headerLastInput } : {}),
|
|
361
|
-
...(preview.title ? { aiTitle: preview.title } : {}),
|
|
362
|
-
};
|
|
266
|
+
return buildDshSessionContent(value, preview, opts);
|
|
363
267
|
}
|
|
364
268
|
catch (error) {
|
|
365
269
|
// 降级为空卡(与 claude/pi 同口径),但完整错误对象入日志保留 stack,
|
|
@@ -508,97 +412,107 @@ function asBlocks(value) {
|
|
|
508
412
|
return [];
|
|
509
413
|
return value.filter((b) => !!b && typeof b === 'object');
|
|
510
414
|
}
|
|
511
|
-
function
|
|
415
|
+
function mapUserMessage(ev) {
|
|
416
|
+
const out = { events: [] };
|
|
417
|
+
const content = asBlocks(ev.data.content);
|
|
418
|
+
const text = content
|
|
419
|
+
.filter((c) => c.type === 'text')
|
|
420
|
+
.map((c) => c.text ?? '')
|
|
421
|
+
.join('');
|
|
422
|
+
if (!text.trim())
|
|
423
|
+
return out;
|
|
424
|
+
// DSH 把上下文注入也写成 user/message,靠 `source.kind` 区分。真实会话里
|
|
425
|
+
// 30 条 user/message 中只有 8 条是用户本人说的(其余是 time-context /
|
|
426
|
+
// agent-instructions / skill-catalog / goal / tool-jobs…)——照单全收会让
|
|
427
|
+
// 回顾卡里塞满"Time sampled while preparing turn…",还把系统提示标成 👤 你。
|
|
428
|
+
const source = ev.data.source;
|
|
429
|
+
const kind = typeof source?.kind === 'string' ? source.kind : undefined;
|
|
430
|
+
if (kind && CONTEXT_INJECTION_KINDS.has(kind))
|
|
431
|
+
return out;
|
|
432
|
+
const isNotice = kind !== undefined && kind !== 'user';
|
|
433
|
+
out.events.push({
|
|
434
|
+
type: isNotice ? 'notice' : 'user',
|
|
435
|
+
...(isNotice ? { title: '🔔 通知' } : {}),
|
|
436
|
+
content: isNotice ? truncateUtf8(text.trim(), NOTICE_MAX_BYTES) : text,
|
|
437
|
+
timestamp: safeEventTimestamp(ev.time),
|
|
438
|
+
});
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
function mapAssistantMessage(ev) {
|
|
512
442
|
const out = { events: [] };
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
// agent-instructions / skill-catalog / goal / tool-jobs…)——照单全收会让
|
|
525
|
-
// 回顾卡里塞满"Time sampled while preparing turn…",还把系统提示标成 👤 你。
|
|
526
|
-
const source = ev.data.source;
|
|
527
|
-
const kind = typeof source?.kind === 'string' ? source.kind : undefined;
|
|
528
|
-
if (kind && CONTEXT_INJECTION_KINDS.has(kind))
|
|
529
|
-
break;
|
|
530
|
-
const isNotice = kind !== undefined && kind !== 'user';
|
|
443
|
+
const content = asBlocks(ev.data.message?.content);
|
|
444
|
+
const timestamp = safeEventTimestamp(ev.time);
|
|
445
|
+
// DSH 的块类型是 text / reasoning / tool-call(不是 Anthropic 的
|
|
446
|
+
// thinking)。只认 text 会让 resume 卡"只有工具调用、没有思考与输出"
|
|
447
|
+
// (一个 31 turn 的会话里 reasoning 块 371 个、text 12 个,
|
|
448
|
+
// 全被丢掉)。tool-call 块不在这里取——独立的 `tool/call` 事件带完整参数。
|
|
449
|
+
for (const block of content) {
|
|
450
|
+
const text = typeof block.text === 'string' ? block.text.trim() : '';
|
|
451
|
+
if (!text)
|
|
452
|
+
continue;
|
|
453
|
+
if (block.type === 'text') {
|
|
531
454
|
out.events.push({
|
|
532
|
-
type:
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
timestamp: safeEventTimestamp(ev.time),
|
|
455
|
+
type: 'assistant',
|
|
456
|
+
content: truncateUtf8(text, TEXT_MAX_BYTES),
|
|
457
|
+
timestamp,
|
|
536
458
|
});
|
|
537
|
-
break;
|
|
538
|
-
}
|
|
539
|
-
case 'assistant/message': {
|
|
540
|
-
const content = asBlocks(ev.data.message?.content);
|
|
541
|
-
const timestamp = safeEventTimestamp(ev.time);
|
|
542
|
-
// DSH 的块类型是 text / reasoning / tool-call(不是 Anthropic 的
|
|
543
|
-
// thinking)。只认 text 会让 resume 卡"只有工具调用、没有思考与输出"
|
|
544
|
-
// (一个 31 turn 的会话里 reasoning 块 371 个、text 12 个,
|
|
545
|
-
// 全被丢掉)。tool-call 块不在这里取——独立的 `tool/call` 事件带完整参数。
|
|
546
|
-
for (const block of content) {
|
|
547
|
-
const text = typeof block.text === 'string' ? block.text.trim() : '';
|
|
548
|
-
if (!text)
|
|
549
|
-
continue;
|
|
550
|
-
if (block.type === 'text') {
|
|
551
|
-
out.events.push({
|
|
552
|
-
type: 'assistant',
|
|
553
|
-
content: truncateUtf8(text, TEXT_MAX_BYTES),
|
|
554
|
-
timestamp,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
else if (block.type === 'reasoning') {
|
|
558
|
-
out.events.push({
|
|
559
|
-
type: 'thinking',
|
|
560
|
-
// 思考是过程信息:resume 卡里留头几百字足够判断"当时在想什么"。
|
|
561
|
-
content: truncateUtf8(text, REASONING_MAX_BYTES),
|
|
562
|
-
timestamp,
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
const usage = ev.data.usage;
|
|
567
|
-
if (usage && typeof usage === 'object')
|
|
568
|
-
out.usage = usage;
|
|
569
|
-
break;
|
|
570
459
|
}
|
|
571
|
-
|
|
572
|
-
// 调用本身先不产出事件:DSH 的 tool/call 与 tool/result 是一对,resume 卡
|
|
573
|
-
// 里合成一个面板(`🔧 bash — cmd` + 输出),而不是两个各占一屏。
|
|
574
|
-
const name = String(ev.data.name ?? '');
|
|
575
|
-
const args = typeof ev.data.arguments === 'string' ? ev.data.arguments : '';
|
|
576
|
-
const summary = summarizeToolInput(name, args);
|
|
577
|
-
out.pendingTool = {
|
|
578
|
-
type: 'tool_use',
|
|
579
|
-
title: summary ? `🔧 ${name} — ${summary}` : `🔧 ${name}`,
|
|
580
|
-
content: truncateUtf8(args, TOOL_INPUT_MAX_BYTES),
|
|
581
|
-
timestamp: safeEventTimestamp(ev.time),
|
|
582
|
-
};
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
case 'tool/result': {
|
|
586
|
-
const msg = ev.data.message;
|
|
587
|
-
const raw = stringifyContent(msg?.content);
|
|
588
|
-
const isError = msg?.isError === true;
|
|
460
|
+
else if (block.type === 'reasoning') {
|
|
589
461
|
out.events.push({
|
|
590
|
-
type: '
|
|
591
|
-
|
|
592
|
-
content: truncateUtf8(
|
|
593
|
-
timestamp
|
|
462
|
+
type: 'thinking',
|
|
463
|
+
// 思考是过程信息:resume 卡里留头几百字足够判断"当时在想什么"。
|
|
464
|
+
content: truncateUtf8(text, REASONING_MAX_BYTES),
|
|
465
|
+
timestamp,
|
|
594
466
|
});
|
|
595
|
-
break;
|
|
596
467
|
}
|
|
597
|
-
default:
|
|
598
|
-
break;
|
|
599
468
|
}
|
|
469
|
+
const usage = ev.data.usage;
|
|
470
|
+
if (usage && typeof usage === 'object')
|
|
471
|
+
out.usage = usage;
|
|
472
|
+
return out;
|
|
473
|
+
}
|
|
474
|
+
function mapToolCall(ev) {
|
|
475
|
+
const out = { events: [] };
|
|
476
|
+
// 调用本身先不产出事件:DSH 的 tool/call 与 tool/result 是一对,resume 卡
|
|
477
|
+
// 里合成一个面板(`🔧 bash — cmd` + 输出),而不是两个各占一屏。
|
|
478
|
+
const name = String(ev.data.name ?? '');
|
|
479
|
+
const args = typeof ev.data.arguments === 'string' ? ev.data.arguments : '';
|
|
480
|
+
const summary = summarizeToolInput(name, args);
|
|
481
|
+
out.pendingTool = {
|
|
482
|
+
type: 'tool_use',
|
|
483
|
+
title: summary ? `🔧 ${name} — ${summary}` : `🔧 ${name}`,
|
|
484
|
+
content: truncateUtf8(args, TOOL_INPUT_MAX_BYTES),
|
|
485
|
+
timestamp: safeEventTimestamp(ev.time),
|
|
486
|
+
};
|
|
487
|
+
return out;
|
|
488
|
+
}
|
|
489
|
+
function mapToolResult(ev) {
|
|
490
|
+
const out = { events: [] };
|
|
491
|
+
const msg = ev.data.message;
|
|
492
|
+
const raw = stringifyContent(msg?.content);
|
|
493
|
+
const isError = msg?.isError === true;
|
|
494
|
+
out.events.push({
|
|
495
|
+
type: 'tool_result',
|
|
496
|
+
title: isError ? '🔴 结果(失败)' : '🟢 结果',
|
|
497
|
+
content: truncateUtf8(raw, TOOL_RESULT_MAX_BYTES),
|
|
498
|
+
timestamp: safeEventTimestamp(ev.time),
|
|
499
|
+
});
|
|
600
500
|
return out;
|
|
601
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* 事件类型 → 命名 handler。用 Map 而非对象字面量 / switch:`constructor` /
|
|
504
|
+
* `toString` 这类外来 type 会命中 Object.prototype,查表会拿到非函数值。
|
|
505
|
+
*/
|
|
506
|
+
const EVENT_MAPPERS = new Map([
|
|
507
|
+
['user/message', mapUserMessage],
|
|
508
|
+
['assistant/message', mapAssistantMessage],
|
|
509
|
+
['tool/call', mapToolCall],
|
|
510
|
+
['tool/result', mapToolResult],
|
|
511
|
+
]);
|
|
512
|
+
function mapEvent(ev) {
|
|
513
|
+
const mapper = EVENT_MAPPERS.get(ev.type);
|
|
514
|
+
return mapper ? mapper(ev) : { events: [] };
|
|
515
|
+
}
|
|
602
516
|
function stringifyContent(content) {
|
|
603
517
|
if (content == null)
|
|
604
518
|
return '[tool result]';
|
|
@@ -613,4 +527,140 @@ function stringifyContent(content) {
|
|
|
613
527
|
}
|
|
614
528
|
return JSON.stringify(content);
|
|
615
529
|
}
|
|
530
|
+
function newDshContentAccumulator() {
|
|
531
|
+
return {
|
|
532
|
+
events: [],
|
|
533
|
+
cumInput: 0,
|
|
534
|
+
cumOutput: 0,
|
|
535
|
+
cumCacheRead: 0,
|
|
536
|
+
cumCacheWrite: 0,
|
|
537
|
+
turnUsage: null,
|
|
538
|
+
lastTurnUsage: null,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
/** turn 边界标记推进:turn/start 开新账本,turn/end 结算为「上一轮」。 */
|
|
542
|
+
function advanceTurnMarker(acc, ev) {
|
|
543
|
+
if (ev.type === 'turn/start') {
|
|
544
|
+
acc.turnUsage = emptyUsageTotals();
|
|
545
|
+
}
|
|
546
|
+
else if (ev.type === 'turn/end') {
|
|
547
|
+
if (acc.turnUsage)
|
|
548
|
+
acc.lastTurnUsage = acc.turnUsage;
|
|
549
|
+
acc.turnUsage = null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/** 工具调用/结果配对:一对合成一个面板;未配对的分支按原顺序推入。 */
|
|
553
|
+
function pushMappedContentEvents(acc, ev, contentEvents) {
|
|
554
|
+
if (contentEvents.pendingTool) {
|
|
555
|
+
// 未完成的工具调用最多同时一个(DSH 按 step 串行执行;并行调用时按
|
|
556
|
+
// 到达顺序配对,最坏情况是两次调用共用一个面板,不会丢信息)。
|
|
557
|
+
if (acc.pendingTool)
|
|
558
|
+
acc.events.push(acc.pendingTool);
|
|
559
|
+
acc.pendingTool = contentEvents.pendingTool;
|
|
560
|
+
}
|
|
561
|
+
else if (ev.type === 'tool/result' && acc.pendingTool && contentEvents.events[0]) {
|
|
562
|
+
const result = contentEvents.events[0];
|
|
563
|
+
// 标题沿用调用的 `🔧 name — summary`,失败改成 ❌(折叠态一眼看出结果)。
|
|
564
|
+
const failed = result.title?.startsWith('🔴') === true;
|
|
565
|
+
acc.events.push({
|
|
566
|
+
type: 'tool_use',
|
|
567
|
+
title: failed
|
|
568
|
+
? (acc.pendingTool.title ?? '❌ 工具').replace('🔧', '❌')
|
|
569
|
+
: acc.pendingTool.title,
|
|
570
|
+
content: result.content,
|
|
571
|
+
timestamp: acc.pendingTool.timestamp,
|
|
572
|
+
});
|
|
573
|
+
acc.pendingTool = undefined;
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
acc.events.push(...contentEvents.events);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/** 单条 usage:会话累计 + 在途 turn 累计 + 末次采样水位。 */
|
|
580
|
+
function accumulateUsage(acc, u) {
|
|
581
|
+
// Cumulative (session-wide): sum across ALL assistant/message usage.
|
|
582
|
+
acc.cumInput += u.inputTokens ?? 0;
|
|
583
|
+
acc.cumOutput += u.outputTokens ?? 0;
|
|
584
|
+
acc.cumCacheRead += u.cacheReadTokens ?? 0;
|
|
585
|
+
acc.cumCacheWrite += u.cacheWriteTokens ?? 0;
|
|
586
|
+
// 本 run 的 flow 字段 = 末个 turn 的采样之和;contextLength 用末次采样
|
|
587
|
+
// 的窗口占用(input + cacheRead + cacheWrite,不含 output)。
|
|
588
|
+
if (acc.turnUsage) {
|
|
589
|
+
acc.turnUsage.input += u.inputTokens ?? 0;
|
|
590
|
+
acc.turnUsage.output += u.outputTokens ?? 0;
|
|
591
|
+
acc.turnUsage.cacheRead += u.cacheReadTokens ?? 0;
|
|
592
|
+
acc.turnUsage.cacheWrite += u.cacheWriteTokens ?? 0;
|
|
593
|
+
}
|
|
594
|
+
acc.lastUsage = u;
|
|
595
|
+
}
|
|
596
|
+
/** 单条 record → 更新累积状态(无闭包留在 readSessionContent 内)。 */
|
|
597
|
+
function accumulateDshEvent(acc, ev) {
|
|
598
|
+
advanceTurnMarker(acc, ev);
|
|
599
|
+
const contentEvents = mapEvent(ev);
|
|
600
|
+
pushMappedContentEvents(acc, ev, contentEvents);
|
|
601
|
+
if (contentEvents.title)
|
|
602
|
+
acc.displayTitle = contentEvents.title;
|
|
603
|
+
if (contentEvents.usage)
|
|
604
|
+
accumulateUsage(acc, contentEvents.usage);
|
|
605
|
+
}
|
|
606
|
+
/** 末轮 flow + 会话累计 → AgentSessionUsage(§9.21 口径)。 */
|
|
607
|
+
function finalizeDshSessionUsage(acc) {
|
|
608
|
+
const lastUsage = acc.lastUsage;
|
|
609
|
+
if (!lastUsage)
|
|
610
|
+
return undefined;
|
|
611
|
+
const input = lastUsage.inputTokens ?? 0;
|
|
612
|
+
const cacheRead = lastUsage.cacheReadTokens ?? 0;
|
|
613
|
+
const cacheWrite = lastUsage.cacheWriteTokens ?? 0;
|
|
614
|
+
// 在途 turn(turn/start 无 turn/end,run 进行中读历史)的采样优先于
|
|
615
|
+
// 上一轮:旧实现只认已结算的 lastTurnUsage,mid-run 读取会把当前 turn
|
|
616
|
+
// 已消耗的量显示成上一个完整 turn 的数字。
|
|
617
|
+
const flow = acc.turnUsage ??
|
|
618
|
+
acc.lastTurnUsage ?? {
|
|
619
|
+
input,
|
|
620
|
+
output: lastUsage.outputTokens ?? 0,
|
|
621
|
+
cacheRead,
|
|
622
|
+
cacheWrite,
|
|
623
|
+
};
|
|
624
|
+
return {
|
|
625
|
+
inputTokens: flow.input,
|
|
626
|
+
outputTokens: flow.output,
|
|
627
|
+
// §9.21 口径:context 是末次采样的窗口水位(input + 末次 output +
|
|
628
|
+
// cache),不把整 turn 的 output 求和(前几步 output 已进入后续 input)。
|
|
629
|
+
contextLength: input + (lastUsage.outputTokens ?? 0) + cacheRead + cacheWrite,
|
|
630
|
+
cacheReadTokens: flow.cacheRead,
|
|
631
|
+
cacheCreationTokens: flow.cacheWrite,
|
|
632
|
+
totalTokens: flow.input + flow.output + flow.cacheRead + flow.cacheWrite,
|
|
633
|
+
...cumulativeUsageFields({
|
|
634
|
+
input: acc.cumInput,
|
|
635
|
+
output: acc.cumOutput,
|
|
636
|
+
cacheRead: acc.cumCacheRead,
|
|
637
|
+
cacheCreation: acc.cumCacheWrite,
|
|
638
|
+
}),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/** session/page 响应 + 服务端预览 → 详情卡内容(纯结构装配)。 */
|
|
642
|
+
function buildDshSessionContent(value, preview, opts) {
|
|
643
|
+
const acc = newDshContentAccumulator();
|
|
644
|
+
for (const entry of value.records ?? []) {
|
|
645
|
+
const ev = entry.event;
|
|
646
|
+
if (!ev)
|
|
647
|
+
continue;
|
|
648
|
+
accumulateDshEvent(acc, ev);
|
|
649
|
+
}
|
|
650
|
+
const sessionUsage = finalizeDshSessionUsage(acc);
|
|
651
|
+
// 中断/仍在执行的调用没有结果:把它当独立事件留下,别让这一条消失。
|
|
652
|
+
if (acc.pendingTool)
|
|
653
|
+
acc.events.push(acc.pendingTool);
|
|
654
|
+
// 头部信息口径:最近输入优先用服务端末轮 prompt(事件里可能只是当前 turn
|
|
655
|
+
// 的一部分),标题用服务端 title;两者都缺时保持原行为。
|
|
656
|
+
const headerLastInput = preview.lastUserMessage ?? acc.displayTitle;
|
|
657
|
+
return {
|
|
658
|
+
// 展示仍按 maxEvents 尾截断(默认 50,保持原行为),usage 已在上面基于
|
|
659
|
+
// 完整 history 累加。
|
|
660
|
+
events: capEvents(acc.events, opts?.maxEvents ?? 50),
|
|
661
|
+
...(sessionUsage ? { usage: sessionUsage } : {}),
|
|
662
|
+
...(headerLastInput ? { displayTitle: headerLastInput } : {}),
|
|
663
|
+
...(preview.title ? { aiTitle: preview.title } : {}),
|
|
664
|
+
};
|
|
665
|
+
}
|
|
616
666
|
//# sourceMappingURL=sessions.js.map
|
|
@@ -99,13 +99,6 @@ export declare class KimiSessionReader implements AgentSessionReader {
|
|
|
99
99
|
* Find a single index entry by sessionId, or undefined if not found.
|
|
100
100
|
*/
|
|
101
101
|
private findSessionIndexEntry;
|
|
102
|
-
/**
|
|
103
|
-
* Extract the last turn.prompt text from a wire.jsonl file.
|
|
104
|
-
* Used as a fallback summary source when state.json has no title/lastPrompt
|
|
105
|
-
* (v2 sessions). Scans the tail of the file for efficiency.
|
|
106
|
-
* Returns undefined if no turn.prompt is found.
|
|
107
|
-
*/
|
|
108
|
-
private extractLastPromptFromWire;
|
|
109
102
|
/**
|
|
110
103
|
* List all sessions for a given working directory
|
|
111
104
|
*/
|