lark-remote 0.1.5 → 0.1.6
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 +17 -0
- package/README.en.md +0 -4
- package/README.md +0 -4
- package/dist/bridge/approval-coordinator.d.ts +108 -0
- package/dist/bridge/approval-coordinator.js +258 -0
- package/dist/bridge/index.d.ts +62 -0
- package/dist/bridge/index.js +402 -21
- package/dist/bridge/queue-manager.d.ts +9 -0
- package/dist/bridge/queue-manager.js +19 -11
- package/dist/card/approval-render.d.ts +16 -0
- package/dist/card/approval-render.js +247 -0
- package/dist/card/card-shared.d.ts +14 -0
- package/dist/card/card-shared.js +28 -0
- package/dist/card/run-renderer.js +77 -6
- package/dist/card/run-state.d.ts +25 -3
- package/dist/card/run-state.js +203 -2
- package/dist/config/dir.js +1 -1
- package/dist/config/index.d.ts +42 -0
- package/dist/config/index.js +23 -1
- package/dist/connector/index.d.ts +5 -0
- package/dist/connector/index.js +12 -0
- package/dist/index.js +44 -22
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +49 -0
- package/dist/router/config/common/render.js +4 -0
- package/dist/router/config/types.d.ts +1 -1
- package/dist/router/index.d.ts +27 -2
- package/dist/router/index.js +109 -28
- package/dist/router/utils.d.ts +2 -0
- package/dist/router/utils.js +5 -2
- package/dist/runner/codex/app-server/client.d.ts +82 -0
- package/dist/runner/codex/app-server/client.js +234 -0
- package/dist/runner/codex/app-server/connection-manager.d.ts +56 -0
- package/dist/runner/codex/app-server/connection-manager.js +170 -0
- package/dist/runner/codex/app-server/protocol-types.d.ts +527 -0
- package/dist/runner/codex/app-server/protocol-types.js +67 -0
- package/dist/runner/codex/app-server/runner.d.ts +121 -0
- package/dist/runner/codex/app-server/runner.js +554 -0
- package/dist/runner/codex/app-server/translator.d.ts +109 -0
- package/dist/runner/codex/app-server/translator.js +734 -0
- package/dist/runner/codex/app-server/transport.d.ts +49 -0
- package/dist/runner/codex/app-server/transport.js +231 -0
- package/dist/runner/codex/factory.d.ts +29 -0
- package/dist/runner/codex/factory.js +39 -0
- package/dist/runner/codex/index.d.ts +2 -0
- package/dist/runner/codex/index.js +2 -0
- package/dist/runner/codex/runner.d.ts +3 -0
- package/dist/runner/codex/runner.js +5 -0
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/index.d.ts +2 -2
- package/dist/runner/index.js +1 -1
- package/dist/runner/types.d.ts +123 -2
- package/dist/session/codex/rollout-reader.js +43 -0
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderApprovalArea: renders the approval request area in a run card.
|
|
3
|
+
*
|
|
4
|
+
* Renders command/file/permissions approval areas with button mappings per §8.3.
|
|
5
|
+
* Uses CardKit 2.0 (no tag:"action" container — 200861 rule).
|
|
6
|
+
*/
|
|
7
|
+
import type { ApprovalView } from '../runner/types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Render the approval area for a run card.
|
|
10
|
+
* Returns an array of CardKit 2.0 elements to append to the card body.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderApprovalArea(approval: ApprovalView | undefined, opts?: {
|
|
13
|
+
expired?: boolean;
|
|
14
|
+
runId?: string;
|
|
15
|
+
terminal?: string;
|
|
16
|
+
}): object[];
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderApprovalArea: renders the approval request area in a run card.
|
|
3
|
+
*
|
|
4
|
+
* Renders command/file/permissions approval areas with button mappings per §8.3.
|
|
5
|
+
* Uses CardKit 2.0 (no tag:"action" container — 200861 rule).
|
|
6
|
+
*/
|
|
7
|
+
/** Unique nonce for card button callbacks (SDK dedup + router validation). */
|
|
8
|
+
function mkNonce() {
|
|
9
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Render the approval area for a run card.
|
|
13
|
+
* Returns an array of CardKit 2.0 elements to append to the card body.
|
|
14
|
+
*/
|
|
15
|
+
export function renderApprovalArea(approval, opts) {
|
|
16
|
+
if (!approval)
|
|
17
|
+
return [];
|
|
18
|
+
// 终态(done/error/interrupted/idle_timeout)下 run 已结束:coordinator 已
|
|
19
|
+
// 释放,按钮点击无处响应,隐藏整个审批区避免"点了没反应"(P2-7)。
|
|
20
|
+
if (opts?.terminal && opts.terminal !== 'running' && opts.terminal !== 'finalizing') {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const elements = [];
|
|
24
|
+
if (approval.kind === 'command') {
|
|
25
|
+
elements.push(...renderCommandApproval(approval, opts?.expired, opts?.runId));
|
|
26
|
+
}
|
|
27
|
+
else if (approval.kind === 'file') {
|
|
28
|
+
elements.push(...renderFileApproval(approval, opts?.expired, opts?.runId));
|
|
29
|
+
}
|
|
30
|
+
else if (approval.kind === 'permissions') {
|
|
31
|
+
elements.push(...renderPermissionsApproval(approval, opts?.expired, opts?.runId));
|
|
32
|
+
}
|
|
33
|
+
return elements;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Render approval action buttons from the real protocol decision list.
|
|
37
|
+
* accept and decline are always offered (decline is a universal safety
|
|
38
|
+
* affordance even when the server omits it); acceptForSession and
|
|
39
|
+
* acceptWithExecpolicyAmendment appear only when the server offers them.
|
|
40
|
+
*/
|
|
41
|
+
function renderDecisionButtons(approval, expired, runId) {
|
|
42
|
+
if (expired) {
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
tag: 'div',
|
|
46
|
+
text: { tag: 'lark_md', content: '⏰ **审批已过期**' },
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
const BUTTONS = {
|
|
51
|
+
accept: { label: '✅ 允许', type: 'primary' },
|
|
52
|
+
acceptForSession: { label: '✅ 允许本次会话', type: 'primary' },
|
|
53
|
+
acceptWithExecpolicyAmendment: { label: '🔒 允许并记住命令', type: 'primary' },
|
|
54
|
+
decline: { label: '❌ 拒绝', type: 'danger' },
|
|
55
|
+
};
|
|
56
|
+
// Fixed presentation order; only offer decisions the protocol listed
|
|
57
|
+
// (accept/decline are always available).
|
|
58
|
+
const offered = ['accept', 'acceptForSession', 'acceptWithExecpolicyAmendment', 'decline'].filter((d) => d === 'accept' || d === 'decline' || approval.availableDecisions.includes(d));
|
|
59
|
+
// 决策按钮逐个作为 body 直接元素返回(CardKit body 纵向堆叠 = 上下排列)。
|
|
60
|
+
// 不能用 column_set width:auto 横排:手机窄屏下多个按钮挤在一行,按钮文字
|
|
61
|
+
// 被截断成省略号,用户无法辨认审批动作。
|
|
62
|
+
return offered.map((d) => ({
|
|
63
|
+
tag: 'button',
|
|
64
|
+
text: { tag: 'plain_text', content: BUTTONS[d].label },
|
|
65
|
+
type: BUTTONS[d].type,
|
|
66
|
+
behaviors: [
|
|
67
|
+
{
|
|
68
|
+
type: 'callback',
|
|
69
|
+
value: {
|
|
70
|
+
cmd: 'approval.respond',
|
|
71
|
+
decision: d,
|
|
72
|
+
requestId: approval.requestId,
|
|
73
|
+
runId,
|
|
74
|
+
nonce: mkNonce(),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// Command Approval
|
|
82
|
+
// =============================================================================
|
|
83
|
+
function renderCommandApproval(approval, expired, runId) {
|
|
84
|
+
const elements = [];
|
|
85
|
+
// Command display
|
|
86
|
+
const cmdContent = approval.command ? `\`${approval.command.slice(0, 500)}\`` : '(no command)';
|
|
87
|
+
elements.push({
|
|
88
|
+
tag: 'div',
|
|
89
|
+
text: { tag: 'lark_md', content: `**⚡ 命令审批**\n${cmdContent}` },
|
|
90
|
+
});
|
|
91
|
+
if (approval.commandCwd) {
|
|
92
|
+
elements.push({
|
|
93
|
+
tag: 'div',
|
|
94
|
+
text: { tag: 'lark_md', content: `📂 \`${approval.commandCwd}\`` },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (approval.reason) {
|
|
98
|
+
elements.push({
|
|
99
|
+
tag: 'div',
|
|
100
|
+
text: { tag: 'lark_md', content: `💡 ${approval.reason}` },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Action buttons — derived from the real protocol decision list (see
|
|
104
|
+
// renderDecisionButtons): accept/decline always, acceptForSession and
|
|
105
|
+
// acceptWithExecpolicyAmendment only when the server offers them.
|
|
106
|
+
elements.push(...renderDecisionButtons(approval, expired, runId));
|
|
107
|
+
// Pending count
|
|
108
|
+
if (approval.pendingTotal > 1) {
|
|
109
|
+
elements.push({
|
|
110
|
+
tag: 'div',
|
|
111
|
+
text: { tag: 'lark_md', content: `📋 还有 ${approval.pendingTotal - 1} 个待审批请求` },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return elements;
|
|
115
|
+
}
|
|
116
|
+
// =============================================================================
|
|
117
|
+
// File Approval
|
|
118
|
+
// =============================================================================
|
|
119
|
+
function renderFileApproval(approval, expired, runId) {
|
|
120
|
+
const elements = [];
|
|
121
|
+
elements.push({
|
|
122
|
+
tag: 'div',
|
|
123
|
+
text: { tag: 'lark_md', content: '**📄 文件变更审批**' },
|
|
124
|
+
});
|
|
125
|
+
if (approval.reason) {
|
|
126
|
+
elements.push({
|
|
127
|
+
tag: 'div',
|
|
128
|
+
text: { tag: 'lark_md', content: `💡 ${approval.reason}` },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// File changes list
|
|
132
|
+
if (approval.fileChanges && approval.fileChanges.length > 0) {
|
|
133
|
+
// 审批区 diff 总预算:估算函数不计审批区,多文件大 diff 可能顶爆 28KB 卡
|
|
134
|
+
// (ErrCode 11310)。与 tool-render 的 OUTPUT_MAX/BODY_TOTAL_MAX 同思路。
|
|
135
|
+
const TOTAL_DIFF_CHARS = 2400;
|
|
136
|
+
let renderedDiffChars = 0;
|
|
137
|
+
let omittedDiffs = 0;
|
|
138
|
+
for (const change of approval.fileChanges) {
|
|
139
|
+
const icon = change.kind === 'add' ? '🆕' : change.kind === 'delete' ? '🗑️' : '✏️';
|
|
140
|
+
elements.push({
|
|
141
|
+
tag: 'div',
|
|
142
|
+
text: { tag: 'lark_md', content: `${icon} \`${change.path}\` (${change.kind})` },
|
|
143
|
+
});
|
|
144
|
+
if (change.diff) {
|
|
145
|
+
if (renderedDiffChars < TOTAL_DIFF_CHARS) {
|
|
146
|
+
elements.push({
|
|
147
|
+
tag: 'div',
|
|
148
|
+
text: { tag: 'lark_md', content: renderFileDiff(change.diff) },
|
|
149
|
+
});
|
|
150
|
+
renderedDiffChars += Math.min(change.diff.length, MAX_DIFF_CHARS);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
omittedDiffs += 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (omittedDiffs > 0) {
|
|
158
|
+
elements.push({
|
|
159
|
+
tag: 'div',
|
|
160
|
+
text: { tag: 'lark_md', content: `_…${omittedDiffs} 个 diff 已省略(卡片预算)_` },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Action buttons
|
|
165
|
+
// Derived from the real protocol decision list (see renderDecisionButtons).
|
|
166
|
+
elements.push(...renderDecisionButtons(approval, expired, runId));
|
|
167
|
+
if (approval.pendingTotal > 1) {
|
|
168
|
+
elements.push({
|
|
169
|
+
tag: 'div',
|
|
170
|
+
text: { tag: 'lark_md', content: `📋 还有 ${approval.pendingTotal - 1} 个待审批请求` },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return elements;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Render a file-change diff as a fenced code block.
|
|
177
|
+
* Truncates to protect the 28KB card budget and neutralizes triple backticks
|
|
178
|
+
* that would break the markdown fence (飞书 11311 类解析错误防线).
|
|
179
|
+
*/
|
|
180
|
+
function renderFileDiff(diff) {
|
|
181
|
+
const sanitized = diff.replace(/```/g, '···');
|
|
182
|
+
const truncated = sanitized.length > MAX_DIFF_CHARS
|
|
183
|
+
? `${sanitized.slice(0, MAX_DIFF_CHARS)}\n…(diff 已截断)`
|
|
184
|
+
: sanitized;
|
|
185
|
+
return `\`\`\`diff\n${truncated}\n\`\`\``;
|
|
186
|
+
}
|
|
187
|
+
/** Per-diff render cap (chars), see TOTAL_DIFF_CHARS for the approval-area cap. */
|
|
188
|
+
const MAX_DIFF_CHARS = 1200;
|
|
189
|
+
// =============================================================================
|
|
190
|
+
// Permissions Approval
|
|
191
|
+
// =============================================================================
|
|
192
|
+
function renderPermissionsApproval(approval, expired, runId) {
|
|
193
|
+
const elements = [];
|
|
194
|
+
elements.push({
|
|
195
|
+
tag: 'div',
|
|
196
|
+
text: { tag: 'lark_md', content: '**🔒 权限审批**' },
|
|
197
|
+
});
|
|
198
|
+
if (approval.reason) {
|
|
199
|
+
elements.push({
|
|
200
|
+
tag: 'div',
|
|
201
|
+
text: { tag: 'lark_md', content: `💡 ${approval.reason}` },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// Permission items with toggle buttons
|
|
205
|
+
if (approval.permissions?.items) {
|
|
206
|
+
for (const item of approval.permissions.items) {
|
|
207
|
+
const icon = item.selected ? '✅' : '⬜';
|
|
208
|
+
elements.push({
|
|
209
|
+
tag: 'div',
|
|
210
|
+
text: { tag: 'lark_md', content: `${icon} ${item.label}` },
|
|
211
|
+
});
|
|
212
|
+
elements.push({
|
|
213
|
+
tag: 'button',
|
|
214
|
+
text: {
|
|
215
|
+
tag: 'plain_text',
|
|
216
|
+
content: item.selected ? '取消授予' : '授予',
|
|
217
|
+
},
|
|
218
|
+
type: item.selected ? 'default' : 'primary',
|
|
219
|
+
size: 'small',
|
|
220
|
+
behaviors: [
|
|
221
|
+
{
|
|
222
|
+
type: 'callback',
|
|
223
|
+
value: {
|
|
224
|
+
cmd: 'approval.toggle',
|
|
225
|
+
requestId: approval.requestId,
|
|
226
|
+
permId: item.id,
|
|
227
|
+
selected: !item.selected,
|
|
228
|
+
runId,
|
|
229
|
+
nonce: mkNonce(),
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Action buttons
|
|
237
|
+
// Derived from the real protocol decision list (see renderDecisionButtons).
|
|
238
|
+
elements.push(...renderDecisionButtons(approval, expired, runId));
|
|
239
|
+
if (approval.pendingTotal > 1) {
|
|
240
|
+
elements.push({
|
|
241
|
+
tag: 'div',
|
|
242
|
+
text: { tag: 'lark_md', content: `📋 还有 ${approval.pendingTotal - 1} 个待审批请求` },
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return elements;
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=approval-render.js.map
|
|
@@ -32,5 +32,19 @@ export declare function newSessionButton(): object;
|
|
|
32
32
|
* agent's session reader (P2 fix: completion cards from non-default agents
|
|
33
33
|
* must carry agent to avoid wrong-reader fallback).
|
|
34
34
|
*/
|
|
35
|
+
/**
|
|
36
|
+
* Create a CardKit 2.0 Compact button for Codex app-server mode.
|
|
37
|
+
* Triggers thread/compact/start via the codex.compact callback.
|
|
38
|
+
*/
|
|
39
|
+
export declare function compactButton(runId: string): object;
|
|
40
|
+
/**
|
|
41
|
+
* Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
|
|
42
|
+
* Unlike compactButton (which targets a finished run's runId), this targets a
|
|
43
|
+
* sessionId directly: the bridge resolves the session and calls the app-server
|
|
44
|
+
* runner's runCompact() without a runId. Carries agent so the handler routes
|
|
45
|
+
* to the correct session reader + runner when the card was rendered for a
|
|
46
|
+
* non-default agent.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resumeCompactButton(sessionId: string, agent: string): object;
|
|
35
49
|
export declare function resumeUseButton(sessionId: string, agent: AgentKind): object;
|
|
36
50
|
export {};
|
package/dist/card/card-shared.js
CHANGED
|
@@ -102,6 +102,34 @@ export function newSessionButton() {
|
|
|
102
102
|
* agent's session reader (P2 fix: completion cards from non-default agents
|
|
103
103
|
* must carry agent to avoid wrong-reader fallback).
|
|
104
104
|
*/
|
|
105
|
+
/**
|
|
106
|
+
* Create a CardKit 2.0 Compact button for Codex app-server mode.
|
|
107
|
+
* Triggers thread/compact/start via the codex.compact callback.
|
|
108
|
+
*/
|
|
109
|
+
export function compactButton(runId) {
|
|
110
|
+
return {
|
|
111
|
+
tag: 'button',
|
|
112
|
+
text: { content: '🗜 Compact' },
|
|
113
|
+
type: 'primary',
|
|
114
|
+
behaviors: [{ type: 'callback', value: { cmd: 'codex.compact', runId } }],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
|
|
119
|
+
* Unlike compactButton (which targets a finished run's runId), this targets a
|
|
120
|
+
* sessionId directly: the bridge resolves the session and calls the app-server
|
|
121
|
+
* runner's runCompact() without a runId. Carries agent so the handler routes
|
|
122
|
+
* to the correct session reader + runner when the card was rendered for a
|
|
123
|
+
* non-default agent.
|
|
124
|
+
*/
|
|
125
|
+
export function resumeCompactButton(sessionId, agent) {
|
|
126
|
+
return {
|
|
127
|
+
tag: 'button',
|
|
128
|
+
text: { content: '🗜 Compact' },
|
|
129
|
+
type: 'primary',
|
|
130
|
+
behaviors: [{ type: 'callback', value: { cmd: 'resume.compact', sessionId, agent } }],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
105
133
|
export function resumeUseButton(sessionId, agent) {
|
|
106
134
|
return {
|
|
107
135
|
tag: 'button',
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { terminalToColor, terminalToLabel, newSessionButton, stopButton, agentDisplayName, } from './card-shared.js';
|
|
1
|
+
import { terminalToColor, terminalToLabel, newSessionButton, stopButton, compactButton, agentDisplayName, } from './card-shared.js';
|
|
2
2
|
import { collapsibleMarkdownPanel, markdownDiv } from './collapsible.js';
|
|
3
3
|
import { toolBodyMd, toolHeaderText } from './tool-render.js';
|
|
4
4
|
import { truncateUtf8, truncateMarkdownTables } from './text-truncate.js';
|
|
5
5
|
import { formatTimestamp } from './time.js';
|
|
6
6
|
import { formatUsageStats } from '../router/index.js';
|
|
7
|
+
import { renderApprovalArea } from './approval-render.js';
|
|
7
8
|
const CARD_BUDGET_BYTES = 28_000;
|
|
8
9
|
const REASONING_BYTES = 4_500;
|
|
9
10
|
const TEXT_BYTES = 10_000;
|
|
@@ -24,6 +25,20 @@ function measureJson(obj) {
|
|
|
24
25
|
}
|
|
25
26
|
/** markdownDiv('', 'notation') 的序列化字节(空内容 div 的常量开销,估算时复用)。 */
|
|
26
27
|
const EMPTY_MD_DIV_BYTES = measureJson(markdownDiv('', 'notation'));
|
|
28
|
+
/**
|
|
29
|
+
* Compact 按钮可见性:普通 run(operationKind='turn')到达任意终态即可压缩。
|
|
30
|
+
* 异常退出(error/interrupted/idle_timeout)时上下文往往更大,更需要压缩后
|
|
31
|
+
* 再继续,因此不再要求 resultSubtype==='success'。compaction 卡
|
|
32
|
+
* (operationKind='compaction')永不显示,防止递归 Compact。
|
|
33
|
+
*/
|
|
34
|
+
function shouldShowCompactButton(state) {
|
|
35
|
+
if (state.operationKind !== 'turn')
|
|
36
|
+
return false;
|
|
37
|
+
return (state.terminal === 'done' ||
|
|
38
|
+
state.terminal === 'error' ||
|
|
39
|
+
state.terminal === 'interrupted' ||
|
|
40
|
+
state.terminal === 'idle_timeout');
|
|
41
|
+
}
|
|
27
42
|
/**
|
|
28
43
|
* Wrap text block in a collapsible panel with timestamp in the header.
|
|
29
44
|
* This unifies timestamp position across all block types (thinking/plan/file_change/tool/text).
|
|
@@ -250,13 +265,17 @@ function buildDegradedElements(state, options) {
|
|
|
250
265
|
}
|
|
251
266
|
}
|
|
252
267
|
elements.push(...buildSummaryContent(state));
|
|
253
|
-
// Bottom action row: stop (if running) + new session (always)
|
|
268
|
+
// Bottom action row: stop (if running) + compact (if applicable) + new session (always)
|
|
254
269
|
const actionButtons = [];
|
|
255
270
|
// finalizing 也显示停止按钮(进程未退出,用户可 /stop)
|
|
256
271
|
const showStop = state.terminal === 'running' || state.terminal === 'finalizing';
|
|
257
272
|
if (showStop) {
|
|
258
273
|
actionButtons.push(stopButton(state.runId));
|
|
259
274
|
}
|
|
275
|
+
// Compact 按钮:普通 turn 到达任意终态即可压缩(含异常退出),compaction 卡除外
|
|
276
|
+
if (shouldShowCompactButton(state)) {
|
|
277
|
+
actionButtons.push(compactButton(state.runId));
|
|
278
|
+
}
|
|
260
279
|
actionButtons.push(newSessionButton());
|
|
261
280
|
if (actionButtons.length > 0) {
|
|
262
281
|
elements.push({ tag: 'div', text: { content: '', tag: 'lark_md' } }, // spacer
|
|
@@ -269,6 +288,15 @@ function buildDegradedElements(state, options) {
|
|
|
269
288
|
})),
|
|
270
289
|
});
|
|
271
290
|
}
|
|
291
|
+
// 审批区位于 body 最底部(底部操作行之后):待审批时决策按钮不遮挡内容流。
|
|
292
|
+
// 多槽渲染(review P2-3):并发审批全部展示,互不顶掉。
|
|
293
|
+
for (const slot of state.approvals ?? []) {
|
|
294
|
+
elements.push(...renderApprovalArea(slot.view, {
|
|
295
|
+
expired: slot.expired,
|
|
296
|
+
runId: state.runId,
|
|
297
|
+
terminal: state.terminal,
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
272
300
|
return elements;
|
|
273
301
|
}
|
|
274
302
|
/**
|
|
@@ -340,13 +368,17 @@ function buildExtremeFallbackElements(state, options) {
|
|
|
340
368
|
elements.push(markdownDiv('_暂无输出_'));
|
|
341
369
|
}
|
|
342
370
|
elements.push(...buildSummaryContent(state));
|
|
343
|
-
// Bottom action row: stop (if running) + new session (always)
|
|
371
|
+
// Bottom action row: stop (if running) + compact (if applicable) + new session (always)
|
|
344
372
|
const actionButtons = [];
|
|
345
373
|
// finalizing 也显示停止按钮(进程未退出,用户可 /stop)
|
|
346
374
|
const showStop = state.terminal === 'running' || state.terminal === 'finalizing';
|
|
347
375
|
if (showStop) {
|
|
348
376
|
actionButtons.push(stopButton(state.runId));
|
|
349
377
|
}
|
|
378
|
+
// Compact 按钮:普通 turn 到达任意终态即可压缩(含异常退出),compaction 卡除外
|
|
379
|
+
if (shouldShowCompactButton(state)) {
|
|
380
|
+
actionButtons.push(compactButton(state.runId));
|
|
381
|
+
}
|
|
350
382
|
actionButtons.push(newSessionButton());
|
|
351
383
|
if (actionButtons.length > 0) {
|
|
352
384
|
elements.push({ tag: 'div', text: { content: '', tag: 'lark_md' } }, // spacer
|
|
@@ -359,6 +391,15 @@ function buildExtremeFallbackElements(state, options) {
|
|
|
359
391
|
})),
|
|
360
392
|
});
|
|
361
393
|
}
|
|
394
|
+
// 审批区位于 body 最底部(底部操作行之后):待审批时决策按钮不遮挡内容流。
|
|
395
|
+
// 多槽渲染(review P2-3):并发审批全部展示,互不顶掉。
|
|
396
|
+
for (const slot of state.approvals ?? []) {
|
|
397
|
+
elements.push(...renderApprovalArea(slot.view, {
|
|
398
|
+
expired: slot.expired,
|
|
399
|
+
runId: state.runId,
|
|
400
|
+
terminal: state.terminal,
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
362
403
|
return elements;
|
|
363
404
|
}
|
|
364
405
|
/** Build the status row element (shared across full/degraded/extreme). */
|
|
@@ -399,13 +440,16 @@ function buildSkeletonElements(state) {
|
|
|
399
440
|
elements.push(markdownDiv('_⚠️ 输出过大且含大量转义字符,已省略全部内容以避免超限;完整内容请查日志_'));
|
|
400
441
|
// Summary (token stats etc.) — static, bounded size
|
|
401
442
|
elements.push(...buildSummaryContent(state));
|
|
402
|
-
// Bottom action row: stop (if running/finalizing) + new session (always).
|
|
443
|
+
// Bottom action row: stop (if running/finalizing) + compact (if applicable) + new session (always).
|
|
403
444
|
// Degraded paths must keep the action buttons reachable (design constraint).
|
|
404
445
|
const actionButtons = [];
|
|
405
446
|
const showStop = state.terminal === 'running' || state.terminal === 'finalizing';
|
|
406
447
|
if (showStop) {
|
|
407
448
|
actionButtons.push(stopButton(state.runId));
|
|
408
449
|
}
|
|
450
|
+
if (shouldShowCompactButton(state)) {
|
|
451
|
+
actionButtons.push(compactButton(state.runId));
|
|
452
|
+
}
|
|
409
453
|
actionButtons.push(newSessionButton());
|
|
410
454
|
elements.push({ tag: 'div', text: { content: '', tag: 'lark_md' } }, // spacer
|
|
411
455
|
{
|
|
@@ -588,11 +632,14 @@ export function renderRunCard(state, options = {}) {
|
|
|
588
632
|
...buildChronologicalContent(state, options, prepared, groups),
|
|
589
633
|
...buildSummaryContent(state),
|
|
590
634
|
];
|
|
591
|
-
// Bottom action row: stop (if running) + new session (always)
|
|
635
|
+
// Bottom action row: stop (if running) + compact (if applicable) + new session (always)
|
|
592
636
|
const actionButtons = [];
|
|
593
637
|
if (showStop) {
|
|
594
638
|
actionButtons.push(stopButton(runId));
|
|
595
639
|
}
|
|
640
|
+
if (shouldShowCompactButton(state)) {
|
|
641
|
+
actionButtons.push(compactButton(runId));
|
|
642
|
+
}
|
|
596
643
|
actionButtons.push(newSessionButton());
|
|
597
644
|
if (actionButtons.length > 0) {
|
|
598
645
|
elements.push({ tag: 'div', text: { content: '', tag: 'lark_md' } }, // spacer
|
|
@@ -605,6 +652,15 @@ export function renderRunCard(state, options = {}) {
|
|
|
605
652
|
})),
|
|
606
653
|
});
|
|
607
654
|
}
|
|
655
|
+
// 审批区位于 body 最底部(底部操作行之后):待审批时决策按钮不遮挡内容流。
|
|
656
|
+
// 多槽渲染(review P2-3):并发审批全部展示,互不顶掉。
|
|
657
|
+
for (const slot of state.approvals ?? []) {
|
|
658
|
+
elements.push(...renderApprovalArea(slot.view, {
|
|
659
|
+
expired: slot.expired,
|
|
660
|
+
runId: state.runId,
|
|
661
|
+
terminal: state.terminal,
|
|
662
|
+
}));
|
|
663
|
+
}
|
|
608
664
|
const card = {
|
|
609
665
|
schema: '2.0',
|
|
610
666
|
config: { wide_screen_mode: true, update_multi: true },
|
|
@@ -765,7 +821,10 @@ function buildSummaryContent(state) {
|
|
|
765
821
|
elements.push(markdownDiv(`⚠️ **运行出错**\n\n${state.errorMsg ?? '未知错误'}`));
|
|
766
822
|
}
|
|
767
823
|
else if (state.terminal === 'interrupted') {
|
|
768
|
-
|
|
824
|
+
// 审批超时(approval_expired 已标记)是「无人响应被自动取消」,不是用户
|
|
825
|
+
// 主动终止;如实展示原因,避免用户误判为 Agent 出错或自己操作过。
|
|
826
|
+
const expiredApproval = (state.approvals ?? []).some((a) => a.expired);
|
|
827
|
+
elements.push(markdownDiv(expiredApproval ? '⏰ **审批超时未响应,已自动取消**' : '⏹ **已被用户终止**'));
|
|
769
828
|
}
|
|
770
829
|
else if (state.terminal === 'idle_timeout') {
|
|
771
830
|
elements.push(markdownDiv(`⏱ **已超时**\n\n${state.idleTimeoutMinutes ?? 0} 分钟无响应,已自动终止`));
|
|
@@ -778,6 +837,7 @@ function buildSummaryContent(state) {
|
|
|
778
837
|
contextLength: state.contextLength,
|
|
779
838
|
contextLimit: state.contextLimit,
|
|
780
839
|
compactCount: state.compactCount,
|
|
840
|
+
compactPreContextLength: state.compactPreContextLength,
|
|
781
841
|
cacheReadTokens: state.cacheReadTokens,
|
|
782
842
|
cacheCreationTokens: state.cacheCreationTokens,
|
|
783
843
|
totalTokens: state.totalTokens,
|
|
@@ -801,6 +861,9 @@ function buildDurationInfo(state) {
|
|
|
801
861
|
state.terminal === 'idle_timeout') {
|
|
802
862
|
return `⏱ ${state.footer || '已完成'}`;
|
|
803
863
|
}
|
|
864
|
+
// 与标题一致:审批等待期间状态行显示「等待审批中」,而不是「工具调用中」。
|
|
865
|
+
if (hasPendingApproval(state))
|
|
866
|
+
return '✋ 等待审批中';
|
|
804
867
|
return footerText2(state.footer);
|
|
805
868
|
}
|
|
806
869
|
function footerText2(footer) {
|
|
@@ -827,6 +890,10 @@ function headerTitle2(state, options = {}) {
|
|
|
827
890
|
return `⏱ ${agent} · 已超时`;
|
|
828
891
|
if (state.terminal === 'finalizing')
|
|
829
892
|
return `⏳ ${agent} · 完成中`;
|
|
893
|
+
// 审批等待期间 server 暂停 turn,命令工具停在 tool_running——标题必须提示
|
|
894
|
+
// 正在等人工决策,而不是「调用工具」(2026-08-14 UX)。
|
|
895
|
+
if (hasPendingApproval(state))
|
|
896
|
+
return `✋ ${agent} · 等待审批`;
|
|
830
897
|
if (state.footer === 'thinking')
|
|
831
898
|
return `💭 ${agent} · 思考中`;
|
|
832
899
|
if (state.footer === 'streaming')
|
|
@@ -835,6 +902,10 @@ function headerTitle2(state, options = {}) {
|
|
|
835
902
|
return `🔧 ${agent} · 调用工具`;
|
|
836
903
|
return `🔴 ${agent} · 运行中`;
|
|
837
904
|
}
|
|
905
|
+
/** 是否有未过期(仍可操作)的待审批请求。 */
|
|
906
|
+
function hasPendingApproval(state) {
|
|
907
|
+
return (state.approvals ?? []).some((a) => !a.expired);
|
|
908
|
+
}
|
|
838
909
|
/** CardKit 2.0 header template color */
|
|
839
910
|
function headerTemplate2(state) {
|
|
840
911
|
return terminalToColor(state.terminal);
|
package/dist/card/run-state.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentEvent } from '../runner/index.js';
|
|
1
|
+
import type { AgentEvent, ApprovalView } from '../runner/index.js';
|
|
2
2
|
export type RunTerminal = 'running' | 'finalizing' | 'done' | 'error' | 'interrupted' | 'idle_timeout';
|
|
3
3
|
export type RunFooter = 'thinking' | 'tool_running' | 'streaming' | null;
|
|
4
4
|
type ToolStatus = 'running' | 'ok' | 'error';
|
|
@@ -23,27 +23,36 @@ export interface ToolEntry {
|
|
|
23
23
|
}
|
|
24
24
|
export type RunBlock = {
|
|
25
25
|
kind: 'thinking';
|
|
26
|
+
/** app-server item id; exec-mode blocks leave it undefined. */
|
|
27
|
+
itemId?: string;
|
|
26
28
|
content: string;
|
|
27
29
|
active: boolean;
|
|
28
30
|
timestamp?: string;
|
|
31
|
+
completedAt?: string;
|
|
29
32
|
} | {
|
|
30
33
|
kind: 'text';
|
|
34
|
+
itemId?: string;
|
|
31
35
|
content: string;
|
|
32
36
|
timestamp?: string;
|
|
37
|
+
completedAt?: string;
|
|
33
38
|
} | {
|
|
34
39
|
kind: 'tool';
|
|
35
40
|
tool: ToolEntry;
|
|
36
41
|
} | {
|
|
37
42
|
kind: 'plan';
|
|
43
|
+
itemId?: string;
|
|
38
44
|
content: string;
|
|
39
45
|
active: boolean;
|
|
40
46
|
timestamp?: string;
|
|
47
|
+
completedAt?: string;
|
|
41
48
|
} | {
|
|
42
49
|
kind: 'file_change';
|
|
50
|
+
itemId?: string;
|
|
43
51
|
path: string;
|
|
44
52
|
operation: 'create' | 'edit' | 'delete' | 'read';
|
|
45
53
|
diff?: string;
|
|
46
54
|
timestamp?: string;
|
|
55
|
+
completedAt?: string;
|
|
47
56
|
};
|
|
48
57
|
export interface RunState {
|
|
49
58
|
runId: string;
|
|
@@ -51,7 +60,7 @@ export interface RunState {
|
|
|
51
60
|
footer: RunFooter;
|
|
52
61
|
blocks: RunBlock[];
|
|
53
62
|
sessionId?: string;
|
|
54
|
-
resultSubtype?: 'success' | 'error';
|
|
63
|
+
resultSubtype?: 'success' | 'error' | 'interrupted';
|
|
55
64
|
contextLength?: number;
|
|
56
65
|
/** 当前模型 context window 上限(codex jsonl 提供);卡片据此渲染百分比。 */
|
|
57
66
|
contextLimit?: number;
|
|
@@ -77,15 +86,28 @@ export interface RunState {
|
|
|
77
86
|
/** 会话累计 cache creation token(所有 run 之和)。 */
|
|
78
87
|
cumulativeCacheCreationTokens?: number;
|
|
79
88
|
plan?: string;
|
|
89
|
+
/** Codex 操作类型:'turn' 表示普通 run,'compaction' 表示 compact 运行。 */
|
|
90
|
+
operationKind?: 'turn' | 'compaction';
|
|
91
|
+
/** 压缩前上下文水位(codex compact 卡展示「压缩前 X → 压缩后 Y」)。 */
|
|
92
|
+
compactPreContextLength?: number;
|
|
93
|
+
/**
|
|
94
|
+
* 待审批请求列表(app-server 模式,review P2-3)。同一 turn 内并发多个审批
|
|
95
|
+
* 时全部渲染,避免后到者顶掉先到者的按钮(单槽会丢 UI,只能等超时 cancel)。
|
|
96
|
+
*/
|
|
97
|
+
approvals?: Array<{
|
|
98
|
+
view: ApprovalView;
|
|
99
|
+
expired?: boolean;
|
|
100
|
+
}>;
|
|
80
101
|
}
|
|
81
102
|
export interface FinishMeta {
|
|
82
|
-
resultSubtype?: 'success' | 'error';
|
|
103
|
+
resultSubtype?: 'success' | 'error' | 'interrupted';
|
|
83
104
|
contextLength?: number;
|
|
84
105
|
/** 当前模型 context window 上限;透传到 RunState 供 done 卡片显示百分比。 */
|
|
85
106
|
contextLimit?: number;
|
|
86
107
|
errorMsg?: string;
|
|
87
108
|
idleTimeoutMinutes?: number;
|
|
88
109
|
compactCount?: number;
|
|
110
|
+
compactPreContextLength?: number;
|
|
89
111
|
cacheReadTokens?: number;
|
|
90
112
|
cacheCreationTokens?: number;
|
|
91
113
|
/** Agent-declared total; threaded to RunState for the done card. */
|