lark-remote 0.5.7 → 0.5.9

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/bridge/index.d.ts +47 -7
  3. package/dist/bridge/index.js +300 -129
  4. package/dist/bridge/queue-manager.d.ts +6 -0
  5. package/dist/bridge/queue-manager.js +61 -63
  6. package/dist/card/approval-render.js +2 -1
  7. package/dist/card/bash-card-session.d.ts +0 -1
  8. package/dist/card/bash-card-session.js +0 -8
  9. package/dist/card/bash-renderer.js +2 -1
  10. package/dist/card/card-budget.js +15 -19
  11. package/dist/card/card-session.d.ts +3 -13
  12. package/dist/card/card-session.js +4 -18
  13. package/dist/card/card-shared.d.ts +11 -0
  14. package/dist/card/card-shared.js +23 -13
  15. package/dist/card/run-card-session.d.ts +0 -1
  16. package/dist/card/run-card-session.js +0 -6
  17. package/dist/card/run-renderer.d.ts +11 -10
  18. package/dist/card/run-renderer.js +198 -35
  19. package/dist/card/run-state.d.ts +29 -2
  20. package/dist/card/run-state.js +23 -4
  21. package/dist/card/text-truncate.d.ts +3 -5
  22. package/dist/card/text-truncate.js +3 -5
  23. package/dist/card/tool-render.js +7 -21
  24. package/dist/common/tool-summary.d.ts +8 -0
  25. package/dist/common/tool-summary.js +38 -20
  26. package/dist/common/turn-failure.d.ts +13 -0
  27. package/dist/common/turn-failure.js +18 -0
  28. package/dist/index.js +13 -14
  29. package/dist/order/index.d.ts +2 -0
  30. package/dist/order/index.js +2 -0
  31. package/dist/router/card-helpers.js +23 -42
  32. package/dist/router/config/codex.js +2 -3
  33. package/dist/router/config/common/model-patch.d.ts +8 -0
  34. package/dist/router/config/common/model-patch.js +13 -0
  35. package/dist/router/config/common/render.js +30 -77
  36. package/dist/router/config/dsh.js +2 -1
  37. package/dist/router/config/kimi.js +3 -3
  38. package/dist/router/index.d.ts +0 -11
  39. package/dist/router/index.js +119 -360
  40. package/dist/router/order-exec-dispatch.js +2 -1
  41. package/dist/router/usage-format.d.ts +5 -0
  42. package/dist/router/usage-format.js +1 -1
  43. package/dist/runner/common/defaults.d.ts +2 -0
  44. package/dist/runner/common/defaults.js +3 -0
  45. package/dist/runner/common/jsonrpc/connection-manager.js +1 -1
  46. package/dist/runner/dsh/client.d.ts +15 -0
  47. package/dist/runner/dsh/client.js +28 -2
  48. package/dist/runner/dsh/goal-view.d.ts +36 -0
  49. package/dist/runner/dsh/goal-view.js +106 -0
  50. package/dist/runner/dsh/runner.js +51 -2
  51. package/dist/runner/index.d.ts +1 -1
  52. package/dist/runner/types.d.ts +59 -1
  53. package/dist/runner/types.js +9 -0
  54. package/dist/session/claude/session-index.d.ts +0 -4
  55. package/dist/session/claude/session-index.js +0 -8
  56. package/dist/session/dsh/sessions.js +58 -2
  57. package/package.json +1 -1
@@ -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
- schema: '2.0',
176
- config: { wide_screen_mode: true },
177
- header: {
178
- template: 'red',
179
- title: { tag: 'plain_text', content: '🚫 队列已满' },
180
- },
181
- body: {
182
- elements: [
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,27 +554,21 @@ export class QueueManager {
559
554
  return;
560
555
  }
561
556
  const previewForCard = liveTask?.messagePreview ?? messagePreview;
562
- const card = {
563
- schema: '2.0',
564
- config: { wide_screen_mode: true },
565
- header: {
566
- template: 'green',
567
- title: { tag: 'plain_text', content: '▶️ 已开始执行' },
568
- },
569
- body: {
570
- elements: [
571
- {
572
- tag: 'div',
573
- text: { tag: 'lark_md', content: `**当前 Workspace:** \`${workspaceName}\`` },
574
- },
575
- { tag: 'hr' },
576
- { tag: 'div', text: { tag: 'lark_md', content: `📝 \`${previewForCard}\`` } },
577
- { tag: 'hr' },
578
- ...this.buildQueueActionButtons(cwd, messageId, true),
579
- ],
580
- },
581
- };
582
- await this.updateCard(cardMessageId, card);
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
+ });
571
+ await this.updateCardWithRetry(cardMessageId, card);
583
572
  getLogger().debug(`[queue-manager] queue card updated to executing messageId=${messageId}`);
