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
|
@@ -20,18 +20,13 @@ export type SessionDisplayUsage = Pick<UsageSnapshot, 'contextLength' | 'context
|
|
|
20
20
|
* 到本函数(run-renderer 的状态行 `📊 N` 也走这里)。
|
|
21
21
|
*/
|
|
22
22
|
export declare function formatTokenK(n: number): string;
|
|
23
|
-
|
|
24
|
-
* Format usage stats (context length, compact count, cache tokens) into a display string.
|
|
25
|
-
* Each stat on its own line with blank lines between sections.
|
|
26
|
-
* @param usage - Usage stats object
|
|
27
|
-
* @param options - Optional display options (showResult adds result line)
|
|
28
|
-
*/
|
|
29
|
-
export declare function formatUsageStats(usage?: UsageSnapshot & {
|
|
23
|
+
interface UsageStatsInput extends UsageSnapshot {
|
|
30
24
|
/** 实际生效模型(system.init 携带)。非空字符串才显示。 */
|
|
31
25
|
model?: string;
|
|
32
26
|
/** 本次 run 花费(USD)。存在才显示。 */
|
|
33
27
|
costUsd?: number;
|
|
34
|
-
}
|
|
28
|
+
}
|
|
29
|
+
interface UsageDisplayOptions {
|
|
35
30
|
showResult?: boolean;
|
|
36
31
|
result?: string;
|
|
37
32
|
/**
|
|
@@ -39,4 +34,12 @@ export declare function formatUsageStats(usage?: UsageSnapshot & {
|
|
|
39
34
|
* 本轮 turn 结束不等于任务结束。
|
|
40
35
|
*/
|
|
41
36
|
completedLabel?: string;
|
|
42
|
-
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format usage stats (context length, compact count, cache tokens) into a display string.
|
|
40
|
+
* Each stat on its own line with blank lines between sections.
|
|
41
|
+
* @param usage - Usage stats object
|
|
42
|
+
* @param options - Optional display options (showResult adds result line)
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatUsageStats(usage?: UsageStatsInput, options?: UsageDisplayOptions): string;
|
|
45
|
+
export {};
|
|
@@ -29,122 +29,136 @@ export function formatTokenK(n) {
|
|
|
29
29
|
}
|
|
30
30
|
return n.toString();
|
|
31
31
|
}
|
|
32
|
+
function cumulativeField(value) {
|
|
33
|
+
return value !== undefined ? ` · 累计 ${formatTokenK(value)}` : '';
|
|
34
|
+
}
|
|
35
|
+
function cumulativeCachePercent(usage) {
|
|
36
|
+
const cumInput = usage?.cumulativeInputTokens;
|
|
37
|
+
const cumCacheRead = usage?.cumulativeCacheReadTokens;
|
|
38
|
+
return cumInput !== undefined && cumCacheRead !== undefined && cumInput + cumCacheRead > 0
|
|
39
|
+
? Math.round((cumCacheRead / (cumInput + cumCacheRead)) * 100)
|
|
40
|
+
: null;
|
|
41
|
+
}
|
|
42
|
+
function cumulativeParts(usage) {
|
|
43
|
+
const cumCacheRead = usage?.cumulativeCacheReadTokens;
|
|
44
|
+
const percent = cumulativeCachePercent(usage);
|
|
45
|
+
const cacheRead = cumCacheRead !== undefined
|
|
46
|
+
? cumulativeField(cumCacheRead) + (percent !== null ? ` (${percent}%)` : '')
|
|
47
|
+
: '';
|
|
48
|
+
return {
|
|
49
|
+
input: cumulativeField(usage?.cumulativeInputTokens),
|
|
50
|
+
output: cumulativeField(usage?.cumulativeOutputTokens),
|
|
51
|
+
cacheRead,
|
|
52
|
+
cacheCreate: cumulativeField(usage?.cumulativeCacheCreationTokens),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
32
55
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
56
|
+
* 一致性护栏:本 run ≤ 累计 是硬不变量(累计 = 所有 run 之和,必 ≥ 本 run)。
|
|
57
|
+
* 违反说明来源 scope 混用(如 DeepSeek live result usage 是累积/超大 scope,
|
|
58
|
+
* 大于 jsonl 去重聚合的 session 累计)。只标记不修正——数字保持原样便于排查,
|
|
59
|
+
* 由数据源修复。两侧都有值时校验(避免旧数据/缺失字段误报)。
|
|
60
|
+
* 两个分支(real input/output / 10% 估计)共用,保证"本 run > 累计"在任意
|
|
61
|
+
* 路径都显式暴露。
|
|
37
62
|
*/
|
|
38
|
-
|
|
63
|
+
function cumulativeTotalSuffix(usage, totalTokens) {
|
|
64
|
+
const cum = usage?.cumulativeTotalTokens;
|
|
65
|
+
if (cum === undefined)
|
|
66
|
+
return '';
|
|
67
|
+
const guard = totalTokens > cum ? ' · ⚠️ 累计异常' : '';
|
|
68
|
+
return cumulativeField(cum) + guard;
|
|
69
|
+
}
|
|
70
|
+
function contextLine(length, usage) {
|
|
71
|
+
const ctxLimit = usage.contextLimit;
|
|
72
|
+
const ctxPercent = ctxLimit !== undefined && ctxLimit > 0 ? ` (${Math.round((length / ctxLimit) * 100)}%)` : '';
|
|
73
|
+
const preCompact = usage.compactPreContextLength !== undefined
|
|
74
|
+
? `(压缩前 ${formatTokenK(usage.compactPreContextLength)})`
|
|
75
|
+
: '';
|
|
76
|
+
return `Context - ${formatTokenK(length)}${ctxPercent}${preCompact}`;
|
|
77
|
+
}
|
|
78
|
+
function headerLines(usage, options) {
|
|
39
79
|
const lines = [];
|
|
40
|
-
// 1. Success 状态(通用格式,无具体结果值)
|
|
41
80
|
lines.push(options?.completedLabel ?? '✅ 已完成');
|
|
42
|
-
|
|
43
|
-
if (usage?.model) {
|
|
81
|
+
if (usage?.model)
|
|
44
82
|
lines.push(`Model - ${usage.model}`);
|
|
45
|
-
|
|
46
|
-
// 1.5. 可选的结果行(showResult 为 true 时显示)
|
|
47
|
-
if (options?.showResult && options?.result) {
|
|
83
|
+
if (options?.showResult && options?.result)
|
|
48
84
|
lines.push(`结果 - ${options.result}`);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (usage?.
|
|
52
|
-
const ctxLimit = usage.contextLimit;
|
|
53
|
-
const ctxPercent = ctxLimit !== undefined && ctxLimit > 0
|
|
54
|
-
? ` (${Math.round((usage.contextLength / ctxLimit) * 100)}%)`
|
|
55
|
-
: '';
|
|
56
|
-
const preCompact = usage.compactPreContextLength !== undefined
|
|
57
|
-
? `(压缩前 ${formatTokenK(usage.compactPreContextLength)})`
|
|
58
|
-
: '';
|
|
59
|
-
lines.push(`Context - ${formatTokenK(usage.contextLength)}${ctxPercent}${preCompact}`);
|
|
60
|
-
}
|
|
61
|
-
// 3. Compact 次数
|
|
62
|
-
if (usage?.compactCount) {
|
|
85
|
+
if (usage?.contextLength !== undefined)
|
|
86
|
+
lines.push(contextLine(usage.contextLength, usage));
|
|
87
|
+
if (usage?.compactCount)
|
|
63
88
|
lines.push(`Compact - ${usage.compactCount}次`);
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
91
|
+
/** 命中率分母由调用方给:real 分支是 input+cacheRead,estimate 分支是 totalInput。 */
|
|
92
|
+
function makeTokenLines(input, output, cacheRead, cacheCreation, totalTokens, cachePercent, parts, usage) {
|
|
93
|
+
const lines = [
|
|
94
|
+
`Input token - ${formatTokenK(input)}${parts.input}`,
|
|
95
|
+
`Output token - ${formatTokenK(output)}${parts.output}`,
|
|
96
|
+
];
|
|
97
|
+
if (cacheCreation > 0) {
|
|
98
|
+
lines.push(`Cache create - ${formatTokenK(cacheCreation)}${parts.cacheCreate}`);
|
|
64
99
|
}
|
|
65
|
-
|
|
100
|
+
lines.push(`Cached token - ${formatTokenK(cacheRead)} (${cachePercent}%)${parts.cacheRead}`);
|
|
101
|
+
lines.push(`Total token - ${formatTokenK(totalTokens)}${cumulativeTotalSuffix(usage, totalTokens)}`);
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
104
|
+
function realTokenLines(usage, realInput, realOutput, parts) {
|
|
105
|
+
// Unified ccusage-aligned formula:
|
|
106
|
+
// total = max(total_tokens, input + output + cacheRead + cacheCreation)
|
|
107
|
+
// cache% = cacheRead / (input + cacheRead)
|
|
108
|
+
// cacheRead/cacheCreation are never double-counted; total_tokens folds in any
|
|
109
|
+
// extra (e.g. opencode reasoning, which is separate from output) via max().
|
|
110
|
+
const cacheRead = usage.cacheReadTokens ?? 0;
|
|
111
|
+
const cacheCreation = usage.cacheCreationTokens ?? 0;
|
|
112
|
+
const sumParts = realInput + realOutput + cacheRead + cacheCreation;
|
|
113
|
+
const totalTokens = usage.totalTokens != null ? Math.max(usage.totalTokens, sumParts) : sumParts;
|
|
114
|
+
const promptTokens = realInput + cacheRead;
|
|
115
|
+
const cachePercent = promptTokens > 0 ? Math.round((cacheRead / promptTokens) * 100) : 0;
|
|
116
|
+
return makeTokenLines(realInput, realOutput, cacheRead, cacheCreation, totalTokens, cachePercent, parts, usage);
|
|
117
|
+
}
|
|
118
|
+
function estimateTokenLines(usage, parts) {
|
|
119
|
+
// Estimate path: when real input/output are absent,
|
|
120
|
+
// fall back to the 10% estimate based on contextLength.
|
|
66
121
|
const cacheRead = usage?.cacheReadTokens ?? 0;
|
|
67
122
|
const cacheCreation = usage?.cacheCreationTokens ?? 0;
|
|
123
|
+
const inputRef = usage?.contextLength ?? cacheRead;
|
|
124
|
+
if (inputRef <= 0 && cacheRead <= 0 && cacheCreation <= 0)
|
|
125
|
+
return [];
|
|
126
|
+
const totalInput = inputRef + cacheRead;
|
|
127
|
+
const outputEst = inputRef > 0 ? Math.round(inputRef * 0.1) : 0;
|
|
128
|
+
const cachePercent = totalInput > 0 ? Math.round((cacheRead / totalInput) * 100) : 0;
|
|
129
|
+
const totalTokens = totalInput + outputEst + cacheCreation;
|
|
130
|
+
return makeTokenLines(totalInput, outputEst, cacheRead, cacheCreation, totalTokens, cachePercent, parts, usage);
|
|
131
|
+
}
|
|
132
|
+
function tokenLines(usage, parts) {
|
|
68
133
|
const realInput = usage?.inputTokens;
|
|
69
134
|
const realOutput = usage?.outputTokens;
|
|
70
|
-
// Cumulative suffix (session-wide totals), appended to Input/Output lines.
|
|
71
|
-
const cumInputSuffix = usage?.cumulativeInputTokens !== undefined
|
|
72
|
-
? ` · 累计 ${formatTokenK(usage.cumulativeInputTokens)}`
|
|
73
|
-
: '';
|
|
74
|
-
const cumOutputSuffix = usage?.cumulativeOutputTokens !== undefined
|
|
75
|
-
? ` · 累计 ${formatTokenK(usage.cumulativeOutputTokens)}`
|
|
76
|
-
: '';
|
|
77
|
-
const cumTotalSuffix = usage?.cumulativeTotalTokens !== undefined
|
|
78
|
-
? ` · 累计 ${formatTokenK(usage.cumulativeTotalTokens)}`
|
|
79
|
-
: '';
|
|
80
|
-
// 一致性护栏:本 run ≤ 累计 是硬不变量(累计 = 所有 run 之和,必 ≥ 本 run)。
|
|
81
|
-
// 违反说明来源 scope 混用(如 DeepSeek live result usage 是累积/超大 scope,
|
|
82
|
-
// 大于 jsonl 去重聚合的 session 累计)。只标记不修正——数字保持原样便于排查,
|
|
83
|
-
// 由数据源修复。两侧都有值时校验(避免旧数据/缺失字段误报)。
|
|
84
|
-
// 两个分支(real input/output / 10% 估计)共用,保证"本 run > 累计"在任意
|
|
85
|
-
// 路径都显式暴露。
|
|
86
|
-
const withCumGuard = (totalTokens) => cumTotalSuffix +
|
|
87
|
-
(usage?.cumulativeTotalTokens !== undefined && totalTokens > usage.cumulativeTotalTokens
|
|
88
|
-
? ' · ⚠️ 累计异常'
|
|
89
|
-
: '');
|
|
90
|
-
// 计算累计 cache 命中率
|
|
91
|
-
const cumInput = usage?.cumulativeInputTokens;
|
|
92
|
-
const cumCacheRead = usage?.cumulativeCacheReadTokens;
|
|
93
|
-
const cumCachePercent = cumInput !== undefined && cumCacheRead !== undefined && cumInput + cumCacheRead > 0
|
|
94
|
-
? Math.round((cumCacheRead / (cumInput + cumCacheRead)) * 100)
|
|
95
|
-
: null;
|
|
96
|
-
// 构建累计 suffix
|
|
97
|
-
const cumCacheReadSuffix = cumCacheRead !== undefined
|
|
98
|
-
? ` · 累计 ${formatTokenK(cumCacheRead)}` +
|
|
99
|
-
(cumCachePercent !== null ? ` (${cumCachePercent}%)` : '')
|
|
100
|
-
: '';
|
|
101
|
-
const cumCacheCreateSuffix = usage?.cumulativeCacheCreationTokens !== undefined
|
|
102
|
-
? ` · 累计 ${formatTokenK(usage.cumulativeCacheCreationTokens)}`
|
|
103
|
-
: '';
|
|
104
135
|
if (realInput !== undefined && realOutput !== undefined) {
|
|
105
|
-
|
|
106
|
-
// total = max(total_tokens, input + output + cacheRead + cacheCreation)
|
|
107
|
-
// cache% = cacheRead / (input + cacheRead)
|
|
108
|
-
// cacheRead/cacheCreation are never double-counted; total_tokens folds in any
|
|
109
|
-
// extra (e.g. opencode reasoning, which is separate from output) via max().
|
|
110
|
-
const promptTokens = realInput + cacheRead;
|
|
111
|
-
const cachePercent = promptTokens > 0 ? Math.round((cacheRead / promptTokens) * 100) : 0;
|
|
112
|
-
const sumParts = realInput + realOutput + cacheRead + cacheCreation;
|
|
113
|
-
const totalTokens = usage?.totalTokens != null ? Math.max(usage.totalTokens, sumParts) : sumParts;
|
|
114
|
-
lines.push(`Input token - ${formatTokenK(realInput)}${cumInputSuffix}`);
|
|
115
|
-
lines.push(`Output token - ${formatTokenK(realOutput)}${cumOutputSuffix}`);
|
|
116
|
-
if (cacheCreation > 0) {
|
|
117
|
-
lines.push(`Cache create - ${formatTokenK(cacheCreation)}${cumCacheCreateSuffix}`);
|
|
118
|
-
}
|
|
119
|
-
lines.push(`Cached token - ${formatTokenK(cacheRead)} (${cachePercent}%)${cumCacheReadSuffix}`);
|
|
120
|
-
lines.push(`Total token - ${formatTokenK(totalTokens)}${withCumGuard(totalTokens)}`);
|
|
136
|
+
return realTokenLines(usage, realInput, realOutput, parts);
|
|
121
137
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (inputRef > 0 || cacheRead > 0 || cacheCreation > 0) {
|
|
127
|
-
const totalInput = inputRef + cacheRead;
|
|
128
|
-
const outputEst = inputRef > 0 ? Math.round(inputRef * 0.1) : 0;
|
|
129
|
-
const cachePercent = totalInput > 0 ? Math.round((cacheRead / totalInput) * 100) : 0;
|
|
130
|
-
const totalTokens = totalInput + outputEst + cacheCreation;
|
|
131
|
-
lines.push(`Input token - ${formatTokenK(totalInput)}${cumInputSuffix}`);
|
|
132
|
-
lines.push(`Output token - ${formatTokenK(outputEst)}${cumOutputSuffix}`);
|
|
133
|
-
if (cacheCreation > 0) {
|
|
134
|
-
lines.push(`Cache create - ${formatTokenK(cacheCreation)}${cumCacheCreateSuffix}`);
|
|
135
|
-
}
|
|
136
|
-
lines.push(`Cached token - ${formatTokenK(cacheRead)} (${cachePercent}%)${cumCacheReadSuffix}`);
|
|
137
|
-
lines.push(`Total token - ${formatTokenK(totalTokens)}${withCumGuard(totalTokens)}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// 8. 本次 run 花费(USD)
|
|
138
|
+
return estimateTokenLines(usage, parts);
|
|
139
|
+
}
|
|
140
|
+
function trailingLines(usage) {
|
|
141
|
+
const lines = [];
|
|
141
142
|
if (usage?.costUsd !== undefined) {
|
|
142
143
|
lines.push(`Cost - $${usage.costUsd.toFixed(4)}`);
|
|
143
144
|
}
|
|
144
|
-
// 9. 推理 token(> 0 才显示,格式对齐既有 token 行)
|
|
145
145
|
if (usage?.reasoningTokens !== undefined && usage.reasoningTokens > 0) {
|
|
146
146
|
lines.push(`Reasoning token - ${formatTokenK(usage.reasoningTokens)}`);
|
|
147
147
|
}
|
|
148
|
-
return lines
|
|
148
|
+
return lines;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Format usage stats (context length, compact count, cache tokens) into a display string.
|
|
152
|
+
* Each stat on its own line with blank lines between sections.
|
|
153
|
+
* @param usage - Usage stats object
|
|
154
|
+
* @param options - Optional display options (showResult adds result line)
|
|
155
|
+
*/
|
|
156
|
+
export function formatUsageStats(usage, options) {
|
|
157
|
+
const parts = cumulativeParts(usage);
|
|
158
|
+
return [
|
|
159
|
+
...headerLines(usage, options),
|
|
160
|
+
...tokenLines(usage, parts),
|
|
161
|
+
...trailingLines(usage),
|
|
162
|
+
].join('\n');
|
|
149
163
|
}
|
|
150
164
|
//# sourceMappingURL=usage-format.js.map
|
|
@@ -33,33 +33,45 @@ export function displayWidth(text) {
|
|
|
33
33
|
total += charWidth(ch);
|
|
34
34
|
return total;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 显示宽度为 2 的码点区间(闭区间,Map 的键 = 下界、值 = 上界)。
|
|
38
|
+
*
|
|
39
|
+
* 用 Map 而非对象字面量:对象字面量的 `constructor` / `toString` 等键会命中
|
|
40
|
+
* Object.prototype,查表会拿到非预期值;数字键的 Map 无此问题。顺序与原 if 链
|
|
41
|
+
* 一致(所有区间都返回同一宽度,遍历早停即等价),逐字保留:
|
|
42
|
+
* - [0x1100,0x115f] Hangul Jamo;U+2329 / U+232A 单独列为单码点区间;
|
|
43
|
+
* - CJK 统一表意文字 / 假名 / 全角标点 / 兼容表意 / 全角 / Hangul 音节;
|
|
44
|
+
* - 常见 emoji(0x1f300–0x1faff)与 Misc Symbols(0x2600–0x27bf);
|
|
45
|
+
* - Misc Technical 块(U+2300–U+23FF):⏱⏹⏳⏸⏺ 等计时器/播放器符号在
|
|
46
|
+
* 状态行里实际使用,手机端按 emoji 宽度渲染——按 1 计会系统性低估行宽,
|
|
47
|
+
* 窄屏余量比设计值更小。
|
|
48
|
+
*/
|
|
49
|
+
const WIDE_CODE_POINT_RANGES = new Map([
|
|
50
|
+
[0x1100, 0x115f],
|
|
51
|
+
[0x2329, 0x2329],
|
|
52
|
+
[0x232a, 0x232a],
|
|
53
|
+
[0x2e80, 0xa4cf],
|
|
54
|
+
[0xac00, 0xd7a3],
|
|
55
|
+
[0xf900, 0xfaff],
|
|
56
|
+
[0xfe30, 0xfe6f],
|
|
57
|
+
[0xff00, 0xff60],
|
|
58
|
+
[0xffe0, 0xffe6],
|
|
59
|
+
[0x1f300, 0x1faff],
|
|
60
|
+
[0x2600, 0x27bf],
|
|
61
|
+
[0x2300, 0x23ff],
|
|
62
|
+
]);
|
|
63
|
+
/** 码点是否落在任一「宽度 2」区间内(模块级命名函数,供 charWidth 查表)。 */
|
|
64
|
+
function isWideCodePoint(code) {
|
|
65
|
+
for (const [lo, hi] of WIDE_CODE_POINT_RANGES) {
|
|
66
|
+
if (code >= lo && code <= hi)
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
36
71
|
function charWidth(ch) {
|
|
37
72
|
const code = ch.codePointAt(0) ?? 0;
|
|
38
73
|
// CJK 统一表意文字、假名、全角标点、常见 emoji 都按两个半角算。
|
|
39
|
-
|
|
40
|
-
return 2;
|
|
41
|
-
if (code >= 0x2e80 && code <= 0xa4cf)
|
|
42
|
-
return 2;
|
|
43
|
-
if (code >= 0xac00 && code <= 0xd7a3)
|
|
44
|
-
return 2;
|
|
45
|
-
if (code >= 0xf900 && code <= 0xfaff)
|
|
46
|
-
return 2;
|
|
47
|
-
if (code >= 0xfe30 && code <= 0xfe6f)
|
|
48
|
-
return 2;
|
|
49
|
-
if (code >= 0xff00 && code <= 0xff60)
|
|
50
|
-
return 2;
|
|
51
|
-
if (code >= 0xffe0 && code <= 0xffe6)
|
|
52
|
-
return 2;
|
|
53
|
-
if (code >= 0x1f300 && code <= 0x1faff)
|
|
54
|
-
return 2;
|
|
55
|
-
if (code >= 0x2600 && code <= 0x27bf)
|
|
56
|
-
return 2;
|
|
57
|
-
// Misc Technical 块(U+2300–U+23FF):⏱⏹⏳⏸⏺ 等计时器/播放器符号在
|
|
58
|
-
// 状态行里实际使用,手机端按 emoji 宽度渲染——按 1 计会系统性低估行宽,
|
|
59
|
-
// 窄屏余量比设计值更小。
|
|
60
|
-
if (code >= 0x2300 && code <= 0x23ff)
|
|
61
|
-
return 2;
|
|
62
|
-
return 1;
|
|
74
|
+
return isWideCodePoint(code) ? 2 : 1;
|
|
63
75
|
}
|
|
64
76
|
/**
|
|
65
77
|
* 中间省略截断:保留开头与结尾,只在中间打 `…`。
|
|
@@ -52,101 +52,143 @@ export function asRecord(input) {
|
|
|
52
52
|
}
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
// --- 字段选取原语 ---
|
|
56
|
+
// 模块级纯函数:调用方(`summarizeToolInput`)只负责选路,字段口径各自独立。
|
|
57
|
+
function pickField(rec, key, max = HEADER_SUMMARY_MAX) {
|
|
58
|
+
const v = rec[key];
|
|
59
|
+
if (typeof v !== 'string')
|
|
58
60
|
return '';
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
const oneLine = v.replace(/\s+/g, ' ').trim();
|
|
62
|
+
// 码点安全截断:裸 slice 切在代理对中间会留下孤立代理项进卡片
|
|
63
|
+
// (同族修复见 tool-render.ts 的 toolHeaderText)。
|
|
64
|
+
return truncate(oneLine, max);
|
|
65
|
+
}
|
|
66
|
+
/** First non-empty of several keys(DSH 同名工具跨版本改过参数名)。 */
|
|
67
|
+
function pickAnyField(rec, keys, max = HEADER_SUMMARY_MAX) {
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
const v = pickField(rec, key, max);
|
|
70
|
+
if (v)
|
|
71
|
+
return v;
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
/** 数组参数的第一个字符串项(DSH web_search 的 queries、present 的 files)。 */
|
|
76
|
+
function pickFirstField(rec, key, max = HEADER_SUMMARY_MAX) {
|
|
77
|
+
const v = rec[key];
|
|
78
|
+
if (typeof v === 'string')
|
|
79
|
+
return pickField(rec, key, max);
|
|
80
|
+
if (!Array.isArray(v))
|
|
75
81
|
return '';
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (typeof v === 'string')
|
|
81
|
-
return pick(key, max);
|
|
82
|
-
if (!Array.isArray(v))
|
|
83
|
-
return '';
|
|
84
|
-
for (const item of v) {
|
|
85
|
-
if (typeof item === 'string' && item.trim()) {
|
|
86
|
-
const oneLine = item.replace(/\s+/g, ' ').trim();
|
|
87
|
-
return truncate(oneLine, max);
|
|
88
|
-
}
|
|
89
|
-
if (item && typeof item === 'object') {
|
|
90
|
-
const path = item.path;
|
|
91
|
-
if (typeof path === 'string' && path.trim())
|
|
92
|
-
return path;
|
|
93
|
-
}
|
|
82
|
+
for (const item of v) {
|
|
83
|
+
if (typeof item === 'string' && item.trim()) {
|
|
84
|
+
const oneLine = item.replace(/\s+/g, ' ').trim();
|
|
85
|
+
return truncate(oneLine, max);
|
|
94
86
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return pick('command');
|
|
100
|
-
case 'file':
|
|
101
|
-
return pick('file_path') || pick('path');
|
|
102
|
-
case 'grep': {
|
|
103
|
-
const pat = pick('pattern', 40);
|
|
104
|
-
const p = pick('path', 30);
|
|
105
|
-
return p ? `${pat} in ${p}` : pat;
|
|
87
|
+
if (item && typeof item === 'object') {
|
|
88
|
+
const path = item.path;
|
|
89
|
+
if (typeof path === 'string' && path.trim())
|
|
90
|
+
return path;
|
|
106
91
|
}
|
|
107
|
-
case 'glob':
|
|
108
|
-
return pick('pattern');
|
|
109
|
-
case 'ls':
|
|
110
|
-
return pick('path');
|
|
111
|
-
case 'webfetch':
|
|
112
|
-
return pick('url');
|
|
113
|
-
case 'websearch':
|
|
114
|
-
case 'websearch_queries':
|
|
115
|
-
return pickFirst('queries', 60) || pick('query', 60);
|
|
116
|
-
default:
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
switch (name) {
|
|
120
|
-
case 'Agent':
|
|
121
|
-
case 'Task':
|
|
122
|
-
case 'subagent':
|
|
123
|
-
case 'workflow':
|
|
124
|
-
return pick('description') || pick('subagent_type');
|
|
125
|
-
case 'todo_write':
|
|
126
|
-
return summarizeTodos(rec);
|
|
127
|
-
case 'job_output':
|
|
128
|
-
case 'job_kill':
|
|
129
|
-
return pick('job_id');
|
|
130
|
-
case 'job_list':
|
|
131
|
-
return '(列出任务)';
|
|
132
|
-
case 'present':
|
|
133
|
-
return pickFirst('files');
|
|
134
|
-
case 'skill':
|
|
135
|
-
return pick('name');
|
|
136
|
-
case 'run_code':
|
|
137
|
-
return pick('description') || pick('language');
|
|
138
|
-
case 'terminal_send':
|
|
139
|
-
return pick('text');
|
|
140
|
-
case 'ask_user_question': {
|
|
141
|
-
const questions = rec.questions;
|
|
142
|
-
return Array.isArray(questions) ? `${questions.length} 个问题` : '';
|
|
143
|
-
}
|
|
144
|
-
case 'send_message':
|
|
145
|
-
case 'interrupt_agent':
|
|
146
|
-
return pick('description') || pick('agent_id');
|
|
147
|
-
default:
|
|
148
|
-
return pickAny(['command', 'file_path', 'path', 'query', 'url']);
|
|
149
92
|
}
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
function shellSummary(rec) {
|
|
96
|
+
return pickField(rec, 'command');
|
|
97
|
+
}
|
|
98
|
+
function fileSummary(rec) {
|
|
99
|
+
return pickField(rec, 'file_path') || pickField(rec, 'path');
|
|
100
|
+
}
|
|
101
|
+
function grepSummary(rec) {
|
|
102
|
+
const pat = pickField(rec, 'pattern', 40);
|
|
103
|
+
const p = pickField(rec, 'path', 30);
|
|
104
|
+
return p ? `${pat} in ${p}` : pat;
|
|
105
|
+
}
|
|
106
|
+
function patternSummary(rec) {
|
|
107
|
+
return pickField(rec, 'pattern');
|
|
108
|
+
}
|
|
109
|
+
function pathSummary(rec) {
|
|
110
|
+
return pickField(rec, 'path');
|
|
111
|
+
}
|
|
112
|
+
function urlSummary(rec) {
|
|
113
|
+
return pickField(rec, 'url');
|
|
114
|
+
}
|
|
115
|
+
function queriesSummary(rec) {
|
|
116
|
+
return pickFirstField(rec, 'queries', 60) || pickField(rec, 'query', 60);
|
|
117
|
+
}
|
|
118
|
+
function subagentSummary(rec) {
|
|
119
|
+
return pickField(rec, 'description') || pickField(rec, 'subagent_type');
|
|
120
|
+
}
|
|
121
|
+
function messageSummary(rec) {
|
|
122
|
+
return pickField(rec, 'description') || pickField(rec, 'agent_id');
|
|
123
|
+
}
|
|
124
|
+
function jobIdSummary(rec) {
|
|
125
|
+
return pickField(rec, 'job_id');
|
|
126
|
+
}
|
|
127
|
+
function jobListSummary() {
|
|
128
|
+
return '(列出任务)';
|
|
129
|
+
}
|
|
130
|
+
function filesSummary(rec) {
|
|
131
|
+
return pickFirstField(rec, 'files');
|
|
132
|
+
}
|
|
133
|
+
function nameSummary(rec) {
|
|
134
|
+
return pickField(rec, 'name');
|
|
135
|
+
}
|
|
136
|
+
function runCodeSummary(rec) {
|
|
137
|
+
return pickField(rec, 'description') || pickField(rec, 'language');
|
|
138
|
+
}
|
|
139
|
+
function textSummary(rec) {
|
|
140
|
+
return pickField(rec, 'text');
|
|
141
|
+
}
|
|
142
|
+
function questionCountSummary(rec) {
|
|
143
|
+
const questions = rec.questions;
|
|
144
|
+
return Array.isArray(questions) ? `${questions.length} 个问题` : '';
|
|
145
|
+
}
|
|
146
|
+
function todoSummary(rec) {
|
|
147
|
+
return summarizeTodos(rec);
|
|
148
|
+
}
|
|
149
|
+
const FALLBACK_FIELDS = ['command', 'file_path', 'path', 'query', 'url'];
|
|
150
|
+
/** 类别 → 摘要器。ToolKind 的每个成员都必须有条目。 */
|
|
151
|
+
const KIND_SUMMARY = new Map([
|
|
152
|
+
['shell', shellSummary],
|
|
153
|
+
['file', fileSummary],
|
|
154
|
+
['grep', grepSummary],
|
|
155
|
+
['glob', patternSummary],
|
|
156
|
+
['ls', pathSummary],
|
|
157
|
+
['webfetch', urlSummary],
|
|
158
|
+
['websearch', queriesSummary],
|
|
159
|
+
['websearch_queries', queriesSummary],
|
|
160
|
+
]);
|
|
161
|
+
/**
|
|
162
|
+
* 工具名 → 摘要器。用 Map 而非对象字面量:`constructor` / `toString` 这类
|
|
163
|
+
* 工具名会命中 Object.prototype,查表会拿到非函数值。
|
|
164
|
+
*/
|
|
165
|
+
const NAME_SUMMARY = new Map([
|
|
166
|
+
['Agent', subagentSummary],
|
|
167
|
+
['Task', subagentSummary],
|
|
168
|
+
['subagent', subagentSummary],
|
|
169
|
+
['workflow', subagentSummary],
|
|
170
|
+
['todo_write', todoSummary],
|
|
171
|
+
['job_output', jobIdSummary],
|
|
172
|
+
['job_kill', jobIdSummary],
|
|
173
|
+
['job_list', jobListSummary],
|
|
174
|
+
['present', filesSummary],
|
|
175
|
+
['skill', nameSummary],
|
|
176
|
+
['run_code', runCodeSummary],
|
|
177
|
+
['terminal_send', textSummary],
|
|
178
|
+
['ask_user_question', questionCountSummary],
|
|
179
|
+
['send_message', messageSummary],
|
|
180
|
+
['interrupt_agent', messageSummary],
|
|
181
|
+
]);
|
|
182
|
+
export function summarizeToolInput(name, input) {
|
|
183
|
+
const rec = asRecord(input);
|
|
184
|
+
if (!rec)
|
|
185
|
+
return '';
|
|
186
|
+
const kind = toolKindOf(name);
|
|
187
|
+
const byKind = kind === undefined ? undefined : KIND_SUMMARY.get(kind);
|
|
188
|
+
if (byKind)
|
|
189
|
+
return byKind(rec);
|
|
190
|
+
const byName = NAME_SUMMARY.get(name);
|
|
191
|
+
return byName ? byName(rec) : pickAnyField(rec, FALLBACK_FIELDS);
|
|
150
192
|
}
|
|
151
193
|
/** todo_write 摘要:完成进度 + 当前 in_progress 项主题。 */
|
|
152
194
|
function summarizeTodos(rec) {
|