lark-remote 0.1.5 → 0.1.7
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 +39 -0
- package/README.en.md +5 -1
- package/README.md +10 -2
- package/dist/bridge/approval-coordinator.d.ts +131 -0
- package/dist/bridge/approval-coordinator.js +409 -0
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +138 -5
- package/dist/bridge/index.js +569 -65
- package/dist/bridge/queue-manager.d.ts +11 -1
- package/dist/bridge/queue-manager.js +20 -11
- package/dist/card/approval-render.d.ts +16 -0
- package/dist/card/approval-render.js +363 -0
- package/dist/card/bash-card-session.d.ts +2 -24
- package/dist/card/bash-card-session.js +6 -73
- package/dist/card/bash-renderer.js +2 -3
- package/dist/card/card-budget.js +29 -75
- package/dist/card/card-session.d.ts +48 -0
- package/dist/card/card-session.js +86 -0
- package/dist/card/card-shared.d.ts +13 -10
- package/dist/card/card-shared.js +27 -24
- package/dist/card/run-card-session.d.ts +2 -55
- package/dist/card/run-card-session.js +6 -104
- package/dist/card/run-renderer.js +140 -202
- package/dist/card/run-state.d.ts +38 -3
- package/dist/card/run-state.js +221 -11
- package/dist/card/text-truncate.d.ts +13 -12
- package/dist/card/text-truncate.js +13 -63
- package/dist/common/fs.d.ts +8 -0
- package/dist/common/fs.js +17 -0
- package/dist/common/guards.d.ts +0 -20
- package/dist/common/guards.js +0 -43
- package/dist/common/sleep.d.ts +2 -0
- package/dist/common/sleep.js +5 -0
- package/dist/common/truncate.d.ts +39 -0
- package/dist/common/truncate.js +92 -0
- package/dist/config/codex-config.d.ts +1 -1
- package/dist/config/codex-config.js +1 -1
- package/dist/config/dir.js +1 -1
- package/dist/config/index.d.ts +116 -9
- package/dist/config/index.js +122 -29
- package/dist/connector/index.d.ts +74 -2
- package/dist/connector/index.js +119 -1
- package/dist/index.js +139 -99
- package/dist/instance-lock.js +4 -12
- package/dist/order/alias-resolve.d.ts +13 -0
- package/dist/order/alias-resolve.js +22 -0
- package/dist/order/alias-store.d.ts +38 -0
- package/dist/order/alias-store.js +106 -0
- package/dist/persistence/atomic-write.d.ts +8 -2
- package/dist/persistence/atomic-write.js +23 -7
- package/dist/restart.js +2 -1
- package/dist/router/card-helpers.d.ts +63 -0
- package/dist/router/card-helpers.js +146 -0
- package/dist/router/config/claude.js +25 -1
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +33 -5
- package/dist/router/config/common/model-patch.d.ts +22 -0
- package/dist/router/config/common/model-patch.js +26 -0
- package/dist/router/config/common/render.js +9 -4
- package/dist/router/config/index.d.ts +12 -0
- package/dist/router/config/index.js +25 -1
- package/dist/router/config/kimi.d.ts +4 -1
- package/dist/router/config/kimi.js +27 -0
- package/dist/router/config/opencode.js +18 -5
- package/dist/router/config/pi.js +4 -5
- package/dist/router/config/types.d.ts +12 -3
- package/dist/router/index.d.ts +77 -17
- package/dist/router/index.js +455 -549
- package/dist/router/utils.d.ts +4 -0
- package/dist/router/utils.js +9 -2
- package/dist/runner/agent-choices-common.d.ts +16 -0
- package/dist/runner/agent-choices-common.js +31 -0
- package/dist/runner/claude/runner.d.ts +47 -10
- package/dist/runner/claude/runner.js +117 -46
- package/dist/runner/claude/session.d.ts +166 -0
- package/dist/runner/claude/session.js +749 -0
- package/dist/runner/codex/app-server/protocol-types.d.ts +519 -0
- package/dist/runner/codex/app-server/protocol-types.js +56 -0
- package/dist/runner/codex/app-server/runner.d.ts +104 -0
- package/dist/runner/codex/app-server/runner.js +377 -0
- package/dist/runner/codex/app-server/translator.d.ts +89 -0
- package/dist/runner/codex/app-server/translator.js +716 -0
- package/dist/runner/codex/index.d.ts +1 -1
- package/dist/runner/codex/index.js +1 -1
- package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
- package/dist/runner/common/acp/protocol-helpers.js +21 -0
- package/dist/runner/common/acp/protocol-types.d.ts +238 -0
- package/dist/runner/common/acp/protocol-types.js +75 -0
- package/dist/runner/common/connection-based-runner.d.ts +111 -0
- package/dist/runner/common/connection-based-runner.js +261 -0
- package/dist/runner/common/jsonrpc/client.d.ts +89 -0
- package/dist/runner/common/jsonrpc/client.js +243 -0
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +205 -0
- package/dist/runner/common/jsonrpc/transport.d.ts +50 -0
- package/dist/runner/common/jsonrpc/transport.js +230 -0
- package/dist/runner/common/jsonrpc/types.d.ts +32 -0
- package/dist/runner/common/jsonrpc/types.js +9 -0
- package/dist/runner/common/process-stopper.d.ts +2 -1
- package/dist/runner/common/process-stopper.js +1 -0
- package/dist/runner/common/runner-utils.d.ts +6 -13
- package/dist/runner/common/runner-utils.js +6 -47
- package/dist/runner/common/spawning-runner.d.ts +24 -70
- package/dist/runner/common/spawning-runner.js +98 -152
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/index.d.ts +5 -5
- package/dist/runner/index.js +4 -4
- package/dist/runner/kimi/acp/runner.d.ts +106 -0
- package/dist/runner/kimi/acp/runner.js +448 -0
- package/dist/runner/kimi/acp/translator.d.ts +124 -0
- package/dist/runner/kimi/acp/translator.js +336 -0
- package/dist/runner/kimi/index.d.ts +1 -1
- package/dist/runner/kimi/index.js +1 -1
- package/dist/runner/opencode/acp/index.d.ts +1 -0
- package/dist/runner/opencode/acp/index.js +2 -0
- package/dist/runner/opencode/acp/runner.d.ts +121 -0
- package/dist/runner/opencode/acp/runner.js +429 -0
- package/dist/runner/opencode/acp/translator.d.ts +119 -0
- package/dist/runner/opencode/acp/translator.js +345 -0
- package/dist/runner/opencode/index.d.ts +1 -1
- package/dist/runner/opencode/index.js +1 -1
- package/dist/runner/pi/index.d.ts +1 -1
- package/dist/runner/pi/index.js +1 -1
- package/dist/runner/pi/rpc/client.d.ts +52 -0
- package/dist/runner/pi/rpc/client.js +114 -0
- package/dist/runner/pi/rpc/index.d.ts +4 -0
- package/dist/runner/pi/rpc/index.js +4 -0
- package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
- package/dist/runner/pi/rpc/protocol-types.js +21 -0
- package/dist/runner/pi/rpc/runner.d.ts +80 -0
- package/dist/runner/pi/rpc/runner.js +199 -0
- package/dist/runner/pi/rpc/translator.d.ts +55 -0
- package/dist/runner/pi/rpc/translator.js +258 -0
- package/dist/runner/registry.d.ts +7 -8
- package/dist/runner/registry.js +3 -4
- package/dist/runner/resolve-agent-choices.js +18 -59
- package/dist/runner/sync-agent-choices.js +16 -67
- package/dist/runner/types.d.ts +153 -2
- package/dist/session/claude/session-index.js +2 -3
- package/dist/session/claude/session-reader.js +3 -6
- package/dist/session/claude/sessions.js +6 -13
- package/dist/session/codex/rollout-reader.d.ts +2 -2
- package/dist/session/codex/rollout-reader.js +57 -17
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +2 -2
- package/dist/session/common/content-blocks.d.ts +7 -0
- package/dist/session/common/content-blocks.js +13 -9
- package/dist/session/common/jsonl.d.ts +8 -25
- package/dist/session/common/jsonl.js +126 -261
- package/dist/session/common/pagination.d.ts +28 -0
- package/dist/session/common/pagination.js +33 -0
- package/dist/session/kimi/sessions.d.ts +34 -1
- package/dist/session/kimi/sessions.js +208 -98
- package/dist/session/opencode/sessions.d.ts +17 -3
- package/dist/session/opencode/sessions.js +29 -29
- package/dist/session/pi/sessions.d.ts +5 -1
- package/dist/session/pi/sessions.js +12 -16
- package/dist/session/session-store.d.ts +11 -1
- package/dist/session/session-store.js +75 -178
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/install.d.ts +20 -2
- package/dist/update/install.js +51 -7
- package/package.json +1 -1
- package/dist/runner/codex/argv.d.ts +0 -33
- package/dist/runner/codex/argv.js +0 -40
- package/dist/runner/codex/jsonl.d.ts +0 -37
- package/dist/runner/codex/jsonl.js +0 -221
- package/dist/runner/codex/runner.d.ts +0 -63
- package/dist/runner/codex/runner.js +0 -124
- package/dist/runner/common/exec-translator.d.ts +0 -73
- package/dist/runner/common/exec-translator.js +0 -105
- package/dist/runner/kimi/jsonl.d.ts +0 -28
- package/dist/runner/kimi/jsonl.js +0 -143
- package/dist/runner/kimi/runner.d.ts +0 -49
- package/dist/runner/kimi/runner.js +0 -105
- package/dist/runner/opencode/argv.d.ts +0 -32
- package/dist/runner/opencode/argv.js +0 -35
- package/dist/runner/opencode/jsonl.d.ts +0 -68
- package/dist/runner/opencode/jsonl.js +0 -295
- package/dist/runner/opencode/runner.d.ts +0 -61
- package/dist/runner/opencode/runner.js +0 -93
- package/dist/runner/pi/jsonl.d.ts +0 -128
- package/dist/runner/pi/jsonl.js +0 -249
- package/dist/runner/pi/runner.d.ts +0 -36
- package/dist/runner/pi/runner.js +0 -76
- package/dist/session/common/catch-up.d.ts +0 -12
- package/dist/session/common/catch-up.js +0 -31
|
@@ -29,6 +29,13 @@ export interface QueuedTask {
|
|
|
29
29
|
workspace: string;
|
|
30
30
|
timestamp: number;
|
|
31
31
|
messagePreview: string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the queued task's message is editable from the queue card.
|
|
34
|
+
* Card actions like Compact are one-shot operations — editing their
|
|
35
|
+
* preview is meaningless, so they enqueue with `editable: false` and the
|
|
36
|
+
* queue card omits the ✏️ 编辑 button.
|
|
37
|
+
*/
|
|
38
|
+
editable?: boolean;
|
|
32
39
|
/**
|
|
33
40
|
* Edited message content. Set by `updateQueuedTaskMessage` when the user
|
|
34
41
|
* edits a queued message. The original closure captured at enqueue time
|
|
@@ -54,6 +61,8 @@ export interface EnqueueOptions {
|
|
|
54
61
|
* real Feishu id and doubles as the reply target.
|
|
55
62
|
*/
|
|
56
63
|
feishuReplyTo?: string;
|
|
64
|
+
/** Whether the queued task is editable (queue card ✏️ 编辑 button). Defaults to true. */
|
|
65
|
+
editable?: boolean;
|
|
57
66
|
/** 入队时刻捕获的 agent+session 绑定(方案 D4)。 */
|
|
58
67
|
binding?: AgentBinding;
|
|
59
68
|
};
|
|
@@ -92,7 +101,8 @@ export declare class QueueManager {
|
|
|
92
101
|
* executing/cancelled state. The promise never rejects (send failures
|
|
93
102
|
* resolve undefined), so awaiting it is always safe.
|
|
94
103
|
*/
|
|
95
|
-
|
|
104
|
+
/** queue card 的 send promise 表(public:bridge 集成测试注入/断言用)。 */
|
|
105
|
+
queueCardMessages: Map<string, Promise<string | undefined>>;
|
|
96
106
|
/** Track number of executing tasks per workspace (for queue card display). */
|
|
97
107
|
private pendingOrExecutingCount;
|
|
98
108
|
/**
|
|
@@ -38,6 +38,7 @@ export class QueueManager {
|
|
|
38
38
|
* executing/cancelled state. The promise never rejects (send failures
|
|
39
39
|
* resolve undefined), so awaiting it is always safe.
|
|
40
40
|
*/
|
|
41
|
+
/** queue card 的 send promise 表(public:bridge 集成测试注入/断言用)。 */
|
|
41
42
|
queueCardMessages = new Map();
|
|
42
43
|
/** Track number of executing tasks per workspace (for queue card display). */
|
|
43
44
|
pendingOrExecutingCount = new Map();
|
|
@@ -138,6 +139,7 @@ export class QueueManager {
|
|
|
138
139
|
workspace,
|
|
139
140
|
timestamp: Date.now(),
|
|
140
141
|
messagePreview,
|
|
142
|
+
editable: taskMeta.editable,
|
|
141
143
|
binding: taskMeta.binding,
|
|
142
144
|
};
|
|
143
145
|
// Check if there are tasks waiting BEFORE adding this one
|
|
@@ -395,22 +397,29 @@ export class QueueManager {
|
|
|
395
397
|
},
|
|
396
398
|
{ tag: 'hr' },
|
|
397
399
|
];
|
|
398
|
-
// Show message preview with edit button (only if not executing
|
|
400
|
+
// Show message preview with edit button (only if not executing AND the
|
|
401
|
+
// task is editable). One-shot card actions like Compact enqueue with
|
|
402
|
+
// editable=false: their preview is not user-editable text, so the ✏️ 编辑
|
|
403
|
+
// button must not appear (queue card is still shown — only editing is
|
|
404
|
+
// meaningless for them).
|
|
399
405
|
if (messagePreview) {
|
|
400
406
|
elements.push({
|
|
401
407
|
tag: 'div',
|
|
402
408
|
text: { tag: 'lark_md', content: `📝 \`${messagePreview}\`` },
|
|
403
409
|
});
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
410
|
+
const task = this.indexGet(workspace, messageId);
|
|
411
|
+
if (task?.editable !== false) {
|
|
412
|
+
// Edit button: a pending (queued) task is editable by default. The
|
|
413
|
+
// executing/cancelled states render via dedicated card builders that
|
|
414
|
+
// hard-disable all buttons, so this builder only serves pending cards.
|
|
415
|
+
elements.push({
|
|
416
|
+
tag: 'button',
|
|
417
|
+
text: { tag: 'plain_text', content: '✏️ 编辑' },
|
|
418
|
+
type: 'default',
|
|
419
|
+
size: 'small',
|
|
420
|
+
behaviors: [{ type: 'callback', value: { cmd: 'queue.edit', workspace, messageId } }],
|
|
421
|
+
});
|
|
422
|
+
}
|
|
414
423
|
elements.push({ tag: 'hr' });
|
|
415
424
|
}
|
|
416
425
|
// Action buttons
|
|
@@ -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,363 @@
|
|
|
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
|
+
/** reason div(3 处共用);反引号中和防 lark_md 解析错误(review P2-2)。 */
|
|
12
|
+
function reasonDiv(approval) {
|
|
13
|
+
if (!approval.reason)
|
|
14
|
+
return null;
|
|
15
|
+
const neutralized = approval.reason.replace(/`/g, '·');
|
|
16
|
+
return {
|
|
17
|
+
tag: 'div',
|
|
18
|
+
text: { tag: 'lark_md', content: `💡 ${neutralized}` },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Append a possibly-null element to the array (convenience for optional divs).
|
|
23
|
+
* If the element is null, nothing is appended.
|
|
24
|
+
*/
|
|
25
|
+
function pushIf(arr, el) {
|
|
26
|
+
if (el !== null)
|
|
27
|
+
arr.push(el);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Render the approval area for a run card.
|
|
31
|
+
* Returns an array of CardKit 2.0 elements to append to the card body.
|
|
32
|
+
*/
|
|
33
|
+
export function renderApprovalArea(approval, opts) {
|
|
34
|
+
if (!approval)
|
|
35
|
+
return [];
|
|
36
|
+
// 终态(done/error/interrupted/idle_timeout)下 run 已结束:coordinator 已
|
|
37
|
+
// 释放,按钮点击无处响应,隐藏整个审批区避免"点了没反应"(P2-7)。
|
|
38
|
+
if (opts?.terminal && opts.terminal !== 'running' && opts.terminal !== 'finalizing') {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const elements = [];
|
|
42
|
+
if (approval.kind === 'command') {
|
|
43
|
+
elements.push(...renderCommandApproval(approval, opts?.expired, opts?.runId));
|
|
44
|
+
}
|
|
45
|
+
else if (approval.kind === 'file') {
|
|
46
|
+
elements.push(...renderFileApproval(approval, opts?.expired, opts?.runId));
|
|
47
|
+
}
|
|
48
|
+
else if (approval.kind === 'permissions') {
|
|
49
|
+
elements.push(...renderPermissionsApproval(approval, opts?.expired, opts?.runId));
|
|
50
|
+
}
|
|
51
|
+
else if (approval.kind === 'question') {
|
|
52
|
+
elements.push(...renderQuestionApproval(approval, opts?.expired, opts?.runId));
|
|
53
|
+
}
|
|
54
|
+
return elements;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Render approval action buttons from the real protocol decision list.
|
|
58
|
+
* accept and decline are always offered (decline is a universal safety
|
|
59
|
+
* affordance even when the server omits it); acceptForSession and
|
|
60
|
+
* acceptWithExecpolicyAmendment appear only when the server offers them.
|
|
61
|
+
*/
|
|
62
|
+
function renderDecisionButtons(approval, expired, runId) {
|
|
63
|
+
if (expired) {
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
tag: 'div',
|
|
67
|
+
text: { tag: 'lark_md', content: '⏰ **审批已过期**' },
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
const BUTTONS = {
|
|
72
|
+
accept: { label: '✅ 允许', type: 'primary' },
|
|
73
|
+
acceptForSession: { label: '✅ 允许本次会话', type: 'primary' },
|
|
74
|
+
acceptWithExecpolicyAmendment: { label: '🔒 允许并记住命令', type: 'primary' },
|
|
75
|
+
acceptAll: { label: '✅ 允许所有', type: 'primary' },
|
|
76
|
+
decline: { label: '❌ 拒绝', type: 'danger' },
|
|
77
|
+
};
|
|
78
|
+
// Fixed presentation order; only offer decisions the protocol listed
|
|
79
|
+
// (accept/decline are always available; acceptAll is Claude 专属「允许所有」)。
|
|
80
|
+
const offered = [
|
|
81
|
+
'accept',
|
|
82
|
+
'acceptForSession',
|
|
83
|
+
'acceptWithExecpolicyAmendment',
|
|
84
|
+
'acceptAll',
|
|
85
|
+
'decline',
|
|
86
|
+
].filter((d) => d === 'accept' || d === 'decline' || approval.availableDecisions.includes(d));
|
|
87
|
+
// 决策按钮逐个作为 body 直接元素返回(CardKit body 纵向堆叠 = 上下排列)。
|
|
88
|
+
// 不能用 column_set width:auto 横排:手机窄屏下多个按钮挤在一行,按钮文字
|
|
89
|
+
// 被截断成省略号,用户无法辨认审批动作。
|
|
90
|
+
return offered.map((d) => ({
|
|
91
|
+
tag: 'button',
|
|
92
|
+
text: { tag: 'plain_text', content: BUTTONS[d].label },
|
|
93
|
+
type: BUTTONS[d].type,
|
|
94
|
+
behaviors: [
|
|
95
|
+
{
|
|
96
|
+
type: 'callback',
|
|
97
|
+
value: {
|
|
98
|
+
cmd: 'approval.respond',
|
|
99
|
+
decision: d,
|
|
100
|
+
requestId: approval.requestId,
|
|
101
|
+
runId,
|
|
102
|
+
nonce: mkNonce(),
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// Command Approval
|
|
110
|
+
// =============================================================================
|
|
111
|
+
function renderCommandApproval(approval, expired, runId) {
|
|
112
|
+
const elements = [];
|
|
113
|
+
// Command display
|
|
114
|
+
// review P2-2:命令里的反引号会提前终止 lark_md 行内代码 span,奇数次
|
|
115
|
+
// 反引号极易触发 11311 解析错误导致整卡失败(同 renderFileDiff 的中和策略)。
|
|
116
|
+
const neutralizedCommand = (approval.command ?? '').replace(/`/g, '·');
|
|
117
|
+
const cmdContent = neutralizedCommand
|
|
118
|
+
? `\`${neutralizedCommand.slice(0, 500)}\``
|
|
119
|
+
: '(no command)';
|
|
120
|
+
elements.push({
|
|
121
|
+
tag: 'div',
|
|
122
|
+
text: { tag: 'lark_md', content: `**⚡ 命令审批**\n${cmdContent}` },
|
|
123
|
+
});
|
|
124
|
+
if (approval.commandCwd) {
|
|
125
|
+
elements.push({
|
|
126
|
+
tag: 'div',
|
|
127
|
+
text: { tag: 'lark_md', content: `📂 \`${approval.commandCwd}\`` },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
pushIf(elements, reasonDiv(approval));
|
|
131
|
+
// Action buttons — derived from the real protocol decision list (see
|
|
132
|
+
// renderDecisionButtons): accept/decline always, acceptForSession and
|
|
133
|
+
// acceptWithExecpolicyAmendment only when the server offers them.
|
|
134
|
+
elements.push(...renderDecisionButtons(approval, expired, runId));
|
|
135
|
+
return elements;
|
|
136
|
+
}
|
|
137
|
+
// =============================================================================
|
|
138
|
+
// File Approval
|
|
139
|
+
// =============================================================================
|
|
140
|
+
function renderFileApproval(approval, expired, runId) {
|
|
141
|
+
const elements = [];
|
|
142
|
+
elements.push({
|
|
143
|
+
tag: 'div',
|
|
144
|
+
text: { tag: 'lark_md', content: '**📄 文件变更审批**' },
|
|
145
|
+
});
|
|
146
|
+
pushIf(elements, reasonDiv(approval));
|
|
147
|
+
// File changes list
|
|
148
|
+
if (approval.fileChanges && approval.fileChanges.length > 0) {
|
|
149
|
+
// 审批区 diff 总预算:估算函数不计审批区,多文件大 diff 可能顶爆 28KB 卡
|
|
150
|
+
// (ErrCode 11310)。与 tool-render 的 OUTPUT_MAX/BODY_TOTAL_MAX 同思路。
|
|
151
|
+
const TOTAL_DIFF_CHARS = 2400;
|
|
152
|
+
let renderedDiffChars = 0;
|
|
153
|
+
let omittedDiffs = 0;
|
|
154
|
+
for (const change of approval.fileChanges) {
|
|
155
|
+
const icon = change.kind === 'add' ? '🆕' : change.kind === 'delete' ? '🗑️' : '✏️';
|
|
156
|
+
elements.push({
|
|
157
|
+
tag: 'div',
|
|
158
|
+
text: { tag: 'lark_md', content: `${icon} \`${change.path}\` (${change.kind})` },
|
|
159
|
+
});
|
|
160
|
+
if (change.diff) {
|
|
161
|
+
if (renderedDiffChars < TOTAL_DIFF_CHARS) {
|
|
162
|
+
elements.push({
|
|
163
|
+
tag: 'div',
|
|
164
|
+
text: { tag: 'lark_md', content: renderFileDiff(change.diff) },
|
|
165
|
+
});
|
|
166
|
+
renderedDiffChars += Math.min(change.diff.length, MAX_DIFF_CHARS);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
omittedDiffs += 1;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (omittedDiffs > 0) {
|
|
174
|
+
elements.push({
|
|
175
|
+
tag: 'div',
|
|
176
|
+
text: { tag: 'lark_md', content: `_…${omittedDiffs} 个 diff 已省略(卡片预算)_` },
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Action buttons
|
|
181
|
+
// Derived from the real protocol decision list (see renderDecisionButtons).
|
|
182
|
+
elements.push(...renderDecisionButtons(approval, expired, runId));
|
|
183
|
+
return elements;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Render a file-change diff as a fenced code block.
|
|
187
|
+
* Truncates to protect the 28KB card budget and neutralizes triple backticks
|
|
188
|
+
* that would break the markdown fence (飞书 11311 类解析错误防线).
|
|
189
|
+
*/
|
|
190
|
+
function renderFileDiff(diff) {
|
|
191
|
+
const sanitized = diff.replace(/```/g, '···');
|
|
192
|
+
const truncated = sanitized.length > MAX_DIFF_CHARS
|
|
193
|
+
? `${sanitized.slice(0, MAX_DIFF_CHARS)}\n…(diff 已截断)`
|
|
194
|
+
: sanitized;
|
|
195
|
+
return `\`\`\`diff\n${truncated}\n\`\`\``;
|
|
196
|
+
}
|
|
197
|
+
/** Per-diff render cap (chars), see TOTAL_DIFF_CHARS for the approval-area cap. */
|
|
198
|
+
const MAX_DIFF_CHARS = 1200;
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// Permissions Approval
|
|
201
|
+
// =============================================================================
|
|
202
|
+
function renderPermissionsApproval(approval, expired, runId) {
|
|
203
|
+
const elements = [];
|
|
204
|
+
elements.push({
|
|
205
|
+
tag: 'div',
|
|
206
|
+
text: { tag: 'lark_md', content: '**🔒 权限审批**' },
|
|
207
|
+
});
|
|
208
|
+
pushIf(elements, reasonDiv(approval));
|
|
209
|
+
// Permission items with toggle buttons
|
|
210
|
+
if (approval.permissions?.items) {
|
|
211
|
+
for (const item of approval.permissions.items) {
|
|
212
|
+
const icon = item.selected ? '✅' : '⬜';
|
|
213
|
+
elements.push({
|
|
214
|
+
tag: 'div',
|
|
215
|
+
text: { tag: 'lark_md', content: `${icon} ${item.label}` },
|
|
216
|
+
});
|
|
217
|
+
elements.push({
|
|
218
|
+
tag: 'button',
|
|
219
|
+
text: {
|
|
220
|
+
tag: 'plain_text',
|
|
221
|
+
content: item.selected ? '取消授予' : '授予',
|
|
222
|
+
},
|
|
223
|
+
type: item.selected ? 'default' : 'primary',
|
|
224
|
+
size: 'small',
|
|
225
|
+
behaviors: [
|
|
226
|
+
{
|
|
227
|
+
type: 'callback',
|
|
228
|
+
value: {
|
|
229
|
+
cmd: 'approval.toggle',
|
|
230
|
+
requestId: approval.requestId,
|
|
231
|
+
permId: item.id,
|
|
232
|
+
selected: !item.selected,
|
|
233
|
+
runId,
|
|
234
|
+
nonce: mkNonce(),
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Action buttons
|
|
242
|
+
// Derived from the real protocol decision list (see renderDecisionButtons).
|
|
243
|
+
elements.push(...renderDecisionButtons(approval, expired, runId));
|
|
244
|
+
return elements;
|
|
245
|
+
}
|
|
246
|
+
// =============================================================================
|
|
247
|
+
// AskUserQuestion Approval
|
|
248
|
+
// =============================================================================
|
|
249
|
+
/**
|
|
250
|
+
* 渲染 Claude AskUserQuestion 选项卡(审批区复用)。
|
|
251
|
+
*
|
|
252
|
+
* - 单选问题:点击选项即选中(协调器在全部问题答完后自动提交)。
|
|
253
|
+
* - 多选问题:点击切换勾选,勾选后出现「提交答案」按钮。
|
|
254
|
+
* - 已选状态经 approval_view_updated 回流(view.questions[].selected)。
|
|
255
|
+
*/
|
|
256
|
+
function renderQuestionApproval(approval, expired, runId) {
|
|
257
|
+
const elements = [];
|
|
258
|
+
elements.push({
|
|
259
|
+
tag: 'div',
|
|
260
|
+
text: { tag: 'lark_md', content: '**❓ 需要你回答**' },
|
|
261
|
+
});
|
|
262
|
+
if (expired) {
|
|
263
|
+
elements.push({
|
|
264
|
+
tag: 'div',
|
|
265
|
+
text: { tag: 'lark_md', content: '⏰ **审批已过期**' },
|
|
266
|
+
});
|
|
267
|
+
return elements;
|
|
268
|
+
}
|
|
269
|
+
const questions = approval.questions ?? [];
|
|
270
|
+
for (let i = 0; i < questions.length; i++) {
|
|
271
|
+
const q = questions[i];
|
|
272
|
+
const selected = q.selected ?? [];
|
|
273
|
+
const header = q.header || `问题 ${i + 1}`;
|
|
274
|
+
elements.push({
|
|
275
|
+
tag: 'div',
|
|
276
|
+
text: {
|
|
277
|
+
tag: 'lark_md',
|
|
278
|
+
content: `**${header}**${q.multiSelect ? '(可多选)' : ''}\n${q.question}`,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
for (const option of q.options) {
|
|
282
|
+
const isSelected = selected.includes(option.label);
|
|
283
|
+
elements.push({
|
|
284
|
+
tag: 'div',
|
|
285
|
+
text: {
|
|
286
|
+
tag: 'lark_md',
|
|
287
|
+
content: `${isSelected ? '✅' : '⬜'} ${option.label}${option.description ? `\n_${option.description}_` : ''}`,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
elements.push({
|
|
291
|
+
tag: 'button',
|
|
292
|
+
text: { tag: 'plain_text', content: isSelected ? '取消选择' : '选择' },
|
|
293
|
+
type: isSelected ? 'default' : 'primary',
|
|
294
|
+
size: 'small',
|
|
295
|
+
behaviors: [
|
|
296
|
+
{
|
|
297
|
+
type: 'callback',
|
|
298
|
+
value: {
|
|
299
|
+
cmd: 'approval.answer',
|
|
300
|
+
requestId: approval.requestId,
|
|
301
|
+
questionIndex: i,
|
|
302
|
+
option: option.label,
|
|
303
|
+
runId,
|
|
304
|
+
nonce: mkNonce(),
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// 自定义答案(Other)的选中态展示:选项按钮无法表示自由文本,单独显示
|
|
311
|
+
// 已选文本,避免「点了没反应」的困惑(review P3)。
|
|
312
|
+
const customSelected = q.selected?.find((s) => !q.options.some((o) => o.label === s));
|
|
313
|
+
if (customSelected) {
|
|
314
|
+
elements.push({
|
|
315
|
+
tag: 'div',
|
|
316
|
+
text: { tag: 'lark_md', content: `✍️ 自定义答案:${customSelected}` },
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
// review P3-4:单选问题提供自定义答案(Other)输入——输入文本后点击
|
|
320
|
+
// 输入框右侧 ✓ 提交图标,input_value 经 connector raw 事件回传。
|
|
321
|
+
if (!q.multiSelect) {
|
|
322
|
+
elements.push({
|
|
323
|
+
tag: 'input',
|
|
324
|
+
name: `answer-custom-${approval.requestId}-${i}`,
|
|
325
|
+
placeholder: { tag: 'plain_text', content: '自定义答案(Other)…' },
|
|
326
|
+
behaviors: [
|
|
327
|
+
{
|
|
328
|
+
type: 'callback',
|
|
329
|
+
value: {
|
|
330
|
+
cmd: 'approval.answerCustom',
|
|
331
|
+
requestId: approval.requestId,
|
|
332
|
+
questionIndex: i,
|
|
333
|
+
runId,
|
|
334
|
+
nonce: mkNonce(),
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
// 多选问题:有勾选才给提交按钮(单选在协调器内即时提交)。
|
|
341
|
+
if (q.multiSelect && selected.length > 0) {
|
|
342
|
+
elements.push({
|
|
343
|
+
tag: 'button',
|
|
344
|
+
text: { tag: 'plain_text', content: `✅ 提交答案(已选 ${selected.length} 项)` },
|
|
345
|
+
type: 'primary',
|
|
346
|
+
behaviors: [
|
|
347
|
+
{
|
|
348
|
+
type: 'callback',
|
|
349
|
+
value: {
|
|
350
|
+
cmd: 'approval.answerSubmit',
|
|
351
|
+
requestId: approval.requestId,
|
|
352
|
+
questionIndex: i,
|
|
353
|
+
runId,
|
|
354
|
+
nonce: mkNonce(),
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return elements;
|
|
362
|
+
}
|
|
363
|
+
//# sourceMappingURL=approval-render.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CardSession, type CardChannel
|
|
1
|
+
import { CardSession, type CardChannel } from './card-session.js';
|
|
2
2
|
import { type BashState, type BashRenderOptions } from './bash-renderer.js';
|
|
3
3
|
/**
|
|
4
4
|
* Store-time 输出上限(字符,P1-3 层①)。§P1-3 建议「store-time 截断,
|
|
@@ -23,18 +23,6 @@ export declare function capBashOutput(value: string): string;
|
|
|
23
23
|
* independent cards.
|
|
24
24
|
*/
|
|
25
25
|
export declare class BashCardSession extends CardSession<BashState, BashRenderOptions> {
|
|
26
|
-
/**
|
|
27
|
-
* P1-3 层③:push 合批窗口(ms),与 RunCardSession 同语义。连续 update 各触发
|
|
28
|
-
* 一次全卡 render + patch 是 PATCH 风暴主源;窗口内多个事件复用同一次延迟 flush。
|
|
29
|
-
* 窗口大小可通过 constructor 覆盖(测试用小窗口/0 禁用)。
|
|
30
|
-
*/
|
|
31
|
-
private readonly coalesceMs;
|
|
32
|
-
private flushTimer;
|
|
33
|
-
private flushInFlight;
|
|
34
|
-
/** in-flight flush 期间到达的 update 标记:flush 完成后需重新调度(防 stale 帧)。 */
|
|
35
|
-
private pendingReschedule;
|
|
36
|
-
/** 当前 in-flight flush 的 promise:finish 在终态 patch 前 await 它保证顺序。 */
|
|
37
|
-
private flushP;
|
|
38
26
|
constructor(opts: {
|
|
39
27
|
connector: CardChannel;
|
|
40
28
|
chatId: string;
|
|
@@ -51,6 +39,7 @@ export declare class BashCardSession extends CardSession<BashState, BashRenderOp
|
|
|
51
39
|
protected get errorPrefix(): string;
|
|
52
40
|
protected renderCard(state: BashState, options: BashRenderOptions): object;
|
|
53
41
|
protected hasOwnBudgetProtection(): boolean;
|
|
42
|
+
protected isFlushableState(): boolean;
|
|
54
43
|
/** Patch the card with updated bash state (e.g. new stdout/stderr). */
|
|
55
44
|
update(patch: Partial<BashState>): Promise<void>;
|
|
56
45
|
/** Transition to a terminal state and patch the card. */
|
|
@@ -59,17 +48,6 @@ export declare class BashCardSession extends CardSession<BashState, BashRenderOp
|
|
|
59
48
|
stderr?: string;
|
|
60
49
|
output?: string;
|
|
61
50
|
}): Promise<void>;
|
|
62
|
-
/**
|
|
63
|
-
* 调度一次延迟 flush:已有 pending 调度则复用(合批核心);flushInFlight 期间
|
|
64
|
-
* 不再起 timer(防重入叠加),但置 pendingReschedule 让 in-flight 完成后补渲染。
|
|
65
|
-
*/
|
|
66
|
-
private scheduleFlush;
|
|
67
|
-
/** 执行一次 render + updateCard(合批窗口到期或 finish 即时 flush 时调用)。 */
|
|
68
|
-
private flush;
|
|
69
|
-
/** 取消待 flush 的合批调度(finish/settle 前清理,避免延迟 update 与终态竞争)。 */
|
|
70
|
-
private cancelPendingFlush;
|
|
71
|
-
/** settle 前兜底取消合批调度,避免 release 后 dangling timer 更新已释放的 stream。 */
|
|
72
|
-
settle(): Promise<StreamSettleResult>;
|
|
73
51
|
/**
|
|
74
52
|
* 合并 patch 并对 output/stderr 做 store-time 截断(P1-3 层①)。
|
|
75
53
|
* 截断发生在入口(store 时),渲染层无需再为大 state 付代价。
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CardSession } from './card-session.js';
|
|
2
2
|
import { renderBashCard } from './bash-renderer.js';
|
|
3
|
+
import { keepTail } from '../common/truncate.js';
|
|
3
4
|
/**
|
|
4
5
|
* Store-time 输出上限(字符,P1-3 层①)。§P1-3 建议「store-time 截断,
|
|
5
6
|
* 保留尾部,上限如 24~64KB」——取区间下界 24_000 字符(字符上限 ×4 字节最坏
|
|
@@ -8,10 +9,6 @@ import { renderBashCard } from './bash-renderer.js';
|
|
|
8
9
|
* state 与 bridge 局部变量。
|
|
9
10
|
*/
|
|
10
11
|
export const BASH_OUTPUT_STORE_CAP = 24_000;
|
|
11
|
-
/** 字符级 tail 保留(与 run-state.keepLatest 的 slice(-maxChars) 语义一致)。 */
|
|
12
|
-
function keepTail(value, maxChars) {
|
|
13
|
-
return value.length <= maxChars ? value : value.slice(-maxChars);
|
|
14
|
-
}
|
|
15
12
|
/**
|
|
16
13
|
* Store-time 截断入口(P1-3 层①/层④共用)。bridge 本地 output/stderr 与 session
|
|
17
14
|
* state 都走它,保证任意路径驻留的字符串 ≤ BASH_OUTPUT_STORE_CAP 字符。
|
|
@@ -29,18 +26,6 @@ export function capBashOutput(value) {
|
|
|
29
26
|
* independent cards.
|
|
30
27
|
*/
|
|
31
28
|
export class BashCardSession extends CardSession {
|
|
32
|
-
/**
|
|
33
|
-
* P1-3 层③:push 合批窗口(ms),与 RunCardSession 同语义。连续 update 各触发
|
|
34
|
-
* 一次全卡 render + patch 是 PATCH 风暴主源;窗口内多个事件复用同一次延迟 flush。
|
|
35
|
-
* 窗口大小可通过 constructor 覆盖(测试用小窗口/0 禁用)。
|
|
36
|
-
*/
|
|
37
|
-
coalesceMs;
|
|
38
|
-
flushTimer;
|
|
39
|
-
flushInFlight = false;
|
|
40
|
-
/** in-flight flush 期间到达的 update 标记:flush 完成后需重新调度(防 stale 帧)。 */
|
|
41
|
-
pendingReschedule = false;
|
|
42
|
-
/** 当前 in-flight flush 的 promise:finish 在终态 patch 前 await 它保证顺序。 */
|
|
43
|
-
flushP;
|
|
44
29
|
constructor(opts) {
|
|
45
30
|
super({
|
|
46
31
|
runId: opts.runId,
|
|
@@ -56,8 +41,8 @@ export class BashCardSession extends CardSession {
|
|
|
56
41
|
renderOptions: opts.renderOptions,
|
|
57
42
|
startTimeoutMs: opts.startTimeoutMs,
|
|
58
43
|
settleTimeoutMs: opts.settleTimeoutMs,
|
|
44
|
+
coalesceMs: opts.coalesceMs,
|
|
59
45
|
});
|
|
60
|
-
this.coalesceMs = opts.coalesceMs ?? 100;
|
|
61
46
|
}
|
|
62
47
|
get logPrefix() {
|
|
63
48
|
return '[bash-card]';
|
|
@@ -76,6 +61,10 @@ export class BashCardSession extends CardSession {
|
|
|
76
61
|
// 卡应跳过它,靠自身 renderer 保护。
|
|
77
62
|
return true;
|
|
78
63
|
}
|
|
64
|
+
isFlushableState() {
|
|
65
|
+
// finish() 已把 state 转终态后,in-flight/延迟 flush 不再用旧 state patch。
|
|
66
|
+
return this.state.terminal === 'running';
|
|
67
|
+
}
|
|
79
68
|
/** Patch the card with updated bash state (e.g. new stdout/stderr). */
|
|
80
69
|
async update(patch) {
|
|
81
70
|
this.state = this.applyCap(patch);
|
|
@@ -102,62 +91,6 @@ export class BashCardSession extends CardSession {
|
|
|
102
91
|
await this.updateCard();
|
|
103
92
|
this.release();
|
|
104
93
|
}
|
|
105
|
-
/**
|
|
106
|
-
* 调度一次延迟 flush:已有 pending 调度则复用(合批核心);flushInFlight 期间
|
|
107
|
-
* 不再起 timer(防重入叠加),但置 pendingReschedule 让 in-flight 完成后补渲染。
|
|
108
|
-
*/
|
|
109
|
-
scheduleFlush() {
|
|
110
|
-
if (this.flushTimer)
|
|
111
|
-
return;
|
|
112
|
-
if (this.flushInFlight) {
|
|
113
|
-
this.pendingReschedule = true;
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
this.flushTimer = setTimeout(() => {
|
|
117
|
-
this.flushTimer = undefined;
|
|
118
|
-
void this.flush();
|
|
119
|
-
}, this.coalesceMs);
|
|
120
|
-
}
|
|
121
|
-
/** 执行一次 render + updateCard(合批窗口到期或 finish 即时 flush 时调用)。 */
|
|
122
|
-
async flush() {
|
|
123
|
-
if (this.flushInFlight)
|
|
124
|
-
return;
|
|
125
|
-
// 终态守卫:finish 已把 state 转终态后,in-flight/延迟 flush 不再用旧 state patch
|
|
126
|
-
if (this.state.terminal !== 'running')
|
|
127
|
-
return;
|
|
128
|
-
this.flushInFlight = true;
|
|
129
|
-
const flushPromise = (async () => {
|
|
130
|
-
try {
|
|
131
|
-
await this.updateCard();
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
|
-
this.flushInFlight = false;
|
|
135
|
-
this.flushP = undefined;
|
|
136
|
-
// in-flight 期间若有 update 到达(pendingReschedule),重新调度一次渲染
|
|
137
|
-
if (this.pendingReschedule) {
|
|
138
|
-
this.pendingReschedule = false;
|
|
139
|
-
this.scheduleFlush();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
})();
|
|
143
|
-
this.flushP = flushPromise;
|
|
144
|
-
return flushPromise;
|
|
145
|
-
}
|
|
146
|
-
/** 取消待 flush 的合批调度(finish/settle 前清理,避免延迟 update 与终态竞争)。 */
|
|
147
|
-
cancelPendingFlush() {
|
|
148
|
-
this.pendingReschedule = false;
|
|
149
|
-
if (this.flushTimer) {
|
|
150
|
-
clearTimeout(this.flushTimer);
|
|
151
|
-
this.flushTimer = undefined;
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
/** settle 前兜底取消合批调度,避免 release 后 dangling timer 更新已释放的 stream。 */
|
|
157
|
-
async settle() {
|
|
158
|
-
this.cancelPendingFlush();
|
|
159
|
-
return super.settle();
|
|
160
|
-
}
|
|
161
94
|
/**
|
|
162
95
|
* 合并 patch 并对 output/stderr 做 store-time 截断(P1-3 层①)。
|
|
163
96
|
* 截断发生在入口(store 时),渲染层无需再为大 state 付代价。
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { terminalToColor, terminalToLabel, stopButton } from './card-shared.js';
|
|
2
2
|
import { markdownDiv } from './collapsible.js';
|
|
3
|
-
import { truncateUtf8 } from './text-truncate.js';
|
|
3
|
+
import { truncateUtf8, CARD_BUDGET_BYTES } from './text-truncate.js';
|
|
4
4
|
/**
|
|
5
5
|
* 飞书卡片大小限制(安全阈值,与 run-renderer 一致)。
|
|
6
|
-
*
|
|
6
|
+
* 从共享的 text-truncate.ts 导入,避免三处各自定义后数值漂移。
|
|
7
7
|
*/
|
|
8
|
-
const CARD_BUDGET_BYTES = 28_000;
|
|
9
8
|
/**
|
|
10
9
|
* 单个输出字段的默认最大字节数(保守值,用于正常路径)。
|
|
11
10
|
* 飞书卡片限制约 28KB,留余量给卡片其他部分(header、status row、footer)
|