584
573
  }
585
574
  catch (err) {
@@ -608,9 +597,9 @@ export class QueueManager {
608
597
  taskList.splice(index, 1);
609
598
  }
610
599
  this.indexRemove(cwd, messageId);
611
- // Note: queueCardMessages mapping is NOT deleted here.
612
- // The caller (handleQueueCancel) will call updateQueueCardToCancelled
613
- // which will clean up the mapping after updating the card.
600
+ // queueCardMessages mapping 仍不在这里删:卡片更新由 Bridge.removeFromQueue
601
+ // 统一触发(单一收口),updateQueueCardToCancelled 成功/失败后都会清掉映射。
602
+ // 直接调 QueueManager.removeFromQueue 的测试/内部用途不会自动更新卡片。
614
603
  getLogger().info(`[queue-manager] removed from queue cwd=${cwd} messageId=${messageId}`);
615
604
  return true;
616
605
  }
@@ -622,28 +611,22 @@ export class QueueManager {
622
611
  return;
623
612
  }
624
613
  const workspaceName = displayName(cwd);
625
- const card = {
626
- schema: '2.0',
627
- config: { wide_screen_mode: true },
628
- header: {
629
- template: 'gray',
630
- title: { tag: 'plain_text', content: '❌ 已撤销' },
631
- },
632
- body: {
633
- elements: [
634
- {
635
- tag: 'div',
636
- text: { tag: 'lark_md', content: `**当前 Workspace:** \`${workspaceName}\`` },
637
- },
638
- { tag: 'hr' },
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
- await this.updateCard(cardMessageId, card);
629
+ await this.updateCardWithRetry(cardMessageId, card);
647
630
  getLogger().debug(`[queue-manager] queue card updated to cancelled, messageId=${messageId}`);
648
631
  }
649
632
  catch (err) {
@@ -653,6 +636,21 @@ export class QueueManager {
653
636
  this.queueCardMessages.delete(messageId);
654
637
  }
655
638
  }
639
+ /**
640
+ * 卡片 PATCH 带一次重试:一次网络抖动不该让排队卡永久停在旧状态——映射
641
+ * 会在本次调用结束时被清理,之后没有任何自动修正机会。
642
+ * 第二次仍失败则向上抛,由调用方 catch 记日志(行为与改前一致)。
643
+ */
644
+ async updateCardWithRetry(messageId, card) {
645
+ try {
646
+ await this.updateCard(messageId, card);
647
+ return;
648
+ }
649
+ catch (err) {
650
+ getLogger().warn(`[queue-manager] queue card update failed, retrying once messageId=${messageId}: ${err.message}`);
651
+ }
652
+ await this.updateCard(messageId, card);
653
+ }
656
654
  /** Get task metadata from queue. */
657
655
  getQueuedTask(cwd, messageId) {
658
656
  // O(1) index lookup instead of array `find`.
@@ -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 { truncateUtf8, truncateMarkdownTables } from './text-truncate.js';
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 { truncateUtf8, CARD_BUDGET_BYTES } from './text-truncate.js';
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 导入,避免三处各自定义后数值漂移。
@@ -1,4 +1,6 @@
1
- import { truncateUtf8, dropOldestMarkdownTables, countMarkdownTables, FEISHU_MAX_TABLES, CARD_BUDGET_BYTES, } from './text-truncate.js';
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
- schema: '2.0',
262
- config: { wide_screen_mode: true },
263
- header: {
264
- template: 'orange',
265
- title: { tag: 'plain_text', content: '⚠️ 内容已截断' },
266
- },
267
- body: {
268
- elements: [
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
- * Whether renderCard() already enforces a size budget (so updateCard can
111
- * skip enforceCardBudget). Subclasses whose renderer has its own
112
- * stringify-level budget protection (e.g. RunCardSession via degraded/
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
- * Whether renderCard() already enforces a size budget (so updateCard can
220
- * skip enforceCardBudget). Subclasses whose renderer has its own
221
- * stringify-level budget protection (e.g. RunCardSession via degraded/
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
- const card = this.renderCard(this.state, this.renderOptions);
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.
@@ -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: hardcoded default mapping for unit tests that exercise
15
- // render helpers without booting the full AgentRegistry.
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: '已完成',
@@ -32,6 +22,8 @@ const TERMINAL_LABELS = {
32
22
  idle_timeout: '已超时',
33
23
  running: '运行中',
34
24
  finalizing: '完成中',
25
+ // 本轮结束、goal 仍 active:任务没结束,标签不能说"完成"。
26
+ goal_continuing: '目标继续',
35
27
  };
36
28
  export function terminalToLabel(terminal) {
37
29
  return TERMINAL_LABELS[terminal] ?? '运行中';
@@ -47,10 +39,27 @@ export function terminalToColor(terminal) {
47
39
  return 'orange';
48
40
  if (terminal === 'finalizing')
49
41
  return 'orange';
42
+ if (terminal === 'goal_continuing')
43
+ return 'orange';
50
44
  if (terminal === 'running')
51
45
  return 'blue';
52
46
  return 'red';
53
47
  }
48
+ /**
49
+ * CardKit 2.0 静态卡骨架:schema/config/header + 给定 body elements。
50
+ * 队列状态卡、预算兜底卡等"只有 header 文案 + 少量 div"的卡共用。
51
+ */
52
+ export function cardWithHeader(opts) {
53
+ return {
54
+ schema: '2.0',
55
+ config: { wide_screen_mode: true },
56
+ header: {
57
+ template: opts.template,
58
+ title: { tag: 'plain_text', content: opts.title },
59
+ },
60
+ body: { elements: opts.elements },
61
+ };
62
+ }
54
63
  /**
55
64
  * Create a CardKit 2.0 button with a callback behavior. Shared constructor
56
65
  * for every callback button in the card module (stop/new-session/compact/
@@ -65,6 +74,7 @@ export function cardButton(text, value, opts = {}) {
65
74
  ...(opts.type ? { type: opts.type } : {}),
66
75
  ...(opts.size ? { size: opts.size } : {}),
67
76
  ...(opts.width ? { width: opts.width } : {}),
77
+ ...(opts.disabled !== undefined ? { disabled: opts.disabled } : {}),
68
78
  behaviors: [{ type: 'callback', value }],
69
79
  };
70
80
  }
@@ -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
- 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;
1
+ import { type RunState, type ToolEntry } from './run-state.js';
11
2
  export interface RunCardRenderOptions {
12
3
  /**
13
4
  * Agent kind for the run card header title (e.g. "Claude · 思考中").
@@ -21,6 +12,13 @@ export interface RunCardRenderOptions {
21
12
  * 无 turn_started → operationKind undefined)同样能拿到按钮。
22
13
  */
23
14
  compactSupported?: boolean;
15
+ /**
16
+ * 卡片没有 `state.sessionId` 时,是否仍有可压缩的会话(bridge 在开跑前从
17
+ * sessionStore 探到的会话)。`false` 表示「本轮没有 init,之前也没有任何
18
+ * 会话」——此时 Compact 按钮点了必然反查不到记录,不渲染。缺省(undefined)
19
+ * 保持原有「按钮可见」语义,供渲染层独立调用方使用。
20
+ */
21
+ sessionFallbackAvailable?: boolean;
24
22
  }
25
23
  type BlockGroup = {
26
24
  kind: 'thinking';
@@ -60,6 +58,9 @@ type GroupContentPrepared = Map<BlockGroup, string>;
60
58
  * 状态标签(运行中/已完成…)不再重复:header 已经写了(`🔧 dsh · 调用工具`
61
59
  * / `✅ dsh · 已完成`),终态 summary 还有一次。
62
60
  *
61
+ * 终态也保留状态行:耗时定格,DSH 的步数/上下文投影展示最后一次快照
62
+ * (2026-10-21 用户要求)。summary 的最终 usage 与状态行并存。
63
+ *
63
64
  * 导出供窄屏守卫测试断言分段宽度(与 `estimateCardBytes` 同一惯例)。
64
65
  */
65
66
  export declare function statusLine(state: RunState): string;