lark-remote 0.5.6 → 0.5.8
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 +46 -6
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/bridge/index.d.ts +63 -1
- package/dist/bridge/index.js +147 -14
- package/dist/bridge/queue-manager.js +41 -58
- package/dist/card/approval-render.js +2 -1
- package/dist/card/bash-card-session.d.ts +0 -1
- package/dist/card/bash-card-session.js +0 -8
- package/dist/card/bash-renderer.js +2 -1
- package/dist/card/card-budget.d.ts +37 -0
- package/dist/card/card-budget.js +143 -24
- package/dist/card/card-session.d.ts +3 -13
- package/dist/card/card-session.js +4 -18
- package/dist/card/card-shared.d.ts +11 -0
- package/dist/card/card-shared.js +19 -13
- package/dist/card/run-card-session.d.ts +0 -1
- package/dist/card/run-card-session.js +0 -6
- package/dist/card/run-renderer.d.ts +18 -0
- package/dist/card/run-renderer.js +330 -62
- package/dist/card/run-state.d.ts +26 -0
- package/dist/card/run-state.js +45 -0
- package/dist/card/text-truncate.d.ts +3 -5
- package/dist/card/text-truncate.js +3 -5
- package/dist/card/time.d.ts +14 -0
- package/dist/card/time.js +30 -0
- package/dist/card/tool-render.d.ts +15 -2
- package/dist/card/tool-render.js +42 -82
- package/dist/common/text-width.d.ts +24 -0
- package/dist/common/text-width.js +93 -0
- package/dist/common/tool-summary.d.ts +19 -0
- package/dist/common/tool-summary.js +171 -0
- package/dist/common/turn-failure.d.ts +13 -0
- package/dist/common/turn-failure.js +18 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/index.js +66 -13
- package/dist/order/index.d.ts +2 -0
- package/dist/order/index.js +2 -0
- package/dist/platform/command.js +1 -1
- package/dist/platform/shell.d.ts +2 -2
- package/dist/platform/spawn.d.ts +1 -1
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/router/card-helpers.d.ts +18 -16
- package/dist/router/card-helpers.js +88 -46
- package/dist/router/config/codex.js +2 -3
- package/dist/router/config/common/model-patch.d.ts +8 -0
- package/dist/router/config/common/model-patch.js +13 -0
- package/dist/router/config/common/render.js +30 -77
- package/dist/router/config/dsh.js +2 -1
- package/dist/router/config/kimi.js +3 -3
- package/dist/router/index.d.ts +9 -11
- package/dist/router/index.js +165 -356
- package/dist/router/order-exec-dispatch.js +2 -1
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +3 -3
- package/dist/runner/common/defaults.d.ts +2 -0
- package/dist/runner/common/defaults.js +3 -0
- 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 +3 -3
- package/dist/runner/common/spawning-runner.d.ts +1 -1
- package/dist/runner/common/spawning-runner.js +2 -2
- package/dist/runner/dsh/auth.js +2 -1
- package/dist/runner/dsh/client.d.ts +100 -3
- package/dist/runner/dsh/client.js +290 -6
- package/dist/runner/dsh/exit-cancel.d.ts +32 -0
- package/dist/runner/dsh/exit-cancel.js +72 -0
- package/dist/runner/dsh/inflight.d.ts +62 -0
- package/dist/runner/dsh/inflight.js +140 -0
- package/dist/runner/dsh/runner.d.ts +90 -4
- package/dist/runner/dsh/runner.js +436 -18
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/dsh/translator.js +103 -1
- package/dist/runner/dsh/types.d.ts +60 -0
- package/dist/runner/types.d.ts +95 -2
- package/dist/runner/types.js +9 -0
- package/dist/session/claude/session-index.d.ts +0 -4
- package/dist/session/claude/session-index.js +0 -8
- package/dist/session/claude/sessions.js +2 -2
- package/dist/session/dsh/sessions.js +157 -17
- package/dist/update/install.js +1 -1
- package/package.json +1 -1
package/dist/card/time.d.ts
CHANGED
|
@@ -1 +1,15 @@
|
|
|
1
1
|
export declare function formatTimestamp(ts: string | number | undefined): string;
|
|
2
|
+
/**
|
|
3
|
+
* 时钟时间 `HH:mm`(同一张卡内的次要信息用)。
|
|
4
|
+
*
|
|
5
|
+
* 面板标题在手机上只有约 36 个显示宽度可用:`2026-10-19 21:23` 光时间戳就吃掉
|
|
6
|
+
* 一半,正文明明只需要知道"什么时候发生的"。完整的日期在会话历史/日志里。
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatClock(ts: string | number | undefined): string;
|
|
9
|
+
/**
|
|
10
|
+
* `MM-DD HH:mm`(面板标题里的历史事件时间)。
|
|
11
|
+
*
|
|
12
|
+
* 比 `HH:mm` 多带上日期(跨天/跨周的会话不会误导),比完整 `YYYY-MM-DD HH:mm`
|
|
13
|
+
* 省 7 个显示宽度——那 7 个单位在手机上正好是文件名能不能显示出来的差别。
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatMonthDayClock(ts: string | number | undefined): string;
|
package/dist/card/time.js
CHANGED
|
@@ -11,4 +11,34 @@ export function formatTimestamp(ts) {
|
|
|
11
11
|
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
12
12
|
return `${yyyy}-${mm}-${dd} ${hh}:${mi}`;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* 时钟时间 `HH:mm`(同一张卡内的次要信息用)。
|
|
16
|
+
*
|
|
17
|
+
* 面板标题在手机上只有约 36 个显示宽度可用:`2026-10-19 21:23` 光时间戳就吃掉
|
|
18
|
+
* 一半,正文明明只需要知道"什么时候发生的"。完整的日期在会话历史/日志里。
|
|
19
|
+
*/
|
|
20
|
+
export function formatClock(ts) {
|
|
21
|
+
if (!ts)
|
|
22
|
+
return '';
|
|
23
|
+
const d = new Date(ts);
|
|
24
|
+
if (isNaN(d.getTime()))
|
|
25
|
+
return '';
|
|
26
|
+
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `MM-DD HH:mm`(面板标题里的历史事件时间)。
|
|
30
|
+
*
|
|
31
|
+
* 比 `HH:mm` 多带上日期(跨天/跨周的会话不会误导),比完整 `YYYY-MM-DD HH:mm`
|
|
32
|
+
* 省 7 个显示宽度——那 7 个单位在手机上正好是文件名能不能显示出来的差别。
|
|
33
|
+
*/
|
|
34
|
+
export function formatMonthDayClock(ts) {
|
|
35
|
+
if (!ts)
|
|
36
|
+
return '';
|
|
37
|
+
const d = new Date(ts);
|
|
38
|
+
if (isNaN(d.getTime()))
|
|
39
|
+
return '';
|
|
40
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
41
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
42
|
+
return `${mm}-${dd} ${formatClock(d.getTime())}`;
|
|
43
|
+
}
|
|
14
44
|
//# sourceMappingURL=time.js.map
|
|
@@ -10,13 +10,24 @@
|
|
|
10
10
|
* like `✅ **Bash** — pwd` so collapsed panels stay informative.
|
|
11
11
|
*/
|
|
12
12
|
import type { ToolEntry } from './run-state.js';
|
|
13
|
+
export { displayWidth, truncateMiddleToDisplayWidth, truncateToDisplayWidth, } from '../common/text-width.js';
|
|
14
|
+
/**
|
|
15
|
+
* 面板标题的显示宽度上限(半角单位,CJK/emoji 记 2)。
|
|
16
|
+
*
|
|
17
|
+
* 手机(~390px 内容宽)上折叠面板的标题只有**一行**可用:减去图标与内边距约
|
|
18
|
+
* 320px,按 ~7.5px/半角算 ≈ 42 个半角宽度(iPhone SE 更窄,超出时按 `·`/空格
|
|
19
|
+
* 折行仍可读,但不能整段换行)。超过就折行,断点常常落在 URL 或命令
|
|
20
|
+
* 中间(2026-10-19 截图反馈)。完整参数在展开后的正文里,标题只负责"哪个工具、
|
|
21
|
+
* 动了什么",并带上 `HH:mm` 时间戳(一起计入预算)。
|
|
22
|
+
*/
|
|
23
|
+
export declare const HEADER_DISPLAY_MAX = 42;
|
|
13
24
|
/**
|
|
14
25
|
* One-line header for a tool call:
|
|
15
26
|
* `⏳ **Bash** — pwd`
|
|
16
27
|
* `✅ **Read** — /repo/a.ts`
|
|
17
28
|
* `❌ **Grep** — pattern in path`
|
|
18
29
|
*/
|
|
19
|
-
export declare function toolHeaderText(tool: ToolEntry): string;
|
|
30
|
+
export declare function toolHeaderText(tool: ToolEntry, suffix?: string): string;
|
|
20
31
|
/**
|
|
21
32
|
* Structured body markdown for a tool call. Renders input fields by tool name
|
|
22
33
|
* (Bash → command, Read/Edit/Write → file_path, etc.) and output in a code
|
|
@@ -25,4 +36,6 @@ export declare function toolHeaderText(tool: ToolEntry): string;
|
|
|
25
36
|
* for unknown tools: `default` returns '' and unknown tools only surface their
|
|
26
37
|
* args through the panel header's `summarizeInput` fallback.
|
|
27
38
|
*/
|
|
28
|
-
export declare function toolBodyMd(tool: ToolEntry
|
|
39
|
+
export declare function toolBodyMd(tool: ToolEntry, opts?: {
|
|
40
|
+
outputMax?: number;
|
|
41
|
+
}): string;
|
package/dist/card/tool-render.js
CHANGED
|
@@ -10,7 +10,20 @@
|
|
|
10
10
|
* like `✅ **Bash** — pwd` so collapsed panels stay informative.
|
|
11
11
|
*/
|
|
12
12
|
import { truncate } from '../common/truncate.js';
|
|
13
|
-
|
|
13
|
+
import { asRecord, HEADER_SUMMARY_MAX, summarizeToolInput, toolKindOf, } from '../common/tool-summary.js';
|
|
14
|
+
import { truncateMiddleToDisplayWidth } from '../common/text-width.js';
|
|
15
|
+
// 这两个宽度工具原本住在这里;resume 卡片也要用,已下沉到 common。
|
|
16
|
+
export { displayWidth, truncateMiddleToDisplayWidth, truncateToDisplayWidth, } from '../common/text-width.js';
|
|
17
|
+
/**
|
|
18
|
+
* 面板标题的显示宽度上限(半角单位,CJK/emoji 记 2)。
|
|
19
|
+
*
|
|
20
|
+
* 手机(~390px 内容宽)上折叠面板的标题只有**一行**可用:减去图标与内边距约
|
|
21
|
+
* 320px,按 ~7.5px/半角算 ≈ 42 个半角宽度(iPhone SE 更窄,超出时按 `·`/空格
|
|
22
|
+
* 折行仍可读,但不能整段换行)。超过就折行,断点常常落在 URL 或命令
|
|
23
|
+
* 中间(2026-10-19 截图反馈)。完整参数在展开后的正文里,标题只负责"哪个工具、
|
|
24
|
+
* 动了什么",并带上 `HH:mm` 时间戳(一起计入预算)。
|
|
25
|
+
*/
|
|
26
|
+
export const HEADER_DISPLAY_MAX = 42;
|
|
14
27
|
const BODY_FIELD_MAX = 600;
|
|
15
28
|
const OUTPUT_MAX = 1200;
|
|
16
29
|
/**
|
|
@@ -27,11 +40,11 @@ const BODY_TOTAL_MAX = 2500;
|
|
|
27
40
|
* `✅ **Read** — /repo/a.ts`
|
|
28
41
|
* `❌ **Grep** — pattern in path`
|
|
29
42
|
*/
|
|
30
|
-
export function toolHeaderText(tool) {
|
|
43
|
+
export function toolHeaderText(tool, suffix = '') {
|
|
31
44
|
const icon = tool.status === 'ok' ? '✅' : tool.status === 'error' ? '❌' : '⏳';
|
|
32
45
|
// use the cached parsed record when available (avoids per-render parse).
|
|
33
46
|
const input = tool.parsedInput !== undefined ? tool.parsedInput : tool.input;
|
|
34
|
-
const summary =
|
|
47
|
+
const summary = summarizeToolInput(tool.name, input);
|
|
35
48
|
// 信息保真 ACP kind 映射后 name 与原始 title 不同时,title 以
|
|
36
49
|
// ` — {summary}` 追加在标题末尾(截断沿用 HEADER_SUMMARY_MAX)。
|
|
37
50
|
// 用 truncate(suffix 为空)而非裸 slice:裸 slice 按 UTF-16 码元切,
|
|
@@ -44,9 +57,14 @@ export function toolHeaderText(tool) {
|
|
|
44
57
|
// `✅ **Bash** — ls` instead of `✅ **Bash** — ls — ls`.
|
|
45
58
|
const parts = [summary, blockSummary].filter(Boolean);
|
|
46
59
|
const uniqueParts = parts.filter((part, i) => parts.indexOf(part) === i);
|
|
47
|
-
|
|
60
|
+
const title = uniqueParts.length > 0
|
|
48
61
|
? `${icon} **${tool.name}** — ${uniqueParts.join(' — ')}`
|
|
49
62
|
: `${icon} **${tool.name}**`;
|
|
63
|
+
// 折叠面板的标题必须能在一行内放下(手机窄屏):超宽就按显示宽度截断,
|
|
64
|
+
// 完整值仍在展开后的正文里。`suffix`(时间戳)一起计入预算,否则"标题压到
|
|
65
|
+
// 60 宽 + 18 宽时间戳"在手机上照样折行。
|
|
66
|
+
// 中间省略:路径/URL 的结尾(文件名)比开头更有信息量。
|
|
67
|
+
return truncateMiddleToDisplayWidth(`${title}${suffix}`, HEADER_DISPLAY_MAX);
|
|
50
68
|
}
|
|
51
69
|
/**
|
|
52
70
|
* Structured body markdown for a tool call. Renders input fields by tool name
|
|
@@ -56,13 +74,14 @@ export function toolHeaderText(tool) {
|
|
|
56
74
|
* for unknown tools: `default` returns '' and unknown tools only surface their
|
|
57
75
|
* args through the panel header's `summarizeInput` fallback.
|
|
58
76
|
*/
|
|
59
|
-
export function toolBodyMd(tool) {
|
|
77
|
+
export function toolBodyMd(tool, opts = {}) {
|
|
60
78
|
const parts = [];
|
|
61
79
|
const inputMd = renderInput(tool);
|
|
62
80
|
if (inputMd)
|
|
63
81
|
parts.push(inputMd);
|
|
64
82
|
if (tool.output) {
|
|
65
|
-
const
|
|
83
|
+
const outputMax = Math.min(opts.outputMax ?? OUTPUT_MAX, OUTPUT_MAX);
|
|
84
|
+
const truncated = truncate(tool.output, outputMax);
|
|
66
85
|
if (tool.status === 'error') {
|
|
67
86
|
parts.push(`**Error**\n\`\`\`\n${truncated}\n\`\`\``);
|
|
68
87
|
}
|
|
@@ -79,51 +98,6 @@ export function toolBodyMd(tool) {
|
|
|
79
98
|
// 同上:走 truncate 保证切点不落在代理对中间(末尾 '…' 由 truncate 附加以对齐预算)。
|
|
80
99
|
return `${truncate(body, BODY_TOTAL_MAX)}\n\n_(body 已截断,完整内容查日志)_`;
|
|
81
100
|
}
|
|
82
|
-
function summarizeInput(name, input) {
|
|
83
|
-
const rec = asRecord(input);
|
|
84
|
-
if (!rec)
|
|
85
|
-
return '';
|
|
86
|
-
const pick = (key, max = HEADER_SUMMARY_MAX) => {
|
|
87
|
-
const v = rec[key];
|
|
88
|
-
if (typeof v !== 'string')
|
|
89
|
-
return '';
|
|
90
|
-
const oneLine = v.replace(/\s+/g, ' ').trim();
|
|
91
|
-
return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine;
|
|
92
|
-
};
|
|
93
|
-
switch (name) {
|
|
94
|
-
case 'Bash':
|
|
95
|
-
case 'bash':
|
|
96
|
-
return pick('command');
|
|
97
|
-
case 'Read':
|
|
98
|
-
case 'Edit':
|
|
99
|
-
case 'Write':
|
|
100
|
-
case 'NotebookEdit':
|
|
101
|
-
case 'read':
|
|
102
|
-
case 'edit':
|
|
103
|
-
case 'write':
|
|
104
|
-
return pick('file_path') || pick('path');
|
|
105
|
-
case 'Grep':
|
|
106
|
-
case 'grep': {
|
|
107
|
-
const pat = pick('pattern', 40);
|
|
108
|
-
const p = pick('path', 30);
|
|
109
|
-
return p ? `${pat} in ${p}` : pat;
|
|
110
|
-
}
|
|
111
|
-
case 'Glob':
|
|
112
|
-
case 'find':
|
|
113
|
-
return pick('pattern');
|
|
114
|
-
case 'ls':
|
|
115
|
-
return pick('path');
|
|
116
|
-
case 'WebFetch':
|
|
117
|
-
return pick('url');
|
|
118
|
-
case 'WebSearch':
|
|
119
|
-
return pick('query', 60);
|
|
120
|
-
case 'Agent':
|
|
121
|
-
case 'Task':
|
|
122
|
-
return pick('description') || pick('subagent_type');
|
|
123
|
-
default:
|
|
124
|
-
return pick('command') || pick('file_path') || pick('path') || pick('query');
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
101
|
// --- Named render bodies (shared by tool-name cases and toolHint branches) ---
|
|
128
102
|
function renderBashInput(str) {
|
|
129
103
|
const cmd = str('command');
|
|
@@ -155,6 +129,15 @@ function renderWebFetchInput(str) {
|
|
|
155
129
|
function renderWebSearchInput(str) {
|
|
156
130
|
return str('query') ? `**Query** \`${truncate(str('query'), BODY_FIELD_MAX)}\`` : '';
|
|
157
131
|
}
|
|
132
|
+
/** DSH `web_search` 的批量查询参数(`queries: string[]`)。 */
|
|
133
|
+
function renderWebQueriesInput(queries) {
|
|
134
|
+
if (!Array.isArray(queries))
|
|
135
|
+
return '';
|
|
136
|
+
const items = queries.filter((q) => typeof q === 'string' && q.trim().length > 0);
|
|
137
|
+
if (items.length === 0)
|
|
138
|
+
return '';
|
|
139
|
+
return `**Query** \`${truncate(items.join('` / `'), BODY_FIELD_MAX)}\``;
|
|
140
|
+
}
|
|
158
141
|
function renderInput(tool) {
|
|
159
142
|
// use the cached parsed record when available (avoids per-render parse).
|
|
160
143
|
const input = tool.parsedInput !== undefined ? tool.parsedInput : tool.input;
|
|
@@ -177,48 +160,25 @@ function renderInput(tool) {
|
|
|
177
160
|
default:
|
|
178
161
|
break;
|
|
179
162
|
}
|
|
180
|
-
switch (tool.name) {
|
|
181
|
-
case '
|
|
182
|
-
case 'bash':
|
|
163
|
+
switch (toolKindOf(tool.name)) {
|
|
164
|
+
case 'shell':
|
|
183
165
|
return renderBashInput(str);
|
|
184
|
-
case '
|
|
185
|
-
case 'Edit':
|
|
186
|
-
case 'Write':
|
|
187
|
-
case 'NotebookEdit':
|
|
188
|
-
case 'read':
|
|
189
|
-
case 'edit':
|
|
190
|
-
case 'write':
|
|
166
|
+
case 'file':
|
|
191
167
|
return renderFileInput(str);
|
|
192
|
-
case 'Grep':
|
|
193
168
|
case 'grep':
|
|
194
169
|
return renderGrepInput(str);
|
|
195
|
-
case '
|
|
196
|
-
case 'find':
|
|
170
|
+
case 'glob':
|
|
197
171
|
return renderGlobInput(str);
|
|
198
172
|
case 'ls':
|
|
199
173
|
return renderLsInput(str);
|
|
200
|
-
case '
|
|
174
|
+
case 'webfetch':
|
|
201
175
|
return renderWebFetchInput(str);
|
|
202
|
-
case '
|
|
176
|
+
case 'websearch':
|
|
203
177
|
return renderWebSearchInput(str);
|
|
178
|
+
case 'websearch_queries':
|
|
179
|
+
return renderWebQueriesInput(rec.queries) || renderWebSearchInput(str);
|
|
204
180
|
default:
|
|
205
181
|
return '';
|
|
206
182
|
}
|
|
207
183
|
}
|
|
208
|
-
function asRecord(input) {
|
|
209
|
-
if (!input)
|
|
210
|
-
return null;
|
|
211
|
-
if (typeof input === 'object')
|
|
212
|
-
return input;
|
|
213
|
-
if (typeof input === 'string') {
|
|
214
|
-
try {
|
|
215
|
-
const p = JSON.parse(input);
|
|
216
|
-
return p && typeof p === 'object' ? p : null;
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
184
|
//# sourceMappingURL=tool-render.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文本的**显示宽度**工具(半角 1 / 全角与 emoji 2)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单列一个模块:卡片标题、状态行、resume 卡片标题都要按"手机上能放几行"
|
|
5
|
+
* 来收敛,而卡片层(`src/card/`)与会话读取层(`src/session/`)都要用——放在
|
|
6
|
+
* 任何一侧都会让另一侧反向依赖。
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 按**显示宽度**截断(半角 1 / 全角与 emoji 2)。
|
|
10
|
+
*
|
|
11
|
+
* markdown 的 `**` 与 emoji 都要按视觉宽度算:只按字符数截断时,一行 34 个
|
|
12
|
+
* CJK 就是 34 个字宽,手机上必然折行。
|
|
13
|
+
*/
|
|
14
|
+
export declare function truncateToDisplayWidth(text: string, maxUnits: number): string;
|
|
15
|
+
/** 字符串的显示宽度(半角 1 / 全角与 emoji 2)。 */
|
|
16
|
+
export declare function displayWidth(text: string): number;
|
|
17
|
+
/**
|
|
18
|
+
* 中间省略截断:保留开头与结尾,只在中间打 `…`。
|
|
19
|
+
*
|
|
20
|
+
* 标题里的路径/URL 必须保留**结尾**才有信息量(`~/code/notes-app/.../notes/
|
|
21
|
+
* 2026-10-19-vision-remaining.md` 里真正有用的是文件名),尾部省略会把文件名
|
|
22
|
+
* 整个切掉;纯尾部截断只适合"开头即结论"的句子(命令)。
|
|
23
|
+
*/
|
|
24
|
+
export declare function truncateMiddleToDisplayWidth(text: string, maxUnits: number): string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文本的**显示宽度**工具(半角 1 / 全角与 emoji 2)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单列一个模块:卡片标题、状态行、resume 卡片标题都要按"手机上能放几行"
|
|
5
|
+
* 来收敛,而卡片层(`src/card/`)与会话读取层(`src/session/`)都要用——放在
|
|
6
|
+
* 任何一侧都会让另一侧反向依赖。
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 按**显示宽度**截断(半角 1 / 全角与 emoji 2)。
|
|
10
|
+
*
|
|
11
|
+
* markdown 的 `**` 与 emoji 都要按视觉宽度算:只按字符数截断时,一行 34 个
|
|
12
|
+
* CJK 就是 34 个字宽,手机上必然折行。
|
|
13
|
+
*/
|
|
14
|
+
export function truncateToDisplayWidth(text, maxUnits) {
|
|
15
|
+
if (displayWidth(text) <= maxUnits)
|
|
16
|
+
return text;
|
|
17
|
+
let used = 0;
|
|
18
|
+
let out = '';
|
|
19
|
+
for (const ch of text) {
|
|
20
|
+
const w = charWidth(ch);
|
|
21
|
+
// 预留 1 个单位给省略号
|
|
22
|
+
if (used + w > maxUnits - 1)
|
|
23
|
+
break;
|
|
24
|
+
out += ch;
|
|
25
|
+
used += w;
|
|
26
|
+
}
|
|
27
|
+
return `${out}…`;
|
|
28
|
+
}
|
|
29
|
+
/** 字符串的显示宽度(半角 1 / 全角与 emoji 2)。 */
|
|
30
|
+
export function displayWidth(text) {
|
|
31
|
+
let total = 0;
|
|
32
|
+
for (const ch of text)
|
|
33
|
+
total += charWidth(ch);
|
|
34
|
+
return total;
|
|
35
|
+
}
|
|
36
|
+
function charWidth(ch) {
|
|
37
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
38
|
+
// CJK 统一表意文字、假名、全角标点、常见 emoji 都按两个半角算。
|
|
39
|
+
if (code >= 0x1100 && (code <= 0x115f || code === 0x2329 || code === 0x232a))
|
|
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
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 中间省略截断:保留开头与结尾,只在中间打 `…`。
|
|
61
|
+
*
|
|
62
|
+
* 标题里的路径/URL 必须保留**结尾**才有信息量(`~/code/notes-app/.../notes/
|
|
63
|
+
* 2026-10-19-vision-remaining.md` 里真正有用的是文件名),尾部省略会把文件名
|
|
64
|
+
* 整个切掉;纯尾部截断只适合"开头即结论"的句子(命令)。
|
|
65
|
+
*/
|
|
66
|
+
export function truncateMiddleToDisplayWidth(text, maxUnits) {
|
|
67
|
+
if (displayWidth(text) <= maxUnits)
|
|
68
|
+
return text;
|
|
69
|
+
const budget = Math.max(1, maxUnits - 1); // 给省略号留 1 个单位
|
|
70
|
+
const headBudget = Math.ceil(budget / 2);
|
|
71
|
+
const tailBudget = budget - headBudget;
|
|
72
|
+
let head = '';
|
|
73
|
+
let used = 0;
|
|
74
|
+
for (const ch of text) {
|
|
75
|
+
const w = charWidth(ch);
|
|
76
|
+
if (used + w > headBudget)
|
|
77
|
+
break;
|
|
78
|
+
head += ch;
|
|
79
|
+
used += w;
|
|
80
|
+
}
|
|
81
|
+
let tail = '';
|
|
82
|
+
used = 0;
|
|
83
|
+
const chars = [...text];
|
|
84
|
+
for (let i = chars.length - 1; i >= 0; i--) {
|
|
85
|
+
const w = charWidth(chars[i]);
|
|
86
|
+
if (used + w > tailBudget)
|
|
87
|
+
break;
|
|
88
|
+
tail = chars[i] + tail;
|
|
89
|
+
used += w;
|
|
90
|
+
}
|
|
91
|
+
return `${head}…${tail}`;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=text-width.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具调用参数 → 一行人话摘要(`bash — npm test`、`read — src/a.ts`)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要抽到 common:run 卡的面板标题(`src/card/tool-render.ts`)和
|
|
5
|
+
* resume 卡的「🔧 工具调用」标题(`src/session/<agent>/sessions.ts`)都要同一套口径;
|
|
6
|
+
* 各写一份必然漂移(同一个 `glob` 在一处显示 pattern、另一处显示目录)。
|
|
7
|
+
*
|
|
8
|
+
* 只做"取哪个字段",不做 agent 分支:工具名与参数名的对应表就是这张 switch。
|
|
9
|
+
*/
|
|
10
|
+
/** 工具标题/摘要共用的单行宽度上限。 */
|
|
11
|
+
export declare const HEADER_SUMMARY_MAX = 80;
|
|
12
|
+
/**
|
|
13
|
+
* 工具别名 → 渲染类别。run 卡面板(`card/tool-render.ts`)与摘要
|
|
14
|
+
* (本文件)共用这一张表,新增别名只改这里,避免两处 switch 漂移。
|
|
15
|
+
*/
|
|
16
|
+
export type ToolKind = 'shell' | 'file' | 'grep' | 'glob' | 'ls' | 'webfetch' | 'websearch' | 'websearch_queries';
|
|
17
|
+
export declare function toolKindOf(name: string): ToolKind | undefined;
|
|
18
|
+
export declare function asRecord(input: unknown): Record<string, unknown> | null;
|
|
19
|
+
export declare function summarizeToolInput(name: string, input: unknown): string;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具调用参数 → 一行人话摘要(`bash — npm test`、`read — src/a.ts`)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要抽到 common:run 卡的面板标题(`src/card/tool-render.ts`)和
|
|
5
|
+
* resume 卡的「🔧 工具调用」标题(`src/session/<agent>/sessions.ts`)都要同一套口径;
|
|
6
|
+
* 各写一份必然漂移(同一个 `glob` 在一处显示 pattern、另一处显示目录)。
|
|
7
|
+
*
|
|
8
|
+
* 只做"取哪个字段",不做 agent 分支:工具名与参数名的对应表就是这张 switch。
|
|
9
|
+
*/
|
|
10
|
+
import { truncate } from './truncate.js';
|
|
11
|
+
/** 工具标题/摘要共用的单行宽度上限。 */
|
|
12
|
+
export const HEADER_SUMMARY_MAX = 80;
|
|
13
|
+
const TOOL_KIND_BY_NAME = {
|
|
14
|
+
Bash: 'shell',
|
|
15
|
+
bash: 'shell',
|
|
16
|
+
pwsh: 'shell',
|
|
17
|
+
Read: 'file',
|
|
18
|
+
Edit: 'file',
|
|
19
|
+
Write: 'file',
|
|
20
|
+
NotebookEdit: 'file',
|
|
21
|
+
read: 'file',
|
|
22
|
+
edit: 'file',
|
|
23
|
+
write: 'file',
|
|
24
|
+
read_image: 'file',
|
|
25
|
+
Grep: 'grep',
|
|
26
|
+
grep: 'grep',
|
|
27
|
+
Glob: 'glob',
|
|
28
|
+
glob: 'glob',
|
|
29
|
+
find: 'glob',
|
|
30
|
+
ls: 'ls',
|
|
31
|
+
WebFetch: 'webfetch',
|
|
32
|
+
web_fetch: 'webfetch',
|
|
33
|
+
WebSearch: 'websearch',
|
|
34
|
+
web_search: 'websearch_queries',
|
|
35
|
+
};
|
|
36
|
+
export function toolKindOf(name) {
|
|
37
|
+
return TOOL_KIND_BY_NAME[name];
|
|
38
|
+
}
|
|
39
|
+
export function asRecord(input) {
|
|
40
|
+
if (!input)
|
|
41
|
+
return null;
|
|
42
|
+
if (typeof input === 'object')
|
|
43
|
+
return input;
|
|
44
|
+
if (typeof input === 'string') {
|
|
45
|
+
try {
|
|
46
|
+
const p = JSON.parse(input);
|
|
47
|
+
return p && typeof p === 'object' ? p : null;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
export function summarizeToolInput(name, input) {
|
|
56
|
+
const rec = asRecord(input);
|
|
57
|
+
if (!rec)
|
|
58
|
+
return '';
|
|
59
|
+
const pick = (key, max = HEADER_SUMMARY_MAX) => {
|
|
60
|
+
const v = rec[key];
|
|
61
|
+
if (typeof v !== 'string')
|
|
62
|
+
return '';
|
|
63
|
+
const oneLine = v.replace(/\s+/g, ' ').trim();
|
|
64
|
+
return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine;
|
|
65
|
+
};
|
|
66
|
+
/** First non-empty of several keys(DSH 同名工具跨版本改过参数名)。 */
|
|
67
|
+
const pickAny = (keys, max = HEADER_SUMMARY_MAX) => {
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
const v = pick(key, max);
|
|
70
|
+
if (v)
|
|
71
|
+
return v;
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
};
|
|
75
|
+
/** 数组参数的第一个字符串项(DSH web_search 的 queries、present 的 files)。 */
|
|
76
|
+
const pickFirst = (key, max = HEADER_SUMMARY_MAX) => {
|
|
77
|
+
const v = rec[key];
|
|
78
|
+
if (typeof v === 'string')
|
|
79
|
+
return pick(key, max);
|
|
80
|
+
if (!Array.isArray(v))
|
|
81
|
+
return '';
|
|
82
|
+
for (const item of v) {
|
|
83
|
+
if (typeof item === 'string' && item.trim()) {
|
|
84
|
+
const oneLine = item.replace(/\s+/g, ' ').trim();
|
|
85
|
+
return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine;
|
|
86
|
+
}
|
|
87
|
+
if (item && typeof item === 'object') {
|
|
88
|
+
const path = item.path;
|
|
89
|
+
if (typeof path === 'string' && path.trim())
|
|
90
|
+
return path;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return '';
|
|
94
|
+
};
|
|
95
|
+
switch (toolKindOf(name)) {
|
|
96
|
+
case 'shell':
|
|
97
|
+
return pick('command');
|
|
98
|
+
case 'file':
|
|
99
|
+
return pick('file_path') || pick('path');
|
|
100
|
+
case 'grep': {
|
|
101
|
+
const pat = pick('pattern', 40);
|
|
102
|
+
const p = pick('path', 30);
|
|
103
|
+
return p ? `${pat} in ${p}` : pat;
|
|
104
|
+
}
|
|
105
|
+
case 'glob':
|
|
106
|
+
return pick('pattern');
|
|
107
|
+
case 'ls':
|
|
108
|
+
return pick('path');
|
|
109
|
+
case 'webfetch':
|
|
110
|
+
return pick('url');
|
|
111
|
+
case 'websearch':
|
|
112
|
+
case 'websearch_queries':
|
|
113
|
+
return pickFirst('queries', 60) || pick('query', 60);
|
|
114
|
+
default:
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
switch (name) {
|
|
118
|
+
case 'Agent':
|
|
119
|
+
case 'Task':
|
|
120
|
+
case 'subagent':
|
|
121
|
+
case 'workflow':
|
|
122
|
+
return pick('description') || pick('subagent_type');
|
|
123
|
+
case 'todo_write':
|
|
124
|
+
return summarizeTodos(rec);
|
|
125
|
+
case 'job_output':
|
|
126
|
+
case 'job_kill':
|
|
127
|
+
return pick('job_id');
|
|
128
|
+
case 'job_list':
|
|
129
|
+
return '(列出任务)';
|
|
130
|
+
case 'present':
|
|
131
|
+
return pickFirst('files');
|
|
132
|
+
case 'skill':
|
|
133
|
+
return pick('name');
|
|
134
|
+
case 'run_code':
|
|
135
|
+
return pick('description') || pick('language');
|
|
136
|
+
case 'terminal_send':
|
|
137
|
+
return pick('text');
|
|
138
|
+
case 'ask_user_question': {
|
|
139
|
+
const questions = rec.questions;
|
|
140
|
+
return Array.isArray(questions) ? `${questions.length} 个问题` : '';
|
|
141
|
+
}
|
|
142
|
+
case 'send_message':
|
|
143
|
+
case 'interrupt_agent':
|
|
144
|
+
return pick('description') || pick('agent_id');
|
|
145
|
+
default:
|
|
146
|
+
return pickAny(['command', 'file_path', 'path', 'query', 'url']);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** todo_write 摘要:完成进度 + 当前 in_progress 项主题。 */
|
|
150
|
+
function summarizeTodos(rec) {
|
|
151
|
+
const todos = rec.todos;
|
|
152
|
+
if (!Array.isArray(todos))
|
|
153
|
+
return '';
|
|
154
|
+
let done = 0;
|
|
155
|
+
let active = '';
|
|
156
|
+
for (const item of todos) {
|
|
157
|
+
if (!item || typeof item !== 'object')
|
|
158
|
+
continue;
|
|
159
|
+
const todo = item;
|
|
160
|
+
if (todo.status === 'completed')
|
|
161
|
+
done += 1;
|
|
162
|
+
if (!active && todo.status === 'in_progress' && typeof todo.content === 'string') {
|
|
163
|
+
active = todo.content.replace(/\s+/g, ' ').trim();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const progress = `${done}/${todos.length}`;
|
|
167
|
+
if (!active)
|
|
168
|
+
return `已完成 ${progress}`;
|
|
169
|
+
return truncate(`已完成 ${progress} · ${active}`, HEADER_SUMMARY_MAX, { suffix: '…' });
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=tool-summary.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent turn 失败原因的判据(卡片渲染与自动续跑共用)。
|
|
3
|
+
*
|
|
4
|
+
* 上游把 `finish_reason: content_filter` 归一到普通 error(DSH 侧 code 为
|
|
5
|
+
* `PI_AI_ERROR`):它既不是限流也不是故障,而是模型输出被服务方安全策略截断,
|
|
6
|
+
* 多数是误报。桥接若把它混进「运行出错」,用户看到的是英文内部报文、不知道
|
|
7
|
+
* 该做什么;桥侧也就无从判断该不该自动续跑。
|
|
8
|
+
*
|
|
9
|
+
* 判据只留一处:渲染层(换文案)与桥接层(自动续跑一次)必须同一个口径,
|
|
10
|
+
* 否则改文案和改重试语义会各自漂移。
|
|
11
|
+
*/
|
|
12
|
+
/** 该 errorMsg 是否表示「模型输出被上游内容审核截断」。 */
|
|
13
|
+
export declare function isContentFilterFailure(errorMsg: string | undefined): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent turn 失败原因的判据(卡片渲染与自动续跑共用)。
|
|
3
|
+
*
|
|
4
|
+
* 上游把 `finish_reason: content_filter` 归一到普通 error(DSH 侧 code 为
|
|
5
|
+
* `PI_AI_ERROR`):它既不是限流也不是故障,而是模型输出被服务方安全策略截断,
|
|
6
|
+
* 多数是误报。桥接若把它混进「运行出错」,用户看到的是英文内部报文、不知道
|
|
7
|
+
* 该做什么;桥侧也就无从判断该不该自动续跑。
|
|
8
|
+
*
|
|
9
|
+
* 判据只留一处:渲染层(换文案)与桥接层(自动续跑一次)必须同一个口径,
|
|
10
|
+
* 否则改文案和改重试语义会各自漂移。
|
|
11
|
+
*/
|
|
12
|
+
/** 服务方内容审核截断的报文特征:`Provider finish_reason: content_filter`。 */
|
|
13
|
+
const CONTENT_FILTER_RE = /finish_reason:\s*["']?content_filter\b/i;
|
|
14
|
+
/** 该 errorMsg 是否表示「模型输出被上游内容审核截断」。 */
|
|
15
|
+
export function isContentFilterFailure(errorMsg) {
|
|
16
|
+
return typeof errorMsg === 'string' && CONTENT_FILTER_RE.test(errorMsg);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=turn-failure.js.map
|
package/dist/config/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare const DEFAULT_TURN_IDLE_TIMEOUT_MINUTES: 30;
|
|
|
28
28
|
* 单文件上限默认 100MB:50MB 对视频偏小(2026-09-15 事故就是 mp4 被丢弃)。
|
|
29
29
|
* 注意飞书侧 `type=file` 超过 100MB 必须用 Range 分片下载,而 SDK 的
|
|
30
30
|
* `downloadResourceToFile` 不分片(必报 234037),所以 100 是实际可用上限
|
|
31
|
-
* (inbound-message-matrix.md §4
|
|
31
|
+
* (inbound-message-matrix.md §4 第 4 条)。
|
|
32
32
|
*/
|
|
33
33
|
export declare const DEFAULT_INBOUND_MEDIA_DIR_NAME = ".lark-remote-temp";
|
|
34
34
|
export declare const DEFAULT_INBOUND_MEDIA_MAX_SIZE_MB = 100;
|
package/dist/config/index.js
CHANGED
|
@@ -56,7 +56,7 @@ export const DEFAULT_TURN_IDLE_TIMEOUT_MINUTES = DEFAULTS.TURN_IDLE_TIMEOUT_MINU
|
|
|
56
56
|
* 单文件上限默认 100MB:50MB 对视频偏小(2026-09-15 事故就是 mp4 被丢弃)。
|
|
57
57
|
* 注意飞书侧 `type=file` 超过 100MB 必须用 Range 分片下载,而 SDK 的
|
|
58
58
|
* `downloadResourceToFile` 不分片(必报 234037),所以 100 是实际可用上限
|
|
59
|
-
* (inbound-message-matrix.md §4
|
|
59
|
+
* (inbound-message-matrix.md §4 第 4 条)。
|
|
60
60
|
*/
|
|
61
61
|
export const DEFAULT_INBOUND_MEDIA_DIR_NAME = '.lark-remote-temp';
|
|
62
62
|
export const DEFAULT_INBOUND_MEDIA_MAX_SIZE_MB = 100;
|
package/dist/connector/index.js
CHANGED
|
@@ -424,7 +424,7 @@ export class FeishuConnector {
|
|
|
424
424
|
kind: res.kind,
|
|
425
425
|
fileName: res.fileName,
|
|
426
426
|
// 飞书侧 >100MB 必须 Range 分片(SDK 不分片),到上限就是下不动:
|
|
427
|
-
// 提示写清楚,避免用户以为"再放大上限就能存"(matrix §4
|
|
427
|
+
// 提示写清楚,避免用户以为"再放大上限就能存"(inbound-message-matrix.md §4 第 4 条)。
|
|
428
428
|
reason: `超过 ${limitMb}MB 大小限制` +
|
|
429
429
|
(limitMb >= 100 ? '(飞书 >100MB 的文件需分片下载,暂不支持)' : ''),
|
|
430
430
|
});
|