lark-remote 0.1.13 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.en.md +2 -6
- package/README.md +3 -7
- package/dist/bridge/final-usage.d.ts +24 -0
- package/dist/bridge/final-usage.js +55 -0
- package/dist/bridge/index.d.ts +0 -7
- package/dist/bridge/index.js +21 -63
- package/dist/bridge/queue-manager.js +4 -3
- package/dist/card/approval-render.js +39 -94
- package/dist/card/bash-renderer.js +12 -21
- package/dist/card/card-shared.d.ts +13 -1
- package/dist/card/card-shared.js +21 -30
- package/dist/card/run-renderer.d.ts +0 -22
- package/dist/card/run-renderer.js +206 -153
- package/dist/card/run-state.d.ts +29 -1
- package/dist/card/run-state.js +172 -84
- package/dist/card/text-truncate.d.ts +0 -9
- package/dist/card/text-truncate.js +0 -9
- package/dist/card/tool-render.d.ts +4 -1
- package/dist/card/tool-render.js +76 -32
- package/dist/cli.js +2 -2
- package/dist/common/fs.d.ts +14 -0
- package/dist/common/fs.js +57 -3
- package/dist/config/codex-config.js +2 -0
- package/dist/config/index.d.ts +0 -5
- package/dist/config/index.js +0 -11
- package/dist/config/kimi-config.js +1 -0
- package/dist/config/opencode-config.js +13 -2
- package/dist/config/qr.d.ts +36 -0
- package/dist/config/qr.js +124 -0
- package/dist/config/wizard.js +69 -27
- package/dist/connector/index.d.ts +3 -2
- package/dist/connector/index.js +5 -3
- package/dist/index.js +14 -41
- package/dist/instance-lock.js +7 -0
- package/dist/persistence/atomic-write.d.ts +2 -0
- package/dist/persistence/atomic-write.js +20 -5
- package/dist/persistence/load-json-file.d.ts +1 -2
- package/dist/persistence/load-json-file.js +2 -3
- package/dist/platform/agent-stopper.d.ts +22 -0
- package/dist/platform/agent-stopper.js +19 -0
- package/dist/platform/command.d.ts +11 -0
- package/dist/platform/command.js +107 -0
- package/dist/platform/identity.d.ts +33 -0
- package/dist/platform/identity.js +187 -0
- package/dist/platform/path.d.ts +24 -0
- package/dist/platform/path.js +110 -0
- package/dist/platform/select.d.ts +9 -0
- package/dist/platform/select.js +12 -0
- package/dist/platform/shell.d.ts +51 -0
- package/dist/platform/shell.js +75 -0
- package/dist/platform/spawn.d.ts +41 -0
- package/dist/platform/spawn.js +77 -0
- package/dist/platform/terminator-posix.d.ts +38 -0
- package/dist/platform/terminator-posix.js +103 -0
- package/dist/platform/terminator-win32.d.ts +21 -0
- package/dist/platform/terminator-win32.js +110 -0
- package/dist/platform/terminator.d.ts +44 -0
- package/dist/platform/terminator.js +24 -0
- package/dist/platform/types.d.ts +23 -0
- package/dist/platform/types.js +5 -0
- package/dist/restart.js +4 -2
- package/dist/router/card-helpers.d.ts +33 -6
- package/dist/router/card-helpers.js +67 -11
- package/dist/router/config/common/render.d.ts +1 -1
- package/dist/router/config/common/render.js +1 -1
- package/dist/router/config-diff.d.ts +4 -0
- package/dist/router/config-diff.js +70 -0
- package/dist/router/index.d.ts +66 -6
- package/dist/router/index.js +450 -315
- package/dist/router/utils.d.ts +6 -0
- package/dist/router/utils.js +12 -0
- package/dist/runner/bash/runner.d.ts +9 -2
- package/dist/runner/bash/runner.js +45 -20
- package/dist/runner/claude/session.d.ts +4 -1
- package/dist/runner/claude/session.js +18 -4
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
- package/dist/runner/codex/app-server/translator.d.ts +18 -0
- package/dist/runner/codex/app-server/translator.js +146 -0
- package/dist/runner/common/acp/base-acp-runner.d.ts +116 -0
- package/dist/runner/common/acp/base-acp-runner.js +248 -0
- package/dist/runner/common/acp/base-translator.d.ts +141 -0
- package/dist/runner/common/acp/base-translator.js +271 -0
- package/dist/runner/common/acp/protocol-helpers.d.ts +39 -0
- package/dist/runner/common/acp/protocol-helpers.js +34 -0
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -0
- package/dist/runner/common/jsonrpc/transport.js +25 -5
- package/dist/runner/common/process-stopper.d.ts +4 -26
- package/dist/runner/common/process-stopper.js +4 -76
- package/dist/runner/common/runner-utils.d.ts +0 -5
- package/dist/runner/common/runner-utils.js +0 -7
- package/dist/runner/common/spawning-runner.d.ts +42 -132
- package/dist/runner/common/spawning-runner.js +52 -271
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +45 -42
- package/dist/runner/kimi/acp/runner.js +140 -293
- package/dist/runner/kimi/acp/translator.d.ts +18 -100
- package/dist/runner/kimi/acp/translator.js +56 -242
- package/dist/runner/opencode/acp/runner.d.ts +22 -46
- package/dist/runner/opencode/acp/runner.js +73 -256
- package/dist/runner/opencode/acp/translator.d.ts +28 -103
- package/dist/runner/opencode/acp/translator.js +51 -236
- package/dist/runner/pi/rpc/translator.d.ts +3 -0
- package/dist/runner/pi/rpc/translator.js +47 -9
- package/dist/runner/probe.d.ts +8 -11
- package/dist/runner/probe.js +17 -21
- package/dist/runner/registry.d.ts +4 -4
- package/dist/runner/registry.js +4 -4
- package/dist/runner/types.d.ts +38 -1
- package/dist/session/claude/sessions.js +34 -57
- package/dist/session/codex/rollout-reader.d.ts +10 -0
- package/dist/session/codex/rollout-reader.js +123 -41
- package/dist/session/common/constants.d.ts +7 -0
- package/dist/session/common/constants.js +9 -0
- package/dist/session/common/two-pass.d.ts +22 -0
- package/dist/session/common/two-pass.js +70 -0
- package/dist/session/dsh/sessions.js +6 -5
- package/dist/session/kimi/sessions.d.ts +6 -0
- package/dist/session/kimi/sessions.js +50 -73
- package/dist/session/opencode/sessions.js +26 -8
- package/dist/session/pi/sessions.js +54 -73
- package/dist/update/index.d.ts +1 -2
- package/dist/update/index.js +1 -2
- package/dist/update/install.d.ts +2 -1
- package/dist/update/install.js +58 -9
- package/dist/update/version-check.d.ts +5 -0
- package/dist/update/version-check.js +9 -0
- package/package.json +9 -6
- package/dist/update/startup-hint.d.ts +0 -5
- package/dist/update/startup-hint.js +0 -11
|
@@ -7,9 +7,21 @@ import { type AgentKind } from '../runner/index.js';
|
|
|
7
7
|
* wire up the registry (notably unit tests of pure render helpers).
|
|
8
8
|
*/
|
|
9
9
|
export declare function agentDisplayName(kind: string): string;
|
|
10
|
-
export { truncate } from '../common/truncate.js';
|
|
11
10
|
export declare function terminalToLabel(terminal: string): string;
|
|
12
11
|
export declare function terminalToColor(terminal: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Create a CardKit 2.0 button with a callback behavior. Shared constructor
|
|
14
|
+
* for every callback button in the card module (stop/new-session/compact/
|
|
15
|
+
* approval decisions/question options/…) — previously each render site
|
|
16
|
+
* hand-rolled the `{tag, text, type, behaviors}` object and they had drifted
|
|
17
|
+
* (plain_text tag sometimes present, sometimes omitted).
|
|
18
|
+
*/
|
|
19
|
+
export declare function cardButton(text: string, value: Record<string, unknown>, opts?: {
|
|
20
|
+
type?: 'primary' | 'default' | 'danger';
|
|
21
|
+
size?: 'small' | 'medium' | 'large';
|
|
22
|
+
/** Fixed width, e.g. '100px' (keeps option-row buttons left-aligned). */
|
|
23
|
+
width?: string;
|
|
24
|
+
}): object;
|
|
13
25
|
/**
|
|
14
26
|
* Create a CardKit 2.0 stop button with behaviors callback.
|
|
15
27
|
*/
|
package/dist/card/card-shared.js
CHANGED
|
@@ -25,7 +25,6 @@ export function agentDisplayName(kind) {
|
|
|
25
25
|
return 'Kimi';
|
|
26
26
|
return kind;
|
|
27
27
|
}
|
|
28
|
-
export { truncate } from '../common/truncate.js';
|
|
29
28
|
const TERMINAL_LABELS = {
|
|
30
29
|
done: '已完成',
|
|
31
30
|
error: '出错',
|
|
@@ -53,27 +52,34 @@ export function terminalToColor(terminal) {
|
|
|
53
52
|
return 'red';
|
|
54
53
|
}
|
|
55
54
|
/**
|
|
56
|
-
* Create a CardKit 2.0
|
|
55
|
+
* Create a CardKit 2.0 button with a callback behavior. Shared constructor
|
|
56
|
+
* for every callback button in the card module (stop/new-session/compact/
|
|
57
|
+
* approval decisions/question options/…) — previously each render site
|
|
58
|
+
* hand-rolled the `{tag, text, type, behaviors}` object and they had drifted
|
|
59
|
+
* (plain_text tag sometimes present, sometimes omitted).
|
|
57
60
|
*/
|
|
58
|
-
export function
|
|
61
|
+
export function cardButton(text, value, opts = {}) {
|
|
59
62
|
return {
|
|
60
63
|
tag: 'button',
|
|
61
|
-
text: {
|
|
62
|
-
type:
|
|
63
|
-
|
|
64
|
+
text: { tag: 'plain_text', content: text },
|
|
65
|
+
...(opts.type ? { type: opts.type } : {}),
|
|
66
|
+
...(opts.size ? { size: opts.size } : {}),
|
|
67
|
+
...(opts.width ? { width: opts.width } : {}),
|
|
68
|
+
behaviors: [{ type: 'callback', value }],
|
|
64
69
|
};
|
|
65
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a CardKit 2.0 stop button with behaviors callback.
|
|
73
|
+
*/
|
|
74
|
+
export function stopButton(runId) {
|
|
75
|
+
return cardButton('⏹ 停止', { cmd: 'stop', runId }, { type: 'danger' });
|
|
76
|
+
}
|
|
66
77
|
/**
|
|
67
78
|
* Create a CardKit 2.0 "new session" button with behaviors callback.
|
|
68
79
|
* Standardized: ✨ 新会话 + primary type
|
|
69
80
|
*/
|
|
70
81
|
export function newSessionButton() {
|
|
71
|
-
return {
|
|
72
|
-
tag: 'button',
|
|
73
|
-
text: { content: '✨ 新会话' },
|
|
74
|
-
type: 'primary',
|
|
75
|
-
behaviors: [{ type: 'callback', value: { cmd: 'new-session' } }],
|
|
76
|
-
};
|
|
82
|
+
return cardButton('✨ 新会话', { cmd: 'new-session' }, { type: 'primary' });
|
|
77
83
|
}
|
|
78
84
|
/**
|
|
79
85
|
* Create a CardKit 2.0 Compact button for any runCompact-capable runner
|
|
@@ -82,12 +88,7 @@ export function newSessionButton() {
|
|
|
82
88
|
* runId 解析 agentKind,claude 走 stream-json 内建 /compact)。
|
|
83
89
|
*/
|
|
84
90
|
export function compactButton(runId) {
|
|
85
|
-
return {
|
|
86
|
-
tag: 'button',
|
|
87
|
-
text: { content: '🗜 Compact' },
|
|
88
|
-
type: 'primary',
|
|
89
|
-
behaviors: [{ type: 'callback', value: { cmd: 'codex.compact', runId } }],
|
|
90
|
-
};
|
|
91
|
+
return cardButton('🗜 Compact', { cmd: 'codex.compact', runId }, { type: 'primary' });
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
93
94
|
* Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
|
|
@@ -98,19 +99,9 @@ export function compactButton(runId) {
|
|
|
98
99
|
* non-default agent.
|
|
99
100
|
*/
|
|
100
101
|
export function resumeCompactButton(sessionId, agent) {
|
|
101
|
-
return {
|
|
102
|
-
tag: 'button',
|
|
103
|
-
text: { content: '🗜 Compact' },
|
|
104
|
-
type: 'primary',
|
|
105
|
-
behaviors: [{ type: 'callback', value: { cmd: 'resume.compact', sessionId, agent } }],
|
|
106
|
-
};
|
|
102
|
+
return cardButton('🗜 Compact', { cmd: 'resume.compact', sessionId, agent }, { type: 'primary' });
|
|
107
103
|
}
|
|
108
104
|
export function resumeUseButton(sessionId, agent) {
|
|
109
|
-
return {
|
|
110
|
-
tag: 'button',
|
|
111
|
-
text: { tag: 'plain_text', content: '🔁 切换到此会话' },
|
|
112
|
-
type: 'primary',
|
|
113
|
-
behaviors: [{ type: 'callback', value: { cmd: 'resume.use', sessionId, agent } }],
|
|
114
|
-
};
|
|
105
|
+
return cardButton('🔁 切换到此会话', { cmd: 'resume.use', sessionId, agent }, { type: 'primary' });
|
|
115
106
|
}
|
|
116
107
|
//# sourceMappingURL=card-shared.js.map
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { RunState, ToolEntry } from './run-state.js';
|
|
2
2
|
export interface RunCardRenderOptions {
|
|
3
|
-
showThinking?: boolean;
|
|
4
|
-
showToolUse?: boolean;
|
|
5
|
-
showToolResult?: boolean;
|
|
6
3
|
/**
|
|
7
4
|
* Agent kind for the run card header title (e.g. "Claude · 思考中").
|
|
8
5
|
* Defaults to 'claude' when not provided; the bridge passes `config.defaultAgent`.
|
|
@@ -42,25 +39,6 @@ type BlockGroup = {
|
|
|
42
39
|
};
|
|
43
40
|
/** 每个块的截断后内容(thinking/plan/text),测量与渲染共用(P1-2 建议项)。 */
|
|
44
41
|
type GroupContentPrepared = Map<BlockGroup, string>;
|
|
45
|
-
/**
|
|
46
|
-
* 影子测量:精确复刻 normal 路径(buildChronologicalContent + renderRunCard 的
|
|
47
|
-
* 骨架)产物 stringify 后的字节数,避免"先建完整卡再丢弃"的浪费。
|
|
48
|
-
*
|
|
49
|
-
* 第一性原理:估算应当测量"将要渲染的内容",而非近似它。
|
|
50
|
-
* - 骨架(schema/config/header/statusRow/summary/buttons/spacer)用小对象实测
|
|
51
|
-
* (measureJson),不依赖固定常数。
|
|
52
|
-
* - 块内容经与渲染完全相同的变换后精确测量:truncateUtf8 截断 →
|
|
53
|
-
* markdownDiv 的 escapeMarkdown(反斜杠 2×)→ JSON.stringify。因此 CJK
|
|
54
|
-
* (3 字节/字符无转义)与 ASCII 高转义(\n/" 膨胀)双向精确,无需转义因子。
|
|
55
|
-
* - tool 块直接 measureJson(renderTool(...))——与渲染共用同一函数,无复制。
|
|
56
|
-
* - 块结构基于 groupBlocks(与渲染一致,连续 text 合并),保证合并语义一致。
|
|
57
|
-
*
|
|
58
|
-
* 唯一系统性低估:elements 数组元素间逗号/括号 ~n+1 字节(忽略,4KB 裕度内)。
|
|
59
|
-
*
|
|
60
|
-
* 注意:估算只是优化路径选择,不替代最终 stringify 兜底。即使估算说"低于阈值"
|
|
61
|
-
* 走正常路径,renderRunCard 仍会 stringify 确认 ≤28KB;若估算低估,仍 fallback
|
|
62
|
-
* 到 degraded(安全网不丢)。
|
|
63
|
-
*/
|
|
64
42
|
/** 导出供测试断言估算精度(估算 ≈ 实际渲染字节,见 tests/anchor/run-card/run-card.test.ts)。 */
|
|
65
43
|
export declare function estimateCardBytes(state: RunState, options?: RunCardRenderOptions, shared?: {
|
|
66
44
|
groups?: BlockGroup[];
|
|
@@ -95,6 +95,58 @@ function renderTextBlock(content, timestamp, finalized = false) {
|
|
|
95
95
|
}),
|
|
96
96
|
];
|
|
97
97
|
}
|
|
98
|
+
/** 运行期通知作为普通消息渲染:与内容流同级的一行斜体提示。 */
|
|
99
|
+
function renderNoticeRow(notice) {
|
|
100
|
+
const icon = notice.level === 'info' ? 'ℹ️' : '⚠️';
|
|
101
|
+
return markdownDiv(`_${icon} ${notice.text}_`);
|
|
102
|
+
}
|
|
103
|
+
/** 内容组用于时间线插值的锚点时刻(与面板标题展示的时间戳一致)。 */
|
|
104
|
+
function groupTimeMs(group) {
|
|
105
|
+
if (group.kind === 'tool') {
|
|
106
|
+
const ts = group.tool.completedAt ?? group.tool.startedAt;
|
|
107
|
+
return ts ? Date.parse(ts) : undefined;
|
|
108
|
+
}
|
|
109
|
+
return group.timestamp ? Date.parse(group.timestamp) : undefined;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 把 notices 作为普通消息并入内容时间线:按到达时间插到对应内容组之间。
|
|
113
|
+
* 内容组自身的相对顺序不被改动(仍由 run-state 的块数组决定),时间戳只决定
|
|
114
|
+
* notice 的落点;无时间戳的 notice 无法定位,兜底置于内容流末尾。
|
|
115
|
+
*/
|
|
116
|
+
function mergeNoticesIntoContent(groups, notices) {
|
|
117
|
+
if (!notices || notices.length === 0)
|
|
118
|
+
return groups;
|
|
119
|
+
const timed = [];
|
|
120
|
+
const untimed = [];
|
|
121
|
+
for (const notice of notices) {
|
|
122
|
+
if (notice.timestamp)
|
|
123
|
+
timed.push(notice);
|
|
124
|
+
else
|
|
125
|
+
untimed.push(notice);
|
|
126
|
+
}
|
|
127
|
+
const items = [];
|
|
128
|
+
let cursor = 0;
|
|
129
|
+
for (const notice of timed) {
|
|
130
|
+
const t = Date.parse(notice.timestamp);
|
|
131
|
+
if (!Number.isNaN(t)) {
|
|
132
|
+
while (cursor < groups.length &&
|
|
133
|
+
groupTimeMs(groups[cursor]) !== undefined &&
|
|
134
|
+
groupTimeMs(groups[cursor]) <= t) {
|
|
135
|
+
items.push(groups[cursor]);
|
|
136
|
+
cursor++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
items.push({ kind: 'notice', notice });
|
|
140
|
+
}
|
|
141
|
+
while (cursor < groups.length) {
|
|
142
|
+
items.push(groups[cursor]);
|
|
143
|
+
cursor++;
|
|
144
|
+
}
|
|
145
|
+
for (const notice of untimed) {
|
|
146
|
+
items.push({ kind: 'notice', notice });
|
|
147
|
+
}
|
|
148
|
+
return items;
|
|
149
|
+
}
|
|
98
150
|
/**
|
|
99
151
|
* 单次截断:把每个超限块(thinking/plan/text)的最终渲染内容算出来,供
|
|
100
152
|
* estimateCardBytes 与 buildChronologicalContent 共用。此前影子测量与正式渲染
|
|
@@ -180,9 +232,9 @@ function groupBlocks(blocks) {
|
|
|
180
232
|
* - Running tool: expanded (user can watch live)
|
|
181
233
|
* - Completed tool: collapsed (click to inspect)
|
|
182
234
|
*/
|
|
183
|
-
function renderTool(tool, finalized
|
|
235
|
+
function renderTool(tool, finalized) {
|
|
184
236
|
const border = tool.status === 'error' ? 'red' : 'grey';
|
|
185
|
-
const body =
|
|
237
|
+
const body = toolBodyMd(tool);
|
|
186
238
|
return collapsibleMarkdownPanel({
|
|
187
239
|
title: toolTitle(tool),
|
|
188
240
|
expanded: !finalized && tool.status === 'running',
|
|
@@ -208,8 +260,8 @@ const EXTREME_TIER = {
|
|
|
208
260
|
toolKeep: EXTREME_TOOL_KEEP,
|
|
209
261
|
textBytes: DEGRADED_TEXT_BYTES,
|
|
210
262
|
};
|
|
211
|
-
/** Bottom action
|
|
212
|
-
function
|
|
263
|
+
/** Bottom action buttons: stop (if running) + compact (if applicable) + new session (always). */
|
|
264
|
+
function actionButtonList(state, options) {
|
|
213
265
|
const actionButtons = [];
|
|
214
266
|
// finalizing 也显示停止按钮(进程未退出,用户可 /stop)
|
|
215
267
|
const showStop = state.terminal === 'running' || state.terminal === 'finalizing';
|
|
@@ -221,6 +273,11 @@ function actionRow(state, options) {
|
|
|
221
273
|
actionButtons.push(compactButton(state.runId));
|
|
222
274
|
}
|
|
223
275
|
actionButtons.push(newSessionButton());
|
|
276
|
+
return actionButtons;
|
|
277
|
+
}
|
|
278
|
+
/** Bottom action row: spacer + column_set of actionButtonList (估算与渲染共用,W2.2)。 */
|
|
279
|
+
function actionRow(state, options) {
|
|
280
|
+
const actionButtons = actionButtonList(state, options);
|
|
224
281
|
if (actionButtons.length === 0)
|
|
225
282
|
return [];
|
|
226
283
|
return [
|
|
@@ -235,7 +292,9 @@ function actionRow(state, options) {
|
|
|
235
292
|
},
|
|
236
293
|
];
|
|
237
294
|
}
|
|
238
|
-
/** 审批区位于 body 最底部(底部操作行之后):待审批时决策按钮不遮挡内容流。
|
|
295
|
+
/** 审批区位于 body 最底部(底部操作行之后):待审批时决策按钮不遮挡内容流。
|
|
296
|
+
* 例外:skeleton 兜底路径中 approvalArea 在 actionRow 之前(信息保真 C4.3),
|
|
297
|
+
* 保证极端降级时审批入口与 stop/new-session 按钮同时可见。 */
|
|
239
298
|
function approvalArea(state) {
|
|
240
299
|
const elements = [];
|
|
241
300
|
for (const slot of state.approvals ?? []) {
|
|
@@ -247,6 +306,12 @@ function approvalArea(state) {
|
|
|
247
306
|
}
|
|
248
307
|
return elements;
|
|
249
308
|
}
|
|
309
|
+
/** 底部操作行之前的内容区提示:被 MAX_BLOCKS 截掉的早期块计数(信息保真 C4.1)。 */
|
|
310
|
+
function omittedBlocksHint(state) {
|
|
311
|
+
return state.omittedBlocks !== undefined && state.omittedBlocks > 0
|
|
312
|
+
? [markdownDiv(`_💡 已省略 ${state.omittedBlocks} 个早期步骤_`)]
|
|
313
|
+
: [];
|
|
314
|
+
}
|
|
250
315
|
/** Assemble the card shell (schema/config/header) around body elements. */
|
|
251
316
|
function assembleRunCard(state, options, elements) {
|
|
252
317
|
return {
|
|
@@ -267,6 +332,7 @@ function assembleRunCard(state, options, elements) {
|
|
|
267
332
|
function buildFallbackElements(state, options, tier) {
|
|
268
333
|
const elements = [];
|
|
269
334
|
elements.push(statusRow(state));
|
|
335
|
+
elements.push(...omittedBlocksHint(state));
|
|
270
336
|
// 单遍分桶:thinking / tool(保持原相对顺序)
|
|
271
337
|
const { thinking: allThinkingBlocks, tool: allToolBlocks } = bucketThinkingAndTool(state.blocks);
|
|
272
338
|
if (allThinkingBlocks.length > tier.thinkingKeep) {
|
|
@@ -298,11 +364,26 @@ function buildFallbackElements(state, options, tier) {
|
|
|
298
364
|
}
|
|
299
365
|
return true; // keep all non-thinking, non-tool blocks
|
|
300
366
|
});
|
|
301
|
-
//
|
|
302
|
-
|
|
367
|
+
// 信息保真 C4.2:plan/file_change 块在降级路径不渲染内容(正常路径已可见,
|
|
368
|
+
// 降级预算紧张),但必须计数留痕,禁止静默丢弃。
|
|
369
|
+
// 计数在渲染前完成,提示与 thinking/tool 省略提示一并置于卡片顶部——不能等
|
|
370
|
+
// 内容循环结束再追加,否则提示会落在最后一条输出之后,打乱「输出收尾」观感。
|
|
371
|
+
const groupsToRender = groupBlocks(filteredBlocks);
|
|
372
|
+
const omittedPlanOrFileChange = groupsToRender.filter((g) => g.kind === 'plan' || g.kind === 'file_change').length;
|
|
373
|
+
if (omittedPlanOrFileChange > 0) {
|
|
374
|
+
elements.push(markdownDiv(`_💡 另外 ${omittedPlanOrFileChange} 个计划/文件变更已省略_`));
|
|
375
|
+
}
|
|
376
|
+
// notices 作为普通消息按时间插入内容流(与正常路径同语义);降级只裁剪
|
|
377
|
+
// 内容块,不裁剪运行期通知。
|
|
378
|
+
const contentItems = mergeNoticesIntoContent(groupsToRender, state.notices);
|
|
379
|
+
// Render thinking/text/tool/notice in chronological order
|
|
380
|
+
for (const item of contentItems) {
|
|
381
|
+
if (item.kind === 'notice') {
|
|
382
|
+
elements.push(renderNoticeRow(item.notice));
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const group = item;
|
|
303
386
|
if (group.kind === 'thinking') {
|
|
304
|
-
if (options.showThinking === false)
|
|
305
|
-
continue;
|
|
306
387
|
const ts = formatTimestamp(group.timestamp);
|
|
307
388
|
const title = '💭 **思考完成**';
|
|
308
389
|
const content = truncateUtf8(group.content, tier.thinkingBytes);
|
|
@@ -322,15 +403,13 @@ function buildFallbackElements(state, options, tier) {
|
|
|
322
403
|
}
|
|
323
404
|
else if (group.kind === 'tool') {
|
|
324
405
|
// Show tool individually (each tool is independent now)
|
|
325
|
-
|
|
326
|
-
continue;
|
|
327
|
-
elements.push(renderTool(group.tool, true, options.showToolResult !== false)); // finalized=true
|
|
406
|
+
elements.push(renderTool(group.tool, true)); // finalized=true
|
|
328
407
|
}
|
|
329
408
|
}
|
|
330
409
|
if (elements.length === 0 && tier === EXTREME_TIER) {
|
|
331
410
|
elements.push(markdownDiv('_暂无输出_'));
|
|
332
411
|
}
|
|
333
|
-
elements.push(...buildSummaryContent(state));
|
|
412
|
+
elements.push(...buildSummaryContent(state, { includeNotices: false }));
|
|
334
413
|
elements.push(...actionRow(state, options));
|
|
335
414
|
elements.push(...approvalArea(state));
|
|
336
415
|
return elements;
|
|
@@ -388,6 +467,9 @@ function buildSkeletonElements(state, options) {
|
|
|
388
467
|
elements.push(markdownDiv('_⚠️ 输出过大且含大量转义字符,已省略全部内容以避免超限;完整内容请查日志_'));
|
|
389
468
|
// Summary (token stats etc.) — static, bounded size
|
|
390
469
|
elements.push(...buildSummaryContent(state));
|
|
470
|
+
// 信息保真 C4.3:skeleton 也渲染审批区——极端兜底不丢审批入口
|
|
471
|
+
//(待审批被静默吞掉 = 用户永远看不到审批请求)。
|
|
472
|
+
elements.push(...approvalArea(state));
|
|
391
473
|
// Bottom action row: stop (if running/finalizing) + compact (if applicable) + new session (always).
|
|
392
474
|
// Degraded paths must keep the action buttons reachable (design constraint).
|
|
393
475
|
elements.push(...actionRow(state, options));
|
|
@@ -431,85 +513,67 @@ function bucketThinkingAndTool(blocks) {
|
|
|
431
513
|
* 走正常路径,renderRunCard 仍会 stringify 确认 ≤28KB;若估算低估,仍 fallback
|
|
432
514
|
* 到 degraded(安全网不丢)。
|
|
433
515
|
*/
|
|
516
|
+
/**
|
|
517
|
+
* W2.2 镜像对单源:thinking/plan/file_change 三个 collapsible 面板组的
|
|
518
|
+
* { title, expanded, border, content } 描述,估算(measurePanelBytes)与渲染
|
|
519
|
+
* (collapsibleMarkdownPanel)共用——此前同一套标题/opIcon/内容模板写两遍。
|
|
520
|
+
* text(renderTextBlock)与 tool(renderTool)形态特殊,由调用方各自处理;
|
|
521
|
+
* 返回 null 表示该组不是 collapsible 面板。
|
|
522
|
+
*/
|
|
523
|
+
function describePanelGroup(group, prepared) {
|
|
524
|
+
if (group.kind === 'tool')
|
|
525
|
+
return null;
|
|
526
|
+
const ts = formatTimestamp(group.timestamp);
|
|
527
|
+
const suffix = ts ? ` (${ts})` : '';
|
|
528
|
+
if (group.kind === 'thinking') {
|
|
529
|
+
const title = (group.active ? '💭 **思考中**' : '💭 **思考完成**') + suffix;
|
|
530
|
+
return { title, expanded: group.active, border: 'grey', content: prepared.get(group) ?? '' };
|
|
531
|
+
}
|
|
532
|
+
if (group.kind === 'plan') {
|
|
533
|
+
const title = (group.active ? '📋 **执行计划**' : '📋 **计划完成**') + suffix;
|
|
534
|
+
return { title, expanded: group.active, border: 'blue', content: prepared.get(group) ?? '' };
|
|
535
|
+
}
|
|
536
|
+
if (group.kind === 'file_change') {
|
|
537
|
+
const opIcon = group.operation === 'create'
|
|
538
|
+
? '🆕'
|
|
539
|
+
: group.operation === 'edit'
|
|
540
|
+
? '✏️'
|
|
541
|
+
: group.operation === 'delete'
|
|
542
|
+
? '🗑️'
|
|
543
|
+
: '📖';
|
|
544
|
+
const title = `${opIcon} **文件改动**${suffix}`;
|
|
545
|
+
const content = group.diff
|
|
546
|
+
? `**${group.path}**\n\n\`\`\`\n${group.diff}\n\`\`\``
|
|
547
|
+
: `**${group.path}** (${group.operation})`;
|
|
548
|
+
return { title, expanded: false, border: 'grey', content };
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
434
552
|
/** 导出供测试断言估算精度(估算 ≈ 实际渲染字节,见 tests/anchor/run-card/run-card.test.ts)。 */
|
|
435
553
|
export function estimateCardBytes(state, options = {}, shared) {
|
|
436
554
|
const finalized = state.terminal !== 'running';
|
|
437
|
-
const showResult = options.showToolResult !== false;
|
|
438
|
-
const showThinking = options.showThinking !== false;
|
|
439
|
-
const showToolUse = options.showToolUse !== false;
|
|
440
555
|
// P1-2:renderRunCard 传入共享 groups/prepared 时直接复用(截断只做一次);
|
|
441
556
|
// 独立调用(测试/预算探针)时自建,行为与原先一致。
|
|
442
557
|
const groups = shared?.groups ?? groupBlocks(state.blocks);
|
|
443
558
|
const prepared = shared?.prepared ?? prepareGroupContent(groups);
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
template: headerTemplate2(state),
|
|
450
|
-
title: { content: headerTitle2(state, options), tag: 'plain_text' },
|
|
451
|
-
},
|
|
452
|
-
body: { elements: [] },
|
|
453
|
-
}) +
|
|
559
|
+
// 卡片外壳直接实测 assembleRunCard 空元素产物(schema/config/header/body 结构
|
|
560
|
+
// 与正式渲染完全一致,W2.2);status/summary/操作行同样实测渲染结构。
|
|
561
|
+
// 注:审批区(approvalArea)不参与估算——待审批通常在途且体积小,超预算由
|
|
562
|
+
// renderRunCard 的 stringify 兜底捕获。
|
|
563
|
+
let total = measureJson(assembleRunCard(state, options, [])) +
|
|
454
564
|
measureJson(statusRow(state)) +
|
|
455
|
-
measureJson(buildSummaryContent(state))
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (state.terminal === 'running' || state.terminal === 'finalizing') {
|
|
459
|
-
actionButtons.push(stopButton(state.runId));
|
|
460
|
-
}
|
|
461
|
-
if (shouldShowCompactButton(state, options)) {
|
|
462
|
-
actionButtons.push(compactButton(state.runId));
|
|
463
|
-
}
|
|
464
|
-
actionButtons.push(newSessionButton());
|
|
465
|
-
total += measureJson([
|
|
466
|
-
{ tag: 'div', text: { content: '', tag: 'lark_md' } },
|
|
467
|
-
{
|
|
468
|
-
tag: 'column_set',
|
|
469
|
-
columns: actionButtons.map((btn) => ({
|
|
470
|
-
tag: 'column',
|
|
471
|
-
width: 'auto',
|
|
472
|
-
elements: [btn],
|
|
473
|
-
})),
|
|
474
|
-
},
|
|
475
|
-
]);
|
|
476
|
-
// 块内容:与 buildChronologicalContent 同源(groupBlocks 合并语义一致)
|
|
565
|
+
measureJson(buildSummaryContent(state, { includeNotices: false })) +
|
|
566
|
+
measureJson(actionRow(state, options));
|
|
567
|
+
// 块内容:与 buildChronologicalContent 同源(describePanelGroup 单源,W2.2)
|
|
477
568
|
let renderedAny = false;
|
|
478
569
|
for (const group of groups) {
|
|
479
|
-
if (group.kind === '
|
|
480
|
-
if (!showThinking)
|
|
481
|
-
continue;
|
|
570
|
+
if (group.kind === 'tool') {
|
|
482
571
|
renderedAny = true;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
total += measurePanelBytes(header, group.active, 'grey', prepared.get(group) ?? '');
|
|
487
|
-
}
|
|
488
|
-
else if (group.kind === 'plan') {
|
|
489
|
-
renderedAny = true;
|
|
490
|
-
const ts = formatTimestamp(group.timestamp);
|
|
491
|
-
const title = group.active ? '📋 **执行计划**' : '📋 **计划完成**';
|
|
492
|
-
const header = ts ? `${title} (${ts})` : title;
|
|
493
|
-
total += measurePanelBytes(header, group.active, 'blue', prepared.get(group) ?? '');
|
|
494
|
-
}
|
|
495
|
-
else if (group.kind === 'file_change') {
|
|
496
|
-
renderedAny = true;
|
|
497
|
-
const ts = formatTimestamp(group.timestamp);
|
|
498
|
-
const opIcon = group.operation === 'create'
|
|
499
|
-
? '🆕'
|
|
500
|
-
: group.operation === 'edit'
|
|
501
|
-
? '✏️'
|
|
502
|
-
: group.operation === 'delete'
|
|
503
|
-
? '🗑️'
|
|
504
|
-
: '📖';
|
|
505
|
-
const title = `${opIcon} **文件改动**`;
|
|
506
|
-
const content = group.diff
|
|
507
|
-
? `**${group.path}**\n\n\`\`\`\n${group.diff}\n\`\`\``
|
|
508
|
-
: `**${group.path}** (${group.operation})`;
|
|
509
|
-
const header = ts ? `${title} (${ts})` : title;
|
|
510
|
-
total += measurePanelBytes(header, false, 'grey', content);
|
|
572
|
+
// tool 直接实测真实渲染元素(复用 renderTool,output ≤1200 字符,物化廉价)
|
|
573
|
+
total += measureJson(renderTool(group.tool, finalized));
|
|
574
|
+
continue;
|
|
511
575
|
}
|
|
512
|
-
|
|
576
|
+
if (group.kind === 'text') {
|
|
513
577
|
const content = prepared.get(group) ?? '';
|
|
514
578
|
if (!content.trim())
|
|
515
579
|
continue;
|
|
@@ -517,14 +581,19 @@ export function estimateCardBytes(state, options = {}, shared) {
|
|
|
517
581
|
const ts = formatTimestamp(group.timestamp);
|
|
518
582
|
const header = `💬 **输出**${ts ? ` (${ts})` : ''}`;
|
|
519
583
|
total += measurePanelBytes(header, true, 'grey', content);
|
|
584
|
+
continue;
|
|
520
585
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
586
|
+
const spec = describePanelGroup(group, prepared);
|
|
587
|
+
if (!spec)
|
|
588
|
+
continue;
|
|
589
|
+
renderedAny = true;
|
|
590
|
+
total += measurePanelBytes(spec.title, spec.expanded, spec.border, spec.content);
|
|
591
|
+
}
|
|
592
|
+
// notices 在正常路径作为普通消息内联渲染(与 buildChronologicalContent 同源,
|
|
593
|
+
// renderNoticeRow 单源);行体积与位置无关,直接按条累加。
|
|
594
|
+
for (const notice of state.notices ?? []) {
|
|
595
|
+
renderedAny = true;
|
|
596
|
+
total += measureJson(renderNoticeRow(notice));
|
|
528
597
|
}
|
|
529
598
|
if (!renderedAny) {
|
|
530
599
|
total += measureJson(markdownDiv('_暂无输出_'));
|
|
@@ -560,8 +629,9 @@ export function renderRunCard(state, options = {}) {
|
|
|
560
629
|
if (estimate < DEGRADED_THRESHOLD) {
|
|
561
630
|
const elements = [
|
|
562
631
|
statusRow(state),
|
|
563
|
-
...
|
|
564
|
-
...
|
|
632
|
+
...omittedBlocksHint(state),
|
|
633
|
+
...buildChronologicalContent(state, prepared, groups),
|
|
634
|
+
...buildSummaryContent(state, { includeNotices: false }),
|
|
565
635
|
...actionRow(state, options),
|
|
566
636
|
...approvalArea(state),
|
|
567
637
|
];
|
|
@@ -599,77 +669,41 @@ export function renderRunCard(state, options = {}) {
|
|
|
599
669
|
return truncatedSkeleton;
|
|
600
670
|
}
|
|
601
671
|
/** Build content in chronological order using groupBlocks to interleave thinking/text/tools. */
|
|
602
|
-
function buildChronologicalContent(state,
|
|
672
|
+
function buildChronologicalContent(state, prepared, groups) {
|
|
603
673
|
const elements = [];
|
|
604
674
|
// finalized = 非 running(含 finalizing:主结果已出,thinking 折叠)
|
|
605
675
|
const finalized = state.terminal !== 'running';
|
|
606
|
-
const showResult = options.showToolResult !== false;
|
|
607
676
|
// P1-2:renderRunCard 传入同一 groups 数组时直接复用(prepared 的键是组对象
|
|
608
677
|
// 引用,重建数组会让 prepared.get 永远 miss,退化为二次截断)。
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const header = ts ? `${title} (${ts})` : title;
|
|
617
|
-
elements.push(collapsibleMarkdownPanel({
|
|
618
|
-
title: header,
|
|
619
|
-
expanded: group.active,
|
|
620
|
-
border: 'grey',
|
|
621
|
-
content,
|
|
622
|
-
textSize: 'notation',
|
|
623
|
-
}));
|
|
624
|
-
}
|
|
625
|
-
else if (group.kind === 'plan') {
|
|
626
|
-
// Plan blocks: render as collapsible panel (default collapsed)
|
|
627
|
-
const ts = formatTimestamp(group.timestamp);
|
|
628
|
-
const title = group.active ? '📋 **执行计划**' : '📋 **计划完成**';
|
|
629
|
-
const content = prepared?.get(group) ?? truncateUtf8(group.content, REASONING_BYTES * 2);
|
|
630
|
-
const header = ts ? `${title} (${ts})` : title;
|
|
631
|
-
elements.push(collapsibleMarkdownPanel({
|
|
632
|
-
title: header,
|
|
633
|
-
expanded: group.active, // expand while active, collapse when done
|
|
634
|
-
border: 'blue',
|
|
635
|
-
content,
|
|
636
|
-
textSize: 'notation',
|
|
637
|
-
}));
|
|
678
|
+
// W2.2:面板组描述经 describePanelGroup 与估算共享同一份模板。
|
|
679
|
+
// notices 作为普通消息按到达时间插入内容流(有内容时不再沉到 summary)。
|
|
680
|
+
const contentItems = mergeNoticesIntoContent(groups, state.notices);
|
|
681
|
+
for (const item of contentItems) {
|
|
682
|
+
if (item.kind === 'notice') {
|
|
683
|
+
elements.push(renderNoticeRow(item.notice));
|
|
684
|
+
continue;
|
|
638
685
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
? '🆕'
|
|
644
|
-
: group.operation === 'edit'
|
|
645
|
-
? '✏️'
|
|
646
|
-
: group.operation === 'delete'
|
|
647
|
-
? '🗑️'
|
|
648
|
-
: '📖';
|
|
649
|
-
const title = `${opIcon} **文件改动**`;
|
|
650
|
-
const content = group.diff
|
|
651
|
-
? `**${group.path}**\n\n\`\`\`\n${group.diff}\n\`\`\``
|
|
652
|
-
: `**${group.path}** (${group.operation})`;
|
|
653
|
-
const header = ts ? `${title} (${ts})` : title;
|
|
654
|
-
elements.push(collapsibleMarkdownPanel({
|
|
655
|
-
title: header,
|
|
656
|
-
expanded: false,
|
|
657
|
-
border: 'grey',
|
|
658
|
-
content,
|
|
659
|
-
textSize: 'notation',
|
|
660
|
-
}));
|
|
686
|
+
const group = item;
|
|
687
|
+
if (group.kind === 'tool') {
|
|
688
|
+
elements.push(renderTool(group.tool, finalized));
|
|
689
|
+
continue;
|
|
661
690
|
}
|
|
662
|
-
|
|
663
|
-
const content = prepared
|
|
691
|
+
if (group.kind === 'text') {
|
|
692
|
+
const content = prepared.get(group) ?? '';
|
|
664
693
|
const ts = formatTimestamp(group.timestamp);
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
else if (group.kind === 'tool') {
|
|
669
|
-
if (options.showToolUse === false)
|
|
670
|
-
continue;
|
|
671
|
-
elements.push(renderTool(group.tool, finalized, showResult));
|
|
694
|
+
elements.push(...renderTextBlock(content, ts, finalized));
|
|
695
|
+
continue;
|
|
672
696
|
}
|
|
697
|
+
const spec = describePanelGroup(group, prepared);
|
|
698
|
+
if (!spec)
|
|
699
|
+
continue;
|
|
700
|
+
elements.push(collapsibleMarkdownPanel({
|
|
701
|
+
title: spec.title,
|
|
702
|
+
expanded: spec.expanded,
|
|
703
|
+
border: spec.border,
|
|
704
|
+
content: spec.content,
|
|
705
|
+
textSize: 'notation',
|
|
706
|
+
}));
|
|
673
707
|
}
|
|
674
708
|
if (elements.length === 0) {
|
|
675
709
|
elements.push(markdownDiv('_暂无输出_'));
|
|
@@ -677,8 +711,16 @@ function buildChronologicalContent(state, options, prepared, groups) {
|
|
|
677
711
|
return elements;
|
|
678
712
|
}
|
|
679
713
|
/** Build summary tab content */
|
|
680
|
-
function buildSummaryContent(state) {
|
|
714
|
+
function buildSummaryContent(state, options = {}) {
|
|
681
715
|
const elements = [];
|
|
716
|
+
// 运行期通知默认仍走 summary(skeleton 兜底:内容区整体缺失时唯一可见位置)。
|
|
717
|
+
// 正常/degraded/extreme 路径传入 includeNotices:false——通知已作为普通消息
|
|
718
|
+
// 按时间线内联渲染(renderNoticeRow),避免同一通知渲染两遍。
|
|
719
|
+
if (options.includeNotices !== false) {
|
|
720
|
+
for (const notice of state.notices ?? []) {
|
|
721
|
+
elements.push(renderNoticeRow(notice));
|
|
722
|
+
}
|
|
723
|
+
}
|
|
682
724
|
// running 时不显示统计(仍在生成);finalizing 显示等待提示
|
|
683
725
|
if (state.terminal === 'running') {
|
|
684
726
|
return elements;
|
|
@@ -710,7 +752,15 @@ function buildSummaryContent(state) {
|
|
|
710
752
|
else {
|
|
711
753
|
const result = state.resultSubtype ?? 'success';
|
|
712
754
|
const hasContent = state.blocks.length > 0;
|
|
713
|
-
|
|
755
|
+
// 压缩(operationKind='compaction')不会返回 agent 正文——引擎只压缩上下文
|
|
756
|
+
// 窗口,空内容属正常,不能沿用普通 run 的「(未返回内容)」异常信号
|
|
757
|
+
// (2026-09 用户反馈)。所有会压缩的 agent(codex/claude/kimi/opencode/pi)
|
|
758
|
+
// 都经 streamCodexCompact 走这条共享渲染路径,一处覆盖全部。
|
|
759
|
+
const empty = hasContent
|
|
760
|
+
? ''
|
|
761
|
+
: state.operationKind === 'compaction'
|
|
762
|
+
? '\n\n🗜 Compact 完成'
|
|
763
|
+
: '\n\n(未返回内容)';
|
|
714
764
|
const usageStatsStr = formatUsageStats({
|
|
715
765
|
contextLength: state.contextLength,
|
|
716
766
|
contextLimit: state.contextLimit,
|
|
@@ -726,6 +776,9 @@ function buildSummaryContent(state) {
|
|
|
726
776
|
cumulativeOutputTokens: state.cumulativeOutputTokens,
|
|
727
777
|
cumulativeCacheReadTokens: state.cumulativeCacheReadTokens,
|
|
728
778
|
cumulativeCacheCreationTokens: state.cumulativeCacheCreationTokens,
|
|
779
|
+
model: state.model,
|
|
780
|
+
costUsd: state.costUsd,
|
|
781
|
+
reasoningTokens: state.reasoningTokens,
|
|
729
782
|
}, { showResult: true, result });
|
|
730
783
|
elements.push(markdownDiv(usageStatsStr + empty));
|
|
731
784
|
}
|