lark-remote 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/bridge/index.d.ts +15 -0
- package/dist/bridge/index.js +61 -0
- package/dist/bridge/queue-manager.js +41 -58
- package/dist/card/approval-render.js +2 -1
- package/dist/card/bash-card-session.d.ts +0 -1
- package/dist/card/bash-card-session.js +0 -8
- package/dist/card/bash-renderer.js +2 -1
- package/dist/card/card-budget.js +15 -19
- package/dist/card/card-session.d.ts +3 -13
- package/dist/card/card-session.js +4 -18
- package/dist/card/card-shared.d.ts +11 -0
- package/dist/card/card-shared.js +19 -13
- package/dist/card/run-card-session.d.ts +0 -1
- package/dist/card/run-card-session.js +0 -6
- package/dist/card/run-renderer.d.ts +3 -9
- package/dist/card/run-renderer.js +99 -27
- package/dist/card/text-truncate.d.ts +3 -5
- package/dist/card/text-truncate.js +3 -5
- package/dist/card/tool-render.js +7 -21
- package/dist/common/tool-summary.d.ts +8 -0
- package/dist/common/tool-summary.js +38 -20
- package/dist/common/turn-failure.d.ts +13 -0
- package/dist/common/turn-failure.js +18 -0
- package/dist/index.js +5 -7
- package/dist/order/index.d.ts +2 -0
- package/dist/order/index.js +2 -0
- package/dist/router/card-helpers.js +23 -42
- package/dist/router/config/codex.js +2 -3
- package/dist/router/config/common/model-patch.d.ts +8 -0
- package/dist/router/config/common/model-patch.js +13 -0
- package/dist/router/config/common/render.js +30 -77
- package/dist/router/config/dsh.js +2 -1
- package/dist/router/config/kimi.js +3 -3
- package/dist/router/index.d.ts +0 -11
- package/dist/router/index.js +108 -349
- package/dist/router/order-exec-dispatch.js +2 -1
- package/dist/runner/common/defaults.d.ts +2 -0
- package/dist/runner/common/defaults.js +3 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +1 -1
- package/dist/runner/dsh/client.js +1 -2
- package/dist/runner/types.d.ts +19 -0
- package/dist/runner/types.js +9 -0
- package/dist/session/claude/session-index.d.ts +0 -4
- package/dist/session/claude/session-index.js +0 -8
- package/dist/session/dsh/sessions.js +57 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.8] - 2026-10-21
|
|
9
|
+
|
|
10
|
+
### 修复
|
|
11
|
+
|
|
12
|
+
- **DSH 的 `/resume` 列表卡看得出会话内容**:此前 DSH 会话的列表卡只有 session id 和时间,必须逐条点进详情才知道哪一条是要找的会话。DSH 0.2.x 的列表响应里已带着会话标题与每轮的输入/输出,现在把它们映射成「标题 / 最近输入 / 最后输出」直接渲染,行上有预览就不再逐条去拉分页详情(那是上万条事件的大响应);字段缺失或标题只是占位符时回退原有行为。详情卡头部同样优先用服务端的末轮输入作「最近输入」,并在它与会话标题不同时分成两段展示,不再把用户自己说的话挂在「AI 标题」下面
|
|
13
|
+
- **后台任务卡里的命令不再被截断**:活过本轮的后台任务(终端里起的后台进程)原先按固定字数截断,用户没法核对卡上那条命令到底是要停哪个。现在短命令内联展示、中等长度用代码块整段展示、只有确实很长的脚本或链式命令才收进折叠面板,且面板里保留完整原文——命令是你都要核对或复制的对象,任何情况下不再 truncate
|
|
14
|
+
- **停止按钮不再被挤成「🟥…」**:命令与停止按钮原先并排同一行(命令列 `grow`、按钮列 `auto`),长命令的最小内容宽度会把按钮压成一个色块。现在命令独占一行,停止按钮在下一行独占一列
|
|
15
|
+
- **运行卡片收尾后保留状态行**:终态原先会把「⏱ 耗时 · 🧰 步数 · 📊 上下文」整行去掉,回看卡片时看不出这一跑花了多久、做了多少步。现在保留:耗时定格,步数与上下文显示该回合最后一次投影的快照
|
|
16
|
+
|
|
17
|
+
### 变更
|
|
18
|
+
|
|
19
|
+
- **模型输出被服务方内容审核截断时不再算「运行出错」,并会自动续跑一次**:上游把 `finish_reason: content_filter` 归一成普通错误,它既不在默认的重试判据里(空响应/限流/服务错误/超时/传输),也不会触发换模型,于是一次多为误报的判定就把整个回合判死——跑了很久的回合在最后一步被截断,已写入磁盘的改动都还在,卡片上却只有一句英文内部报文。现在错误卡说明三件事:谁拦的、改动还在、下一步可做什么(重试仍被拦截时换模型或稍后再试);桥接层同时在同一会话补一条「从上次中断的位置继续,不要重复已完成的步骤」,只补一次,且若运行期间会话指针已被 `/new`、`/resume` 移动过就跳过,避免把旧会话接回来
|
|
20
|
+
- **DSH 在卡片上的显示名统一为 `DSH`**:各 agent 的显示名收敛到同一张表,启动注册与没有注册表时的回退路径共用同一份数据,不再出现同一 agent 在不同位置叫法不同
|
|
21
|
+
- **重复实现单源化,无行为变化**:档位取中位、工具摘要、卡片头部/按钮/字段行、队列管理与配置卡渲染等此前写了两遍的实现合并为单一来源;测试专用生产钩子移除;同主题测试套件合并、共享夹具抽出(用例覆盖不减)
|
|
22
|
+
- **文档里的测试锚点改指存活文件**:套件合并删掉了若干测试文件,贡献指南与架构设计文档按路径点名的锚点因此指向不存在的路径,已改指承接其覆盖的文件。无行为变化
|
|
23
|
+
|
|
8
24
|
## [0.5.7] - 2026-10-20
|
|
9
25
|
|
|
10
26
|
### 新增
|
package/dist/bridge/index.d.ts
CHANGED
|
@@ -416,7 +416,22 @@ export declare class Bridge {
|
|
|
416
416
|
forwardToClaude(message: string, ctx: CommandContext, opts?: {
|
|
417
417
|
cwdOverride?: string;
|
|
418
418
|
binding?: AgentBinding;
|
|
419
|
+
autoRetry?: boolean;
|
|
419
420
|
}): Promise<void>;
|
|
421
|
+
/**
|
|
422
|
+
* 内容审核截断后的单次自动续跑。
|
|
423
|
+
*
|
|
424
|
+
* DSH 把上游 `finish_reason: content_filter` 归一成 PI_AI_ERROR:既不在默认
|
|
425
|
+
* 重试码里(EMPTY_RESPONSE/RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT),也不会换模型,
|
|
426
|
+
* 于是一次(多数是误报的)判定就把整个 turn 判死。2026-10-21 现场:60 步、
|
|
427
|
+
* 10 分钟的 turn 在最后一步被截断,已写入的文件还在,用户只看到「运行出错」
|
|
428
|
+
* 加一句英文报文。
|
|
429
|
+
*
|
|
430
|
+
* 桥侧不改 dsh 的重试策略,改用「同一 session 补一条继续提示」:审核是概率
|
|
431
|
+
* 判定,重采样通常能过;模型从历史里接着做,不必重跑已完成的工作。
|
|
432
|
+
* 只续一次——续跑仍被截断就停在错误卡,不进入无限重试。
|
|
433
|
+
*/
|
|
434
|
+
private retryAfterContentFilter;
|
|
420
435
|
/**
|
|
421
436
|
* Step 1 of forwardToClaude: Create run session components.
|
|
422
437
|
* Returns runId, cardSession, and activeRun object for tracking.
|
package/dist/bridge/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { sleep } from '../common/sleep.js';
|
|
3
|
+
import { isContentFilterFailure } from '../common/turn-failure.js';
|
|
3
4
|
import { resolveFinalUsage, errorMessage } from './final-usage.js';
|
|
4
5
|
import { getLogger } from '../logger/index.js';
|
|
5
6
|
import { RunCardSession } from '../card/run-card-session.js';
|
|
@@ -919,6 +920,66 @@ export class Bridge {
|
|
|
919
920
|
// Step 3: Finalize the run (error handling, fallback card, completion notification, cleanup)
|
|
920
921
|
await this.finalizeRun(cardSession, activeRun, ctx, cwd);
|
|
921
922
|
}
|
|
923
|
+
// Step 4: 服务方内容审核截断(`finish_reason: content_filter`)后自动续跑一次。
|
|
924
|
+
// 放在 finalize 之后:activeRuns 槽位已释放,续跑能正常开新卡片。
|
|
925
|
+
await this.retryAfterContentFilter({
|
|
926
|
+
cardSession,
|
|
927
|
+
ctx,
|
|
928
|
+
cwd,
|
|
929
|
+
agentKind,
|
|
930
|
+
sessionId,
|
|
931
|
+
sessionEpochAtStart,
|
|
932
|
+
autoRetried: opts?.autoRetry === true,
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* 内容审核截断后的单次自动续跑。
|
|
937
|
+
*
|
|
938
|
+
* DSH 把上游 `finish_reason: content_filter` 归一成 PI_AI_ERROR:既不在默认
|
|
939
|
+
* 重试码里(EMPTY_RESPONSE/RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT),也不会换模型,
|
|
940
|
+
* 于是一次(多数是误报的)判定就把整个 turn 判死。2026-10-21 现场:60 步、
|
|
941
|
+
* 10 分钟的 turn 在最后一步被截断,已写入的文件还在,用户只看到「运行出错」
|
|
942
|
+
* 加一句英文报文。
|
|
943
|
+
*
|
|
944
|
+
* 桥侧不改 dsh 的重试策略,改用「同一 session 补一条继续提示」:审核是概率
|
|
945
|
+
* 判定,重采样通常能过;模型从历史里接着做,不必重跑已完成的工作。
|
|
946
|
+
* 只续一次——续跑仍被截断就停在错误卡,不进入无限重试。
|
|
947
|
+
*/
|
|
948
|
+
async retryAfterContentFilter(params) {
|
|
949
|
+
const state = params.cardSession.currentState;
|
|
950
|
+
if (state.terminal !== 'error' || !isContentFilterFailure(state.errorMsg))
|
|
951
|
+
return;
|
|
952
|
+
if (params.autoRetried) {
|
|
953
|
+
getLogger().info(`[lark-remote] content_filter auto-retry already used runId=${state.runId} cwd=${params.cwd}`);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
// 会话指针在 run 期间被 /new、/resume 等移动过:续跑会接到另一个会话上,
|
|
957
|
+
// 「从断点继续」的语义不再成立,宁可不做(错误卡已经把话说清楚)。
|
|
958
|
+
const epochNow = this.sessionStore.getSessionEpoch(params.ctx.userId, params.agentKind);
|
|
959
|
+
if (epochNow !== params.sessionEpochAtStart) {
|
|
960
|
+
getLogger().info(`[lark-remote] content_filter auto-retry skipped: session epoch moved runId=${state.runId}`);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
getLogger().warn(`[lark-remote] content_filter detected, auto-retrying once runId=${state.runId} cwd=${params.cwd}`);
|
|
964
|
+
await this.sendResult({
|
|
965
|
+
text: '🔁 上一回合被服务方内容审核截断(content_filter,通常是误报),已自动重试一次。',
|
|
966
|
+
}, params.ctx);
|
|
967
|
+
try {
|
|
968
|
+
await this.forwardToClaude('(自动继续)上一回合因服务方内容审核被截断(content_filter,通常是误报)。' +
|
|
969
|
+
'请从上次中断的位置继续,不要重复已经完成的步骤。', params.ctx, {
|
|
970
|
+
cwdOverride: params.cwd,
|
|
971
|
+
binding: {
|
|
972
|
+
agent: params.agentKind,
|
|
973
|
+
sessionId: params.sessionId,
|
|
974
|
+
sessionEpoch: params.sessionEpochAtStart,
|
|
975
|
+
},
|
|
976
|
+
autoRetry: true,
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
catch (err) {
|
|
980
|
+
// 续跑自身失败不应冒泡成未捕获异常:用户已经收到错误卡与重试通知。
|
|
981
|
+
getLogger().error(`[lark-remote] content_filter auto-retry failed runId=${state.runId}`, err);
|
|
982
|
+
}
|
|
922
983
|
}
|
|
923
984
|
/**
|
|
924
985
|
* Step 1 of forwardToClaude: Create run session components.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getLogger } from '../logger/index.js';
|
|
2
|
+
import { cardWithHeader } from '../card/card-shared.js';
|
|
2
3
|
import { displayName } from '../platform/path.js';
|
|
3
4
|
/**
|
|
4
5
|
* Per-workspace cap on the number of tasks WAITING to execute.
|
|
@@ -171,25 +172,19 @@ export class QueueManager {
|
|
|
171
172
|
// a rejected task never executes, so it must not occupy a slot.
|
|
172
173
|
if (currentQueueLength >= MAX_WAITING_QUEUE) {
|
|
173
174
|
getLogger().warn(`[queue-manager] queue full cwd=${cwd} depth=${currentQueueLength} rejecting task messageId=${taskMeta.messageId}`);
|
|
174
|
-
void this.sendCard(taskMeta.chatId, {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
{
|
|
184
|
-
tag: 'div',
|
|
185
|
-
text: {
|
|
186
|
-
tag: 'lark_md',
|
|
187
|
-
content: `当前工作目录已有 ${MAX_WAITING_QUEUE} 条消息排队等待,为防止积压已拒收本条消息。请等待队列消化后重发,或用 \`/stop\` 清空当前任务。`,
|
|
188
|
-
},
|
|
175
|
+
void this.sendCard(taskMeta.chatId, cardWithHeader({
|
|
176
|
+
template: 'red',
|
|
177
|
+
title: '🚫 队列已满',
|
|
178
|
+
elements: [
|
|
179
|
+
{
|
|
180
|
+
tag: 'div',
|
|
181
|
+
text: {
|
|
182
|
+
tag: 'lark_md',
|
|
183
|
+
content: `当前工作目录已有 ${MAX_WAITING_QUEUE} 条消息排队等待,为防止积压已拒收本条消息。请等待队列消化后重发,或用 \`/stop\` 清空当前任务。`,
|
|
189
184
|
},
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}, { replyTo: taskMeta.feishuReplyTo ?? taskMeta.messageId }).catch((err) => {
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
}), { replyTo: taskMeta.feishuReplyTo ?? taskMeta.messageId }).catch((err) => {
|
|
193
188
|
getLogger().error('[queue-manager] failed to send queue-full rejection card:', err);
|
|
194
189
|
});
|
|
195
190
|
return;
|
|
@@ -559,26 +554,20 @@ export class QueueManager {
|
|
|
559
554
|
return;
|
|
560
555
|
}
|
|
561
556
|
const previewForCard = liveTask?.messagePreview ?? messagePreview;
|
|
562
|
-
const card = {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
{ tag: 'div', text: { tag: 'lark_md', content: `📝 \`${previewForCard}\`` } },
|
|
577
|
-
{ tag: 'hr' },
|
|
578
|
-
...this.buildQueueActionButtons(cwd, messageId, true),
|
|
579
|
-
],
|
|
580
|
-
},
|
|
581
|
-
};
|
|
557
|
+
const card = cardWithHeader({
|
|
558
|
+
template: 'green',
|
|
559
|
+
title: '▶️ 已开始执行',
|
|
560
|
+
elements: [
|
|
561
|
+
{
|
|
562
|
+
tag: 'div',
|
|
563
|
+
text: { tag: 'lark_md', content: `**当前 Workspace:** \`${workspaceName}\`` },
|
|
564
|
+
},
|
|
565
|
+
{ tag: 'hr' },
|
|
566
|
+
{ tag: 'div', text: { tag: 'lark_md', content: `📝 \`${previewForCard}\`` } },
|
|
567
|
+
{ tag: 'hr' },
|
|
568
|
+
...this.buildQueueActionButtons(cwd, messageId, true),
|
|
569
|
+
],
|
|
570
|
+
});
|
|
582
571
|
await this.updateCard(cardMessageId, card);
|
|
583
572
|
getLogger().debug(`[queue-manager] queue card updated to executing messageId=${messageId}`);
|
|
584
573
|
}
|
|
@@ -622,26 +611,20 @@ export class QueueManager {
|
|
|
622
611
|
return;
|
|
623
612
|
}
|
|
624
613
|
const workspaceName = displayName(cwd);
|
|
625
|
-
const card = {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
{ tag: 'div', text: { tag: 'lark_md', content: `📝 该消息已从队列中撤销` } },
|
|
640
|
-
{ tag: 'hr' },
|
|
641
|
-
...this.buildQueueActionButtons(cwd, messageId, true),
|
|
642
|
-
],
|
|
643
|
-
},
|
|
644
|
-
};
|
|
614
|
+
const card = cardWithHeader({
|
|
615
|
+
template: 'gray',
|
|
616
|
+
title: '❌ 已撤销',
|
|
617
|
+
elements: [
|
|
618
|
+
{
|
|
619
|
+
tag: 'div',
|
|
620
|
+
text: { tag: 'lark_md', content: `**当前 Workspace:** \`${workspaceName}\`` },
|
|
621
|
+
},
|
|
622
|
+
{ tag: 'hr' },
|
|
623
|
+
{ tag: 'div', text: { tag: 'lark_md', content: `📝 该消息已从队列中撤销` } },
|
|
624
|
+
{ tag: 'hr' },
|
|
625
|
+
...this.buildQueueActionButtons(cwd, messageId, true),
|
|
626
|
+
],
|
|
627
|
+
});
|
|
645
628
|
try {
|
|
646
629
|
await this.updateCard(cardMessageId, card);
|
|
647
630
|
getLogger().debug(`[queue-manager] queue card updated to cancelled, messageId=${messageId}`);
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Uses CardKit 2.0 (no tag:"action" container — 200861 rule).
|
|
6
6
|
*/
|
|
7
7
|
import { collapsibleMarkdownPanel } from './collapsible.js';
|
|
8
|
-
import {
|
|
8
|
+
import { truncateMarkdownTables } from './text-truncate.js';
|
|
9
|
+
import { truncateUtf8 } from '../common/truncate.js';
|
|
9
10
|
import { cardButton } from './card-shared.js';
|
|
10
11
|
/** Unique nonce for card button callbacks (SDK dedup + router validation). */
|
|
11
12
|
function mkNonce() {
|
|
@@ -38,7 +38,6 @@ export declare class BashCardSession extends CardSession<BashState, BashRenderOp
|
|
|
38
38
|
protected get logPrefix(): string;
|
|
39
39
|
protected get errorPrefix(): string;
|
|
40
40
|
protected renderCard(state: BashState, options: BashRenderOptions): object;
|
|
41
|
-
protected hasOwnBudgetProtection(): boolean;
|
|
42
41
|
protected isFlushableState(): boolean;
|
|
43
42
|
/** Patch the card with updated bash state (e.g. new stdout/stderr). */
|
|
44
43
|
update(patch: Partial<BashState>): Promise<void>;
|
|
@@ -53,14 +53,6 @@ export class BashCardSession extends CardSession {
|
|
|
53
53
|
renderCard(state, options) {
|
|
54
54
|
return renderBashCard(state, options);
|
|
55
55
|
}
|
|
56
|
-
hasOwnBudgetProtection() {
|
|
57
|
-
// renderBashCard 自带 stringify 级 degraded/extreme fallback 预算保护
|
|
58
|
-
// (与 renderRunCard 对称),保证返回卡 ≤28KB 且保留 command/exitCode。
|
|
59
|
-
// enforceCardBudget 是会话事件面板专用的静态裁剪层,对 bash 卡的普通
|
|
60
|
-
// markdownDiv output 是 misfit(超限时极端降级丢 command/exitCode),bash
|
|
61
|
-
// 卡应跳过它,靠自身 renderer 保护。
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
56
|
isFlushableState() {
|
|
65
57
|
// finish() 已把 state 转终态后,in-flight/延迟 flush 不再用旧 state patch。
|
|
66
58
|
return this.state.terminal === 'running';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { terminalToColor, terminalToLabel, stopButton } from './card-shared.js';
|
|
2
2
|
import { markdownDiv } from './collapsible.js';
|
|
3
|
-
import {
|
|
3
|
+
import { CARD_BUDGET_BYTES } from './text-truncate.js';
|
|
4
|
+
import { truncateUtf8 } from '../common/truncate.js';
|
|
4
5
|
/**
|
|
5
6
|
* 飞书卡片大小限制(安全阈值,与 run-renderer 一致)。
|
|
6
7
|
* 从共享的 text-truncate.ts 导入,避免三处各自定义后数值漂移。
|
package/dist/card/card-budget.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dropOldestMarkdownTables, countMarkdownTables, FEISHU_MAX_TABLES, CARD_BUDGET_BYTES, } from './text-truncate.js';
|
|
2
|
+
import { truncateUtf8 } from '../common/truncate.js';
|
|
3
|
+
import { cardWithHeader } from './card-shared.js';
|
|
2
4
|
/**
|
|
3
5
|
* 静态卡片体积保护
|
|
4
6
|
*
|
|
@@ -257,25 +259,19 @@ function enforceByteBudget(truncated, opts = {}, bytesBefore) {
|
|
|
257
259
|
return done(true);
|
|
258
260
|
}
|
|
259
261
|
// ========== 阶段5:最终兜底(原 extreme_fallback) ==========
|
|
260
|
-
const minimalCard = {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
{
|
|
270
|
-
tag: 'div',
|
|
271
|
-
text: {
|
|
272
|
-
tag: 'lark_md',
|
|
273
|
-
content: '会话内容过大,已自动截断。\n\n请使用 `/active` 查看完整列表。',
|
|
274
|
-
},
|
|
262
|
+
const minimalCard = cardWithHeader({
|
|
263
|
+
template: 'orange',
|
|
264
|
+
title: '⚠️ 内容已截断',
|
|
265
|
+
elements: [
|
|
266
|
+
{
|
|
267
|
+
tag: 'div',
|
|
268
|
+
text: {
|
|
269
|
+
tag: 'lark_md',
|
|
270
|
+
content: '会话内容过大,已自动截断。\n\n请使用 `/active` 查看完整列表。',
|
|
275
271
|
},
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
};
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
});
|
|
279
275
|
return {
|
|
280
276
|
card: minimalCard,
|
|
281
277
|
wasTruncated: true,
|
|
@@ -107,19 +107,9 @@ export declare abstract class CardSession<S, RO = unknown> {
|
|
|
107
107
|
start(): Promise<void>;
|
|
108
108
|
settle(): Promise<StreamSettleResult>;
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* extreme fallback) should override this to return true.
|
|
114
|
-
*/
|
|
115
|
-
protected hasOwnBudgetProtection(): boolean;
|
|
116
|
-
/**
|
|
117
|
-
* Render the current card and apply stringify-level budget enforcement unless
|
|
118
|
-
* the subclass renderer has its own budget protection. Centralizes the budget
|
|
119
|
-
* decision so start()/updateCard()/settle() stay symmetric — previously only
|
|
120
|
-
* updateCard() guarded, while start() producer and settle() fallback sent bare
|
|
121
|
-
* renderCard output (risking >28KB for BashCardSession, which has no own
|
|
122
|
-
* stringify-level safety net).
|
|
110
|
+
* 渲染当前卡片。所有生产子类的 renderer 都自带 stringify 级预算保护
|
|
111
|
+
* (run/bash 的 degraded/extreme 路径);发送前 Bridge 还会再走一次
|
|
112
|
+
* enforceCardBudget,因此这里不再重复裁剪。
|
|
123
113
|
*/
|
|
124
114
|
protected safeRenderCard(): object;
|
|
125
115
|
/**
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getLogger } from '../logger/index.js';
|
|
2
|
-
import { enforceCardBudget } from './card-budget.js';
|
|
3
2
|
/**
|
|
4
3
|
* Base class for single-card streaming sessions (RunCardSession, BashCardSession).
|
|
5
4
|
* Provides the shared lifecycle: start → (push/update) → finish → settle.
|
|
@@ -216,25 +215,12 @@ export class CardSession {
|
|
|
216
215
|
}
|
|
217
216
|
}
|
|
218
217
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* extreme fallback) should override this to return true.
|
|
223
|
-
*/
|
|
224
|
-
hasOwnBudgetProtection() {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Render the current card and apply stringify-level budget enforcement unless
|
|
229
|
-
* the subclass renderer has its own budget protection. Centralizes the budget
|
|
230
|
-
* decision so start()/updateCard()/settle() stay symmetric — previously only
|
|
231
|
-
* updateCard() guarded, while start() producer and settle() fallback sent bare
|
|
232
|
-
* renderCard output (risking >28KB for BashCardSession, which has no own
|
|
233
|
-
* stringify-level safety net).
|
|
218
|
+
* 渲染当前卡片。所有生产子类的 renderer 都自带 stringify 级预算保护
|
|
219
|
+
* (run/bash 的 degraded/extreme 路径);发送前 Bridge 还会再走一次
|
|
220
|
+
* enforceCardBudget,因此这里不再重复裁剪。
|
|
234
221
|
*/
|
|
235
222
|
safeRenderCard() {
|
|
236
|
-
|
|
237
|
-
return this.hasOwnBudgetProtection() ? card : enforceCardBudget(card).card;
|
|
223
|
+
return this.renderCard(this.state, this.renderOptions);
|
|
238
224
|
}
|
|
239
225
|
/**
|
|
240
226
|
* Push an update to the card. Tries controller.update first, falls back to
|
|
@@ -9,6 +9,15 @@ import { type AgentKind } from '../runner/index.js';
|
|
|
9
9
|
export declare function agentDisplayName(kind: string): string;
|
|
10
10
|
export declare function terminalToLabel(terminal: string): string;
|
|
11
11
|
export declare function terminalToColor(terminal: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* CardKit 2.0 静态卡骨架:schema/config/header + 给定 body elements。
|
|
14
|
+
* 队列状态卡、预算兜底卡等"只有 header 文案 + 少量 div"的卡共用。
|
|
15
|
+
*/
|
|
16
|
+
export declare function cardWithHeader(opts: {
|
|
17
|
+
template: string;
|
|
18
|
+
title: string;
|
|
19
|
+
elements: object[];
|
|
20
|
+
}): object;
|
|
12
21
|
/**
|
|
13
22
|
* Create a CardKit 2.0 button with a callback behavior. Shared constructor
|
|
14
23
|
* for every callback button in the card module (stop/new-session/compact/
|
|
@@ -21,6 +30,8 @@ export declare function cardButton(text: string, value: Record<string, unknown>,
|
|
|
21
30
|
size?: 'small' | 'medium' | 'large';
|
|
22
31
|
/** Fixed width, e.g. '100px' (keeps option-row buttons left-aligned). */
|
|
23
32
|
width?: string;
|
|
33
|
+
/** 置灰(当前项 / 不可执行),仍保留回调 payload。 */
|
|
34
|
+
disabled?: boolean;
|
|
24
35
|
}): object;
|
|
25
36
|
/**
|
|
26
37
|
* Create a CardKit 2.0 stop button with behaviors callback.
|
package/dist/card/card-shared.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentRegistry } from '../runner/index.js';
|
|
2
|
+
import { AGENT_DISPLAY_NAMES } from '../runner/types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Display name for an agent kind in the run card header.
|
|
4
5
|
*
|
|
@@ -11,19 +12,8 @@ export function agentDisplayName(kind) {
|
|
|
11
12
|
if (globalReg) {
|
|
12
13
|
return globalReg.getDisplayName(kind);
|
|
13
14
|
}
|
|
14
|
-
// Fallback
|
|
15
|
-
|
|
16
|
-
if (kind === 'claude')
|
|
17
|
-
return 'Claude';
|
|
18
|
-
if (kind === 'codex')
|
|
19
|
-
return 'Codex';
|
|
20
|
-
if (kind === 'opencode')
|
|
21
|
-
return 'Opencode';
|
|
22
|
-
if (kind === 'pi')
|
|
23
|
-
return 'Pi';
|
|
24
|
-
if (kind === 'kimi')
|
|
25
|
-
return 'Kimi';
|
|
26
|
-
return kind;
|
|
15
|
+
// Fallback for render helpers that run without the global registry.
|
|
16
|
+
return AGENT_DISPLAY_NAMES[kind] ?? kind;
|
|
27
17
|
}
|
|
28
18
|
const TERMINAL_LABELS = {
|
|
29
19
|
done: '已完成',
|
|
@@ -51,6 +41,21 @@ export function terminalToColor(terminal) {
|
|
|
51
41
|
return 'blue';
|
|
52
42
|
return 'red';
|
|
53
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* CardKit 2.0 静态卡骨架:schema/config/header + 给定 body elements。
|
|
46
|
+
* 队列状态卡、预算兜底卡等"只有 header 文案 + 少量 div"的卡共用。
|
|
47
|
+
*/
|
|
48
|
+
export function cardWithHeader(opts) {
|
|
49
|
+
return {
|
|
50
|
+
schema: '2.0',
|
|
51
|
+
config: { wide_screen_mode: true },
|
|
52
|
+
header: {
|
|
53
|
+
template: opts.template,
|
|
54
|
+
title: { tag: 'plain_text', content: opts.title },
|
|
55
|
+
},
|
|
56
|
+
body: { elements: opts.elements },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
54
59
|
/**
|
|
55
60
|
* Create a CardKit 2.0 button with a callback behavior. Shared constructor
|
|
56
61
|
* for every callback button in the card module (stop/new-session/compact/
|
|
@@ -65,6 +70,7 @@ export function cardButton(text, value, opts = {}) {
|
|
|
65
70
|
...(opts.type ? { type: opts.type } : {}),
|
|
66
71
|
...(opts.size ? { size: opts.size } : {}),
|
|
67
72
|
...(opts.width ? { width: opts.width } : {}),
|
|
73
|
+
...(opts.disabled !== undefined ? { disabled: opts.disabled } : {}),
|
|
68
74
|
behaviors: [{ type: 'callback', value }],
|
|
69
75
|
};
|
|
70
76
|
}
|
|
@@ -18,7 +18,6 @@ export declare class RunCardSession extends CardSession<RunState, RunCardRenderO
|
|
|
18
18
|
protected get logPrefix(): string;
|
|
19
19
|
protected get errorPrefix(): string;
|
|
20
20
|
protected renderCard(state: RunState, options: RunCardRenderOptions): object;
|
|
21
|
-
protected hasOwnBudgetProtection(): boolean;
|
|
22
21
|
protected isFlushableState(): boolean;
|
|
23
22
|
push(event: AgentEvent): Promise<void>;
|
|
24
23
|
finish(terminal: Exclude<RunTerminal, 'running' | 'finalizing'>, meta?: FinishMeta): Promise<void>;
|
|
@@ -22,12 +22,6 @@ export class RunCardSession extends CardSession {
|
|
|
22
22
|
renderCard(state, options) {
|
|
23
23
|
return renderRunCard(state, options);
|
|
24
24
|
}
|
|
25
|
-
hasOwnBudgetProtection() {
|
|
26
|
-
// renderRunCard has 3-tier degraded/extreme fallback budget protection
|
|
27
|
-
// that guarantees the returned card is <= 28KB, so the base-class
|
|
28
|
-
// enforceCardBudget stringify pass is redundant for run cards.
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
25
|
isFlushableState() {
|
|
32
26
|
// finish() 已转终态(done/error/interrupted/idle_timeout)时不再 patch;
|
|
33
27
|
// 'finalizing' 是非终态,in-flight/延迟 flush 仍可渲染累积事件。
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import type { RunState, ToolEntry } from './run-state.js';
|
|
2
|
-
/**
|
|
3
|
-
* 带「详情」按钮的工具行数上限(从最新往回数)。
|
|
4
|
-
*
|
|
5
|
-
* 按钮行是节点大户:`column_set` + 2 个 `column` + 标题 div/text + button/text/
|
|
6
|
-
* behaviors/value ≈ 10 个 JSON 节点;纯标题行只有 2 个。24 行全带按钮 ≈ 240 节点,
|
|
7
|
-
* 直接撞飞书的元素总数上限(实测 203 通过 / 204 失败,见 card-budget.ts)。
|
|
8
|
-
* 最近 8 行保留按钮(用户最可能展开的就是刚跑完的),更早的行退化成一行标题。
|
|
9
|
-
*/
|
|
10
|
-
export declare const MAX_TOOL_ROWS_WITH_BUTTON = 8;
|
|
11
2
|
export interface RunCardRenderOptions {
|
|
12
3
|
/**
|
|
13
4
|
* Agent kind for the run card header title (e.g. "Claude · 思考中").
|
|
@@ -60,6 +51,9 @@ type GroupContentPrepared = Map<BlockGroup, string>;
|
|
|
60
51
|
* 状态标签(运行中/已完成…)不再重复:header 已经写了(`🔧 dsh · 调用工具`
|
|
61
52
|
* / `✅ dsh · 已完成`),终态 summary 还有一次。
|
|
62
53
|
*
|
|
54
|
+
* 终态也保留状态行:耗时定格,DSH 的步数/上下文投影展示最后一次快照
|
|
55
|
+
* (2026-10-21 用户要求)。summary 的最终 usage 与状态行并存。
|
|
56
|
+
*
|
|
63
57
|
* 导出供窄屏守卫测试断言分段宽度(与 `estimateCardBytes` 同一惯例)。
|
|
64
58
|
*/
|
|
65
59
|
export declare function statusLine(state: RunState): string;
|