lark-remote 0.5.19 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/bridge/approval-actions.d.ts +1 -1
- package/dist/bridge/approval-actions.js +1 -1
- package/dist/bridge/bash-run.d.ts +71 -0
- package/dist/bridge/bash-run.js +217 -0
- package/dist/bridge/compact.d.ts +155 -0
- package/dist/bridge/compact.js +450 -0
- package/dist/bridge/final-usage.js +4 -4
- package/dist/bridge/inbound-media.js +72 -46
- package/dist/bridge/index.d.ts +106 -153
- package/dist/bridge/index.js +400 -1000
- package/dist/bridge/job-watch.d.ts +68 -0
- package/dist/bridge/job-watch.js +146 -0
- package/dist/bridge/queue-manager.d.ts +3 -3
- package/dist/bridge/queue-manager.js +10 -29
- package/dist/card/approval-render.d.ts +2 -2
- package/dist/card/approval-render.js +141 -130
- package/dist/card/card-budget.js +123 -98
- package/dist/card/card-helpers.d.ts +3 -3
- package/dist/card/card-helpers.js +6 -6
- package/dist/card/final-answer-file.js +1 -1
- package/dist/card/run-renderer.d.ts +3 -3
- package/dist/card/run-renderer.js +114 -159
- package/dist/card/run-state.d.ts +1 -15
- package/dist/card/run-state.js +358 -292
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +21 -17
- package/dist/card/usage-format.d.ts +22 -7
- package/dist/card/usage-format.js +119 -100
- package/dist/cli.js +15 -3
- package/dist/clone.js +2 -2
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/config/codex-config.js +130 -106
- package/dist/config/index.d.ts +16 -2
- package/dist/config/index.js +23 -8
- package/dist/connector/index.js +6 -6
- package/dist/error-classification.js +3 -2
- package/dist/inbound/placeholder.d.ts +1 -1
- package/dist/inbound/placeholder.js +1 -1
- package/dist/index.js +387 -281
- package/dist/logger/index.js +6 -6
- package/dist/message-handlers.d.ts +0 -5
- package/dist/message-handlers.js +254 -237
- package/dist/model-catalog.d.ts +2 -2
- package/dist/model-catalog.js +3 -8
- package/dist/platform/identity.js +1 -1
- package/dist/platform/path.d.ts +1 -1
- package/dist/platform/path.js +1 -1
- package/dist/platform/sleep-blocker.js +1 -1
- package/dist/platform/terminator-win32.js +2 -1
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +190 -152
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/config/dsh.d.ts +4 -8
- package/dist/router/config/dsh.js +3 -10
- package/dist/router/config/kimi.js +1 -1
- package/dist/router/config/types.d.ts +1 -1
- package/dist/router/config-diff.js +1 -1
- package/dist/router/index.d.ts +56 -11
- package/dist/router/index.js +401 -391
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +6 -4
- package/dist/runner/codex/app-server/runner.d.ts +9 -2
- package/dist/runner/codex/app-server/runner.js +30 -25
- package/dist/runner/codex/app-server/translator.d.ts +2 -5
- package/dist/runner/codex/app-server/translator.js +291 -238
- package/dist/runner/common/acp/acp-approval.d.ts +1 -1
- package/dist/runner/common/acp/acp-approval.js +1 -1
- package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
- package/dist/runner/common/acp/base-acp-runner.js +26 -65
- package/dist/runner/common/acp/base-translator.d.ts +12 -2
- package/dist/runner/common/acp/base-translator.js +50 -26
- package/dist/runner/common/acp/protocol-types.d.ts +1 -1
- package/dist/runner/common/acp/protocol-types.js +2 -2
- package/dist/runner/common/connection-based-runner.d.ts +32 -1
- package/dist/runner/common/connection-based-runner.js +32 -2
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
- package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
- package/dist/runner/common/spawning-runner.d.ts +6 -1
- package/dist/runner/common/spawning-runner.js +9 -4
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/catalog-probe.d.ts +17 -0
- package/dist/runner/dsh/catalog-probe.js +10 -0
- package/dist/runner/dsh/client.d.ts +7 -11
- package/dist/runner/dsh/client.js +356 -289
- package/dist/runner/dsh/exit-cancel.d.ts +2 -2
- package/dist/runner/dsh/exit-cancel.js +2 -2
- package/dist/runner/dsh/inflight.d.ts +1 -1
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +44 -4
- package/dist/runner/dsh/runner.js +296 -199
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +1 -2
- package/dist/runner/kimi/acp/runner.js +82 -46
- package/dist/runner/kimi/acp/translator.d.ts +1 -1
- package/dist/runner/kimi/acp/translator.js +3 -4
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +4 -4
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
- package/dist/runner/pi/rpc/protocol-types.js +1 -1
- package/dist/runner/pi/rpc/runner.d.ts +3 -0
- package/dist/runner/pi/rpc/runner.js +27 -42
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/runner/types.d.ts +2 -2
- package/dist/runner/types.js +1 -1
- package/dist/session/claude/sessions.d.ts +45 -0
- package/dist/session/claude/sessions.js +160 -122
- package/dist/session/codex/rollout-reader.js +181 -182
- package/dist/session/common/throw-session-list-error.d.ts +10 -0
- package/dist/session/common/throw-session-list-error.js +16 -0
- package/dist/session/dsh/sessions.js +291 -235
- package/dist/session/kimi/sessions.d.ts +40 -8
- package/dist/session/kimi/sessions.js +373 -301
- package/dist/session/opencode/sessions.d.ts +142 -1
- package/dist/session/opencode/sessions.js +378 -321
- package/dist/session/pi/sessions.js +4 -5
- package/dist/session/session-store.d.ts +9 -1
- package/dist/session/session-store.js +99 -99
- package/dist/update/version-check.js +2 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.js +1 -1
- package/package.json +1 -1
|
@@ -66,13 +66,18 @@ function pushIf(arr, el) {
|
|
|
66
66
|
if (el !== null)
|
|
67
67
|
arr.push(el);
|
|
68
68
|
}
|
|
69
|
+
const APPROVAL_AREA_RENDERERS = new Map([
|
|
70
|
+
['command', renderCommandApproval],
|
|
71
|
+
['tool', renderToolApproval],
|
|
72
|
+
['file', renderFileApproval],
|
|
73
|
+
['permissions', renderPermissionsApproval],
|
|
74
|
+
['question', renderQuestionApproval],
|
|
75
|
+
]);
|
|
69
76
|
/**
|
|
70
77
|
* Render the approval area for a run card.
|
|
71
78
|
* Returns an array of CardKit 2.0 elements to append to the card body.
|
|
72
79
|
*/
|
|
73
80
|
export function renderApprovalArea(approval, opts) {
|
|
74
|
-
if (!approval)
|
|
75
|
-
return [];
|
|
76
81
|
// 终态(done/error/interrupted/idle_timeout)下 run 已结束:coordinator 已
|
|
77
82
|
// 释放,按钮点击无处响应,隐藏整个审批区避免"点了没反应"。
|
|
78
83
|
if (isTerminalHidden(opts))
|
|
@@ -85,21 +90,10 @@ export function renderApprovalArea(approval, opts) {
|
|
|
85
90
|
text: { tag: 'lark_md', content: `ℹ️ ${neutralized}` },
|
|
86
91
|
});
|
|
87
92
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
elements.push(...renderToolApproval(approval, opts?.expired, opts?.runId));
|
|
93
|
-
}
|
|
94
|
-
else if (approval.kind === 'file') {
|
|
95
|
-
elements.push(...renderFileApproval(approval, opts?.expired, opts?.runId));
|
|
96
|
-
}
|
|
97
|
-
else if (approval.kind === 'permissions') {
|
|
98
|
-
elements.push(...renderPermissionsApproval(approval, opts?.expired, opts?.runId));
|
|
99
|
-
}
|
|
100
|
-
else if (approval.kind === 'question') {
|
|
101
|
-
elements.push(...renderQuestionApproval(approval, opts?.expired, opts?.runId));
|
|
102
|
-
}
|
|
93
|
+
// kind → 渲染器查表(Map 保持插入顺序,未知 kind 与原先无 else 分支一致:不追加)。
|
|
94
|
+
const render = APPROVAL_AREA_RENDERERS.get(approval.kind);
|
|
95
|
+
if (render)
|
|
96
|
+
elements.push(...render(approval, opts?.expired, opts?.runId));
|
|
103
97
|
// 响应发送失败:明确告知原因 + 可重试,而不是静默停在可点状态。
|
|
104
98
|
if (approval.deliveryError) {
|
|
105
99
|
const neutralized = capApprovalText(approval.deliveryError);
|
|
@@ -123,8 +117,6 @@ export function renderApprovalArea(approval, opts) {
|
|
|
123
117
|
* 结构性保证 ≤ 几个元素 + ≤ 1KB 文本,审批入口(红线)在任何降级档都活着。
|
|
124
118
|
*/
|
|
125
119
|
export function renderApprovalAreaCompact(approval, opts) {
|
|
126
|
-
if (!approval)
|
|
127
|
-
return [];
|
|
128
120
|
if (isTerminalHidden(opts))
|
|
129
121
|
return [];
|
|
130
122
|
const elements = [
|
|
@@ -433,9 +425,26 @@ function renderFileDiff(diff) {
|
|
|
433
425
|
}
|
|
434
426
|
/** Per-diff render cap (chars), see TOTAL_DIFF_CHARS for the approval-area cap. */
|
|
435
427
|
const MAX_DIFF_CHARS = 1200;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
428
|
+
/** 单个权限条目:label div + toggle 按钮(模块级命名函数,供循环逐个调用)。 */
|
|
429
|
+
function renderPermissionItem(item, requestId, runId) {
|
|
430
|
+
const icon = item.selected ? '✅' : '⬜';
|
|
431
|
+
// label 仅展示(payload 用 item.id),可安全截断。
|
|
432
|
+
const label = capApprovalText(item.label, APPROVAL_LABEL_BYTES);
|
|
433
|
+
return [
|
|
434
|
+
{
|
|
435
|
+
tag: 'div',
|
|
436
|
+
text: { tag: 'lark_md', content: `${icon} ${label}` },
|
|
437
|
+
},
|
|
438
|
+
cardButton(item.selected ? '取消授予' : '授予', {
|
|
439
|
+
cmd: 'approval.toggle',
|
|
440
|
+
requestId,
|
|
441
|
+
permId: item.id,
|
|
442
|
+
selected: !item.selected,
|
|
443
|
+
runId,
|
|
444
|
+
nonce: mkNonce(),
|
|
445
|
+
}, { type: item.selected ? 'default' : 'primary', size: 'small' }),
|
|
446
|
+
];
|
|
447
|
+
}
|
|
439
448
|
function renderPermissionsApproval(approval, expired, runId) {
|
|
440
449
|
const elements = [];
|
|
441
450
|
elements.push({
|
|
@@ -449,21 +458,7 @@ function renderPermissionsApproval(approval, expired, runId) {
|
|
|
449
458
|
const items = approval.permissions.items.slice(0, MAX_RENDERED_CHANGE_ITEMS);
|
|
450
459
|
const omittedItems = approval.permissions.items.length - items.length;
|
|
451
460
|
for (const item of items) {
|
|
452
|
-
|
|
453
|
-
// label 仅展示(payload 用 item.id),可安全截断。
|
|
454
|
-
const label = capApprovalText(item.label, APPROVAL_LABEL_BYTES);
|
|
455
|
-
elements.push({
|
|
456
|
-
tag: 'div',
|
|
457
|
-
text: { tag: 'lark_md', content: `${icon} ${label}` },
|
|
458
|
-
});
|
|
459
|
-
elements.push(cardButton(item.selected ? '取消授予' : '授予', {
|
|
460
|
-
cmd: 'approval.toggle',
|
|
461
|
-
requestId: approval.requestId,
|
|
462
|
-
permId: item.id,
|
|
463
|
-
selected: !item.selected,
|
|
464
|
-
runId,
|
|
465
|
-
nonce: mkNonce(),
|
|
466
|
-
}, { type: item.selected ? 'default' : 'primary', size: 'small' }));
|
|
461
|
+
elements.push(...renderPermissionItem(item, approval.requestId, runId));
|
|
467
462
|
}
|
|
468
463
|
if (omittedItems > 0) {
|
|
469
464
|
elements.push({
|
|
@@ -533,112 +528,128 @@ function renderQuestionApproval(approval, expired, runId) {
|
|
|
533
528
|
break;
|
|
534
529
|
}
|
|
535
530
|
renderedQuestions++;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
531
|
+
elements.push(...renderQuestionBlock(approval, i, runId));
|
|
532
|
+
}
|
|
533
|
+
// 提问卡底部统一「跳过回答」:走现有 approval.respond + decision=decline
|
|
534
|
+
// (coordinator 对 question 允许 decline 作为安全兜底),runner 按协议映射
|
|
535
|
+
// 跳过语义(Claude deny / Codex 空 answers / Kimi action decline / Pi cancelled)。
|
|
536
|
+
if (!expired) {
|
|
537
|
+
elements.push(cardButton('⏭️ 跳过回答', {
|
|
538
|
+
cmd: 'approval.respond',
|
|
539
|
+
decision: 'decline',
|
|
540
|
+
requestId: approval.requestId,
|
|
541
|
+
runId,
|
|
542
|
+
nonce: mkNonce(),
|
|
543
|
+
}, { type: 'default' }));
|
|
544
|
+
}
|
|
545
|
+
return elements;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* 渲染单题区块:题面 + 详情面板 + 选项行/输入框 + (多选)提交按钮。
|
|
549
|
+
* 从 renderQuestionApproval 的循环体抽出;循环保留题数/元素预算裁剪与
|
|
550
|
+
* 卡片装配。交互组件(按钮/输入框)继续直挂 body elements——本函数输出
|
|
551
|
+
* 必须始终被 expectNoV1ActionContainer 断言覆盖(200861 红线,见
|
|
552
|
+
* run-renderer.test.ts 的 question 系列);nonce 仍由各交互元素生成时
|
|
553
|
+
* 各自调用一次 mkNonce(),不在块外缓存复用。
|
|
554
|
+
*/
|
|
555
|
+
function renderQuestionBlock(approval, qIndex, runId) {
|
|
556
|
+
const q = approval.questions?.[qIndex];
|
|
557
|
+
// 越界防御(正常调用由循环边界保证);不虚构题面。
|
|
558
|
+
if (!q)
|
|
559
|
+
return [];
|
|
560
|
+
const elements = [];
|
|
561
|
+
const selected = q.selected ?? [];
|
|
562
|
+
const header = capApprovalText(q.header || `问题 ${qIndex + 1}`, APPROVAL_LABEL_BYTES);
|
|
563
|
+
const freeText = (q.options?.length ?? 0) === 0;
|
|
564
|
+
elements.push({
|
|
565
|
+
tag: 'div',
|
|
566
|
+
text: {
|
|
567
|
+
tag: 'lark_md',
|
|
568
|
+
content: `**${header}**${q.multiSelect ? '(可多选)' : ''}\n${capApprovalText(q.question)}`,
|
|
569
|
+
},
|
|
570
|
+
});
|
|
571
|
+
if (q.detail) {
|
|
572
|
+
// 题面外的补充正文(DSH plan-review 提问把计划全文放这里):必须随题
|
|
573
|
+
// 渲染,否则用户是在「看不见计划」的情况下批准。与 ExitPlanMode 的
|
|
574
|
+
// 计划面板同口径:反引号中和 + 表格限数 + 字节预算折叠展示。
|
|
575
|
+
const neutralized = q.detail.replace(/`/g, '·');
|
|
576
|
+
const content = truncateUtf8(truncateMarkdownTables(neutralized), PLAN_PANEL_BYTES);
|
|
577
|
+
elements.push(collapsibleMarkdownPanel({
|
|
578
|
+
title: '📋 详情',
|
|
579
|
+
expanded: false,
|
|
580
|
+
border: 'blue',
|
|
581
|
+
content,
|
|
582
|
+
}));
|
|
583
|
+
}
|
|
584
|
+
if (freeText) {
|
|
585
|
+
// 自由文本题(Codex options:null / Pi extension input):无选项行,
|
|
586
|
+
// 直接渲染题面 + 输入框,提交走 answerCustom(coordinator 不要求有选项)。
|
|
587
|
+
elements.push(renderAnswerInput(approval.requestId, qIndex, q.placeholder ?? '输入回答…', runId));
|
|
588
|
+
// 已答回显:input_value 不跨卡片更新保留,多题场景下用户提交后若还有
|
|
589
|
+
// 其他问题未答,卡片必须展示已答文本(与自定义答案选中态同模式)。
|
|
590
|
+
const answered = q.selected?.find((s) => s.length > 0);
|
|
591
|
+
if (answered) {
|
|
592
|
+
elements.push({
|
|
593
|
+
tag: 'div',
|
|
594
|
+
text: { tag: 'lark_md', content: `✍️ 已答:${capApprovalText(answered)}` },
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
return elements;
|
|
598
|
+
}
|
|
599
|
+
// 选项渲染上限(label 是回调 payload 不可截断,只能按条数折叠)。
|
|
600
|
+
const options = q.options ?? [];
|
|
601
|
+
for (const option of options.slice(0, MAX_RENDERED_OPTIONS)) {
|
|
602
|
+
const isSelected = selected.includes(option.label);
|
|
603
|
+
// 单选/多选图标区分:单选 ⚪/🔵(radio 隐喻),多选 ⬜/☑️(checkbox 隐喻)。
|
|
604
|
+
const icon = q.multiSelect ? (isSelected ? '☑️' : '⬜') : isSelected ? '🔵' : '⚪';
|
|
605
|
+
elements.push(renderOptionRow(approval.requestId, qIndex, option, isSelected, icon, runId));
|
|
606
|
+
}
|
|
607
|
+
if (options.length > MAX_RENDERED_OPTIONS) {
|
|
540
608
|
elements.push({
|
|
541
609
|
tag: 'div',
|
|
542
610
|
text: {
|
|
543
611
|
tag: 'lark_md',
|
|
544
|
-
content:
|
|
612
|
+
content: `_…另有 ${options.length - MAX_RENDERED_OPTIONS} 个选项未展示(卡片元素预算)_`,
|
|
545
613
|
},
|
|
546
614
|
});
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
text: { tag: 'lark_md', content: `✍️ 已答:${capApprovalText(answered)}` },
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
// 选项渲染上限(label 是回调 payload 不可截断,只能按条数折叠)。
|
|
576
|
-
const options = q.options ?? [];
|
|
577
|
-
for (const option of options.slice(0, MAX_RENDERED_OPTIONS)) {
|
|
578
|
-
const isSelected = selected.includes(option.label);
|
|
579
|
-
// 单选/多选图标区分:单选 ⚪/🔵(radio 隐喻),多选 ⬜/☑️(checkbox 隐喻)。
|
|
580
|
-
const icon = q.multiSelect ? (isSelected ? '☑️' : '⬜') : isSelected ? '🔵' : '⚪';
|
|
581
|
-
elements.push(renderOptionRow(approval.requestId, i, option, isSelected, icon, runId));
|
|
582
|
-
}
|
|
583
|
-
if (options.length > MAX_RENDERED_OPTIONS) {
|
|
584
|
-
elements.push({
|
|
585
|
-
tag: 'div',
|
|
586
|
-
text: {
|
|
587
|
-
tag: 'lark_md',
|
|
588
|
-
content: `_…另有 ${options.length - MAX_RENDERED_OPTIONS} 个选项未展示(卡片元素预算)_`,
|
|
589
|
-
},
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
// 自定义答案(Other)的选中态展示:选项按钮无法表示自由文本,单独显示
|
|
593
|
-
// 已选文本,避免「点了没反应」的困惑。
|
|
594
|
-
const customSelected = q.selected?.find((s) => !q.options.some((o) => o.label === s));
|
|
595
|
-
if (customSelected) {
|
|
615
|
+
}
|
|
616
|
+
// 自定义答案(Other)的选中态展示:选项按钮无法表示自由文本,单独显示
|
|
617
|
+
// 已选文本,避免「点了没反应」的困惑。
|
|
618
|
+
const customSelected = q.selected?.find((s) => !q.options.some((o) => o.label === s));
|
|
619
|
+
if (customSelected) {
|
|
620
|
+
elements.push({
|
|
621
|
+
tag: 'div',
|
|
622
|
+
text: { tag: 'lark_md', content: `✍️ 自定义答案:${capApprovalText(customSelected)}` },
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
// 单选问题提供自定义答案(Other)输入——输入文本后提交
|
|
626
|
+
// (移动端键盘回车/完成键,桌面端输入框右侧提交图标),input_value 经
|
|
627
|
+
// connector raw 事件回传。
|
|
628
|
+
// 显隐按 isOther !== false:Kimi form 会丢弃非声明选项值,翻译时置
|
|
629
|
+
// isOther=false 隐藏输入;Claude 未设置该字段默认显示(行为不变)。
|
|
630
|
+
if (!q.multiSelect && q.isOther !== false) {
|
|
631
|
+
elements.push(renderAnswerInput(approval.requestId, qIndex, '自定义答案(Other)…', runId, 'approval.answerCustom'));
|
|
632
|
+
}
|
|
633
|
+
// Codex user_note:选项之外的补充说明(allowNote 数据驱动门控)。
|
|
634
|
+
// 提交走 answerNote(coordinator 记录 note,随答案一起提交),已填回显。
|
|
635
|
+
if (q.allowNote === true) {
|
|
636
|
+
elements.push(renderAnswerInput(approval.requestId, qIndex, '补充说明(可选)…', runId, 'approval.answerNote'));
|
|
637
|
+
if (q.note) {
|
|
596
638
|
elements.push({
|
|
597
639
|
tag: 'div',
|
|
598
|
-
text: { tag: 'lark_md', content:
|
|
640
|
+
text: { tag: 'lark_md', content: `📝 ${capApprovalText(q.note)}` },
|
|
599
641
|
});
|
|
600
642
|
}
|
|
601
|
-
// 单选问题提供自定义答案(Other)输入——输入文本后提交
|
|
602
|
-
// (移动端键盘回车/完成键,桌面端输入框右侧提交图标),input_value 经
|
|
603
|
-
// connector raw 事件回传。
|
|
604
|
-
// 显隐按 isOther !== false:Kimi form 会丢弃非声明选项值,翻译时置
|
|
605
|
-
// isOther=false 隐藏输入;Claude 未设置该字段默认显示(行为不变)。
|
|
606
|
-
if (!q.multiSelect && q.isOther !== false) {
|
|
607
|
-
elements.push(renderAnswerInput(approval.requestId, i, '自定义答案(Other)…', runId, 'approval.answerCustom'));
|
|
608
|
-
}
|
|
609
|
-
// Codex user_note:选项之外的补充说明(allowNote 数据驱动门控)。
|
|
610
|
-
// 提交走 answerNote(coordinator 记录 note,随答案一起提交),已填回显。
|
|
611
|
-
if (q.allowNote === true) {
|
|
612
|
-
elements.push(renderAnswerInput(approval.requestId, i, '补充说明(可选)…', runId, 'approval.answerNote'));
|
|
613
|
-
if (q.note) {
|
|
614
|
-
elements.push({
|
|
615
|
-
tag: 'div',
|
|
616
|
-
text: { tag: 'lark_md', content: `📝 ${capApprovalText(q.note)}` },
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
// 多选问题:有勾选才给提交按钮(单选在协调器内即时提交)。
|
|
621
|
-
if (q.multiSelect && selected.length > 0) {
|
|
622
|
-
elements.push(cardButton(`✅ 提交答案(已选 ${selected.length} 项)`, {
|
|
623
|
-
cmd: 'approval.answerSubmit',
|
|
624
|
-
requestId: approval.requestId,
|
|
625
|
-
questionIndex: i,
|
|
626
|
-
runId,
|
|
627
|
-
nonce: mkNonce(),
|
|
628
|
-
}, { type: 'primary' }));
|
|
629
|
-
}
|
|
630
643
|
}
|
|
631
|
-
//
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
elements.push(cardButton('⏭️ 跳过回答', {
|
|
636
|
-
cmd: 'approval.respond',
|
|
637
|
-
decision: 'decline',
|
|
644
|
+
// 多选问题:有勾选才给提交按钮(单选在协调器内即时提交)。
|
|
645
|
+
if (q.multiSelect && selected.length > 0) {
|
|
646
|
+
elements.push(cardButton(`✅ 提交答案(已选 ${selected.length} 项)`, {
|
|
647
|
+
cmd: 'approval.answerSubmit',
|
|
638
648
|
requestId: approval.requestId,
|
|
649
|
+
questionIndex: qIndex,
|
|
639
650
|
runId,
|
|
640
651
|
nonce: mkNonce(),
|
|
641
|
-
}, { type: '
|
|
652
|
+
}, { type: 'primary' }));
|
|
642
653
|
}
|
|
643
654
|
return elements;
|
|
644
655
|
}
|
|
@@ -693,7 +704,7 @@ function renderOptionRow(requestId, questionIndex, option, isSelected, icon, run
|
|
|
693
704
|
function renderAnswerInput(requestId, questionIndex, placeholder, runId, cmd = 'approval.answerCustom') {
|
|
694
705
|
// name 必须含 cmd,保证同一题同时渲染多个输入框(如单选选项题的
|
|
695
706
|
// Other 输入 + Note 输入)时元素 name 唯一——飞书对同卡内重复 name
|
|
696
|
-
// 报 ErrCode 11310
|
|
707
|
+
// 报 ErrCode 11310 拒绝整卡。
|
|
697
708
|
const kind = cmd === 'approval.answerNote' ? 'note' : 'custom';
|
|
698
709
|
return {
|
|
699
710
|
tag: 'input',
|
package/dist/card/card-budget.js
CHANGED
|
@@ -160,125 +160,130 @@ function enforceElementBudget(card, opts = {}, bytesBefore) {
|
|
|
160
160
|
// 它最终会落到最小卡(节点数固定很小)。
|
|
161
161
|
return enforceByteBudget(card, opts, bytesBefore);
|
|
162
162
|
}
|
|
163
|
-
|
|
164
|
-
* Byte-budget enforcement, extracted so table-limit fix can delegate to it
|
|
165
|
-
* when table truncation alone doesn't bring the card under the byte limit.
|
|
166
|
-
*
|
|
167
|
-
* 裁剪判定是**结构化**的:所有 collapsible_panel 都是待裁剪对象,不看面板
|
|
168
|
-
* 标题文本。历史教训(2026-08-08):旧实现用
|
|
169
|
-
* emoji 标题模式(🤖👤💭🔧🟢🔴)识别「会话事件面板」,但真实
|
|
170
|
-
* sessionEventPanel 产出的标题是裸英文 type 名(emoji 在事件正文而非标题),
|
|
171
|
-
* 导致裁剪阶段对真实 resume 卡零命中,任何 >28KB 的卡片无裁剪直通极端降级,
|
|
172
|
-
* 整卡被替换成一句话。
|
|
173
|
-
*
|
|
174
|
-
* 渐进式降级(每级仍超限才进入下一级):
|
|
175
|
-
* 阶段1 逐面板截断内容(maxPanelContentBytes)
|
|
176
|
-
* 阶段2 限制面板数量(删除最老,maxEventPanels)
|
|
177
|
-
* 阶段3 丢弃全部面板,保留骨架(header/usage/按钮)+ 省略提示
|
|
178
|
-
* 阶段4 截断剩余顶层 div 文本
|
|
179
|
-
* 阶段5 最终兜底 minimal card(骨架本身已超限的病理场景)
|
|
180
|
-
*/
|
|
181
|
-
function enforceByteBudget(truncated, opts = {}, bytesBefore) {
|
|
163
|
+
function makeByteBudgetContext(truncated, opts, bytesBefore) {
|
|
182
164
|
const { maxEventPanels = 5, maxPanelContentBytes = 2000, truncationHint = '📜 还有 {count} 个事件未显示', } = opts;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
165
|
+
return {
|
|
166
|
+
truncated,
|
|
167
|
+
elements: truncated.body?.elements ?? [],
|
|
168
|
+
reasons: [],
|
|
169
|
+
omitted: 0,
|
|
170
|
+
modifications: 0,
|
|
171
|
+
textTruncations: 0,
|
|
172
|
+
maxEventPanels,
|
|
173
|
+
maxPanelContentBytes,
|
|
174
|
+
truncationHint,
|
|
175
|
+
bytesBefore,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/** 原 size 闭包:当前卡片序列化字节数。 */
|
|
179
|
+
function byteBudgetSize(ctx) {
|
|
180
|
+
return Buffer.byteLength(JSON.stringify(ctx.truncated), 'utf8');
|
|
181
|
+
}
|
|
182
|
+
/** 原 done 闭包:构造结果对象(reason 由各阶段累积)。 */
|
|
183
|
+
function byteBudgetDone(ctx, wasTruncated) {
|
|
184
|
+
return {
|
|
185
|
+
card: ctx.truncated,
|
|
188
186
|
wasTruncated,
|
|
189
|
-
reason: reasons.join('+') || undefined,
|
|
190
|
-
bytesBefore: bytesBefore ??
|
|
191
|
-
bytesAfter:
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
187
|
+
reason: ctx.reasons.join('+') || undefined,
|
|
188
|
+
bytesBefore: ctx.bytesBefore ?? byteBudgetSize(ctx),
|
|
189
|
+
bytesAfter: byteBudgetSize(ctx),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** 原 elementsWithinBudgetWithHint 闭包(含 hint 预留位)。 */
|
|
193
|
+
function byteBudgetElementsWithin(ctx) {
|
|
194
|
+
return countCardElements(ctx.truncated) + (ctx.omitted > 0 ? 1 : 0) <= MAX_CARD_ELEMENTS;
|
|
195
|
+
}
|
|
196
|
+
/** 原 `size() <= ... && elementsWithinBudgetWithHint()` 达标判定。 */
|
|
197
|
+
function byteBudgetFits(ctx) {
|
|
198
|
+
return byteBudgetSize(ctx) <= CARD_BUDGET_BYTES && byteBudgetElementsWithin(ctx);
|
|
199
|
+
}
|
|
200
|
+
function comparePanelIndexDesc(a, b) {
|
|
201
|
+
return b - a;
|
|
202
|
+
}
|
|
203
|
+
/** 原 dropOldestPanels 闭包:按文档顺序删最旧的 count 个面板。 */
|
|
204
|
+
function dropOldestPanels(ctx, count) {
|
|
205
|
+
const indices = getPanelIndices(ctx.elements).slice(0, count).sort(comparePanelIndexDesc);
|
|
206
|
+
for (const idx of indices) {
|
|
207
|
+
ctx.elements.splice(idx, 1);
|
|
208
|
+
ctx.omitted++;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** 达标则插入 hint 并返回结果;未达标返回 undefined,交给下一阶段继续降级。 */
|
|
212
|
+
function byteBudgetTryFinish(ctx, wasTruncated) {
|
|
213
|
+
if (!byteBudgetFits(ctx))
|
|
214
|
+
return undefined;
|
|
215
|
+
insertOmittedHint(ctx.elements, ctx.omitted, ctx.truncationHint);
|
|
216
|
+
return byteBudgetDone(ctx, wasTruncated);
|
|
217
|
+
}
|
|
218
|
+
// ========== 阶段1:裁剪所有面板的内容 ==========
|
|
219
|
+
function truncatePanelContents(ctx) {
|
|
220
|
+
for (const el of ctx.elements) {
|
|
196
221
|
if (el.tag !== 'collapsible_panel')
|
|
197
222
|
continue;
|
|
198
223
|
for (const inner of el.elements ?? []) {
|
|
199
224
|
if (inner.tag === 'div' && inner.text?.content) {
|
|
200
225
|
const content = inner.text.content;
|
|
201
|
-
if (Buffer.byteLength(content, 'utf8') > maxPanelContentBytes) {
|
|
202
|
-
inner.text.content = truncateUtf8(content, maxPanelContentBytes);
|
|
203
|
-
modifications++;
|
|
226
|
+
if (Buffer.byteLength(content, 'utf8') > ctx.maxPanelContentBytes) {
|
|
227
|
+
inner.text.content = truncateUtf8(content, ctx.maxPanelContentBytes);
|
|
228
|
+
ctx.modifications++;
|
|
204
229
|
}
|
|
205
230
|
}
|
|
206
231
|
}
|
|
207
232
|
}
|
|
208
|
-
if (modifications > 0) {
|
|
209
|
-
reasons.push('panel_content_truncated');
|
|
210
|
-
}
|
|
211
|
-
// ========== 阶段2:限制面板数量(删除最老) ==========
|
|
212
|
-
let omitted = 0;
|
|
213
|
-
const dropOldestPanels = (count) => {
|
|
214
|
-
const indices = getPanelIndices(elements)
|
|
215
|
-
.slice(0, count)
|
|
216
|
-
.sort((a, b) => b - a);
|
|
217
|
-
for (const idx of indices) {
|
|
218
|
-
elements.splice(idx, 1);
|
|
219
|
-
omitted++;
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
const panelCount = getPanelIndices(elements).length;
|
|
223
|
-
if (panelCount > maxEventPanels) {
|
|
224
|
-
dropOldestPanels(panelCount - maxEventPanels);
|
|
225
|
-
reasons.push('event_count_limited');
|
|
226
|
-
}
|
|
227
|
-
// 元素数预算必须与字节预算同查(含 hint 预留位):字节路径的返回条件此前
|
|
228
|
-
// 只看字节——元素超限但卡内无 collapsible_panel 时(如 95 个小 div),
|
|
229
|
-
// 阶段1-4 全是空操作,超限卡原样直通(实测 wasTruncated:false、95 元素
|
|
230
|
-
// > 90 → 飞书 11310 整卡报废,正是文件头注释记载的 /ws、/order 故障形态)。
|
|
231
|
-
const elementsWithinBudgetWithHint = () => countCardElements(truncated) + (omitted > 0 ? 1 : 0) <= MAX_CARD_ELEMENTS;
|
|
232
|
-
if (size() <= CARD_BUDGET_BYTES && elementsWithinBudgetWithHint()) {
|
|
233
|
-
insertOmittedHint(elements, omitted, truncationHint);
|
|
234
|
-
return done(modifications > 0 || omitted > 0);
|
|
233
|
+
if (ctx.modifications > 0) {
|
|
234
|
+
ctx.reasons.push('panel_content_truncated');
|
|
235
235
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (
|
|
241
|
-
|
|
236
|
+
}
|
|
237
|
+
// ========== 阶段2:限制面板数量(删除最老) ==========
|
|
238
|
+
function limitPanelCount(ctx) {
|
|
239
|
+
const panelCount = getPanelIndices(ctx.elements).length;
|
|
240
|
+
if (panelCount > ctx.maxEventPanels) {
|
|
241
|
+
dropOldestPanels(ctx, panelCount - ctx.maxEventPanels);
|
|
242
|
+
ctx.reasons.push('event_count_limited');
|
|
242
243
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
}
|
|
245
|
+
// ========== 阶段3:丢弃全部面板,保留卡片骨架 ==========
|
|
246
|
+
// header(cwd/sessionId/标题)、usage、操作按钮等信息量高、体积小的元素
|
|
247
|
+
// 必须保留——整卡替换为一句话是信息损失最大的降级方式。
|
|
248
|
+
function dropAllPanels(ctx) {
|
|
249
|
+
dropOldestPanels(ctx, getPanelIndices(ctx.elements).length);
|
|
250
|
+
if (ctx.omitted > 0) {
|
|
251
|
+
ctx.reasons.push('panels_dropped');
|
|
246
252
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
253
|
+
}
|
|
254
|
+
// ========== 阶段4:截断剩余顶层 div 文本 ==========
|
|
255
|
+
// 骨架元素(header/usage 等)自身超限的情况(如超大 displayTitle)。
|
|
256
|
+
function truncateTopLevelDivs(ctx) {
|
|
257
|
+
for (const el of ctx.elements) {
|
|
251
258
|
if (el.tag === 'div' && el.text?.content) {
|
|
252
259
|
const content = el.text.content;
|
|
253
|
-
if (Buffer.byteLength(content, 'utf8') > maxPanelContentBytes) {
|
|
254
|
-
el.text.content = truncateUtf8(content, maxPanelContentBytes);
|
|
255
|
-
textTruncations++;
|
|
260
|
+
if (Buffer.byteLength(content, 'utf8') > ctx.maxPanelContentBytes) {
|
|
261
|
+
el.text.content = truncateUtf8(content, ctx.maxPanelContentBytes);
|
|
262
|
+
ctx.textTruncations++;
|
|
256
263
|
}
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
|
-
if (textTruncations > 0) {
|
|
260
|
-
reasons.push('text_truncated');
|
|
266
|
+
if (ctx.textTruncations > 0) {
|
|
267
|
+
ctx.reasons.push('text_truncated');
|
|
261
268
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
elements.shift();
|
|
273
|
-
omitted++;
|
|
274
|
-
}
|
|
275
|
-
reasons.push('element_budget_elements_dropped');
|
|
276
|
-
if (size() <= CARD_BUDGET_BYTES && elementsWithinBudgetWithHint()) {
|
|
277
|
-
insertOmittedHint(elements, omitted, truncationHint);
|
|
278
|
-
return done(true);
|
|
269
|
+
}
|
|
270
|
+
// ========== 阶段4.5:元素数兜底(无面板可删的元素超限) ==========
|
|
271
|
+
// 阶段1-4 只会裁内容/删面板;元素超限且无面板时在此按序丢弃最旧的顶层
|
|
272
|
+
// 元素直到达标(与 table/panel 的「删最旧、留最新」口径一致,尾部操作
|
|
273
|
+
// 按钮保留)。留 ≥3 个元素兜底,仍不达标则交给阶段5 的最小卡。
|
|
274
|
+
function dropExcessElements(ctx) {
|
|
275
|
+
if (countCardElements(ctx.truncated) > MAX_CARD_ELEMENTS) {
|
|
276
|
+
while (countCardElements(ctx.truncated) > MAX_CARD_ELEMENTS && ctx.elements.length > 3) {
|
|
277
|
+
ctx.elements.shift();
|
|
278
|
+
ctx.omitted++;
|
|
279
279
|
}
|
|
280
|
+
ctx.reasons.push('element_budget_elements_dropped');
|
|
281
|
+
return byteBudgetTryFinish(ctx, true);
|
|
280
282
|
}
|
|
281
|
-
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
// ========== 阶段5:最终兜底(原 extreme_fallback) ==========
|
|
286
|
+
function byteBudgetMinimalCard(ctx) {
|
|
282
287
|
const minimalCard = cardWithHeader({
|
|
283
288
|
template: 'orange',
|
|
284
289
|
title: '⚠️ 内容已截断',
|
|
@@ -296,10 +301,30 @@ function enforceByteBudget(truncated, opts = {}, bytesBefore) {
|
|
|
296
301
|
card: minimalCard,
|
|
297
302
|
wasTruncated: true,
|
|
298
303
|
reason: 'extreme_fallback',
|
|
299
|
-
bytesBefore: bytesBefore ??
|
|
304
|
+
bytesBefore: ctx.bytesBefore ?? byteBudgetSize(ctx),
|
|
300
305
|
bytesAfter: Buffer.byteLength(JSON.stringify(minimalCard), 'utf8'),
|
|
301
306
|
};
|
|
302
307
|
}
|
|
308
|
+
function enforceByteBudget(truncated, opts = {}, bytesBefore) {
|
|
309
|
+
const ctx = makeByteBudgetContext(truncated, opts, bytesBefore);
|
|
310
|
+
truncatePanelContents(ctx);
|
|
311
|
+
limitPanelCount(ctx);
|
|
312
|
+
let result = byteBudgetTryFinish(ctx, ctx.modifications > 0 || ctx.omitted > 0);
|
|
313
|
+
if (result)
|
|
314
|
+
return result;
|
|
315
|
+
dropAllPanels(ctx);
|
|
316
|
+
result = byteBudgetTryFinish(ctx, true);
|
|
317
|
+
if (result)
|
|
318
|
+
return result;
|
|
319
|
+
truncateTopLevelDivs(ctx);
|
|
320
|
+
result = byteBudgetTryFinish(ctx, true);
|
|
321
|
+
if (result)
|
|
322
|
+
return result;
|
|
323
|
+
result = dropExcessElements(ctx);
|
|
324
|
+
if (result)
|
|
325
|
+
return result;
|
|
326
|
+
return byteBudgetMinimalCard(ctx);
|
|
327
|
+
}
|
|
303
328
|
/**
|
|
304
329
|
* 在卡片头部插入「N 个事件未显示」提示(仅在有面板被移除时)。
|
|
305
330
|
*/
|
|
@@ -27,7 +27,7 @@ export declare function parseJumpOffset(inputValue: unknown, pageSize: number):
|
|
|
27
27
|
* Build the CardKit 2.0 pagination block shared by /ls, /ws, /resume, /active,
|
|
28
28
|
* /order.
|
|
29
29
|
*
|
|
30
|
-
*
|
|
30
|
+
* 第一性原理(窄屏):分页栏要同时承载「页码信息」和「翻页
|
|
31
31
|
* 控件」两类需求。旧布局把两者塞进同一行 `column_set`(文案 weighted 4 : 输入
|
|
32
32
|
* weighted 2),手机窄屏下 auto 按钮先占走固有宽度,剩余宽度按 4:2 切分后文案
|
|
33
33
|
* 列只有百来像素、输入框只有几十像素——文案折行、输入框窄到没法点。
|
|
@@ -104,8 +104,8 @@ export declare function searchBar(opts: {
|
|
|
104
104
|
*
|
|
105
105
|
* 旧口径是 `events.slice(-max)`:按**原始事件条数**截尾。工具往返(bash 调用 →
|
|
106
106
|
* job 登记 → 结果)一段就能把窗口占满,于是重返会话的用户真正需要的两条信息
|
|
107
|
-
*
|
|
108
|
-
*
|
|
107
|
+
* ——最后一句我说了什么、最后一句它答了什么——被挤出窗口:自动恢复卡 5 个
|
|
108
|
+
* 面板全是 bash 调用,一条用户消息、一条 assistant 正文都没有。
|
|
109
109
|
*
|
|
110
110
|
* 契约:先取最新 `max` 条,再把最后一条 `user` 与最后一条 `assistant`(正文)
|
|
111
111
|
* 不在窗口内时补进来——补位时淘汰最早的**非锚点**事件。输出保持时间顺序,
|