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.
Files changed (190) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.en.md +5 -1
  3. package/README.md +10 -2
  4. package/dist/bridge/approval-coordinator.d.ts +131 -0
  5. package/dist/bridge/approval-coordinator.js +409 -0
  6. package/dist/bridge/inbound-media.d.ts +62 -0
  7. package/dist/bridge/inbound-media.js +282 -0
  8. package/dist/bridge/index.d.ts +138 -5
  9. package/dist/bridge/index.js +569 -65
  10. package/dist/bridge/queue-manager.d.ts +11 -1
  11. package/dist/bridge/queue-manager.js +20 -11
  12. package/dist/card/approval-render.d.ts +16 -0
  13. package/dist/card/approval-render.js +363 -0
  14. package/dist/card/bash-card-session.d.ts +2 -24
  15. package/dist/card/bash-card-session.js +6 -73
  16. package/dist/card/bash-renderer.js +2 -3
  17. package/dist/card/card-budget.js +29 -75
  18. package/dist/card/card-session.d.ts +48 -0
  19. package/dist/card/card-session.js +86 -0
  20. package/dist/card/card-shared.d.ts +13 -10
  21. package/dist/card/card-shared.js +27 -24
  22. package/dist/card/run-card-session.d.ts +2 -55
  23. package/dist/card/run-card-session.js +6 -104
  24. package/dist/card/run-renderer.js +140 -202
  25. package/dist/card/run-state.d.ts +38 -3
  26. package/dist/card/run-state.js +221 -11
  27. package/dist/card/text-truncate.d.ts +13 -12
  28. package/dist/card/text-truncate.js +13 -63
  29. package/dist/common/fs.d.ts +8 -0
  30. package/dist/common/fs.js +17 -0
  31. package/dist/common/guards.d.ts +0 -20
  32. package/dist/common/guards.js +0 -43
  33. package/dist/common/sleep.d.ts +2 -0
  34. package/dist/common/sleep.js +5 -0
  35. package/dist/common/truncate.d.ts +39 -0
  36. package/dist/common/truncate.js +92 -0
  37. package/dist/config/codex-config.d.ts +1 -1
  38. package/dist/config/codex-config.js +1 -1
  39. package/dist/config/dir.js +1 -1
  40. package/dist/config/index.d.ts +116 -9
  41. package/dist/config/index.js +122 -29
  42. package/dist/connector/index.d.ts +74 -2
  43. package/dist/connector/index.js +119 -1
  44. package/dist/index.js +139 -99
  45. package/dist/instance-lock.js +4 -12
  46. package/dist/order/alias-resolve.d.ts +13 -0
  47. package/dist/order/alias-resolve.js +22 -0
  48. package/dist/order/alias-store.d.ts +38 -0
  49. package/dist/order/alias-store.js +106 -0
  50. package/dist/persistence/atomic-write.d.ts +8 -2
  51. package/dist/persistence/atomic-write.js +23 -7
  52. package/dist/restart.js +2 -1
  53. package/dist/router/card-helpers.d.ts +63 -0
  54. package/dist/router/card-helpers.js +146 -0
  55. package/dist/router/config/claude.js +25 -1
  56. package/dist/router/config/codex.d.ts +1 -1
  57. package/dist/router/config/codex.js +33 -5
  58. package/dist/router/config/common/model-patch.d.ts +22 -0
  59. package/dist/router/config/common/model-patch.js +26 -0
  60. package/dist/router/config/common/render.js +9 -4
  61. package/dist/router/config/index.d.ts +12 -0
  62. package/dist/router/config/index.js +25 -1
  63. package/dist/router/config/kimi.d.ts +4 -1
  64. package/dist/router/config/kimi.js +27 -0
  65. package/dist/router/config/opencode.js +18 -5
  66. package/dist/router/config/pi.js +4 -5
  67. package/dist/router/config/types.d.ts +12 -3
  68. package/dist/router/index.d.ts +77 -17
  69. package/dist/router/index.js +455 -549
  70. package/dist/router/utils.d.ts +4 -0
  71. package/dist/router/utils.js +9 -2
  72. package/dist/runner/agent-choices-common.d.ts +16 -0
  73. package/dist/runner/agent-choices-common.js +31 -0
  74. package/dist/runner/claude/runner.d.ts +47 -10
  75. package/dist/runner/claude/runner.js +117 -46
  76. package/dist/runner/claude/session.d.ts +166 -0
  77. package/dist/runner/claude/session.js +749 -0
  78. package/dist/runner/codex/app-server/protocol-types.d.ts +519 -0
  79. package/dist/runner/codex/app-server/protocol-types.js +56 -0
  80. package/dist/runner/codex/app-server/runner.d.ts +104 -0
  81. package/dist/runner/codex/app-server/runner.js +377 -0
  82. package/dist/runner/codex/app-server/translator.d.ts +89 -0
  83. package/dist/runner/codex/app-server/translator.js +716 -0
  84. package/dist/runner/codex/index.d.ts +1 -1
  85. package/dist/runner/codex/index.js +1 -1
  86. package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
  87. package/dist/runner/common/acp/protocol-helpers.js +21 -0
  88. package/dist/runner/common/acp/protocol-types.d.ts +238 -0
  89. package/dist/runner/common/acp/protocol-types.js +75 -0
  90. package/dist/runner/common/connection-based-runner.d.ts +111 -0
  91. package/dist/runner/common/connection-based-runner.js +261 -0
  92. package/dist/runner/common/jsonrpc/client.d.ts +89 -0
  93. package/dist/runner/common/jsonrpc/client.js +243 -0
  94. package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
  95. package/dist/runner/common/jsonrpc/connection-manager.js +205 -0
  96. package/dist/runner/common/jsonrpc/transport.d.ts +50 -0
  97. package/dist/runner/common/jsonrpc/transport.js +230 -0
  98. package/dist/runner/common/jsonrpc/types.d.ts +32 -0
  99. package/dist/runner/common/jsonrpc/types.js +9 -0
  100. package/dist/runner/common/process-stopper.d.ts +2 -1
  101. package/dist/runner/common/process-stopper.js +1 -0
  102. package/dist/runner/common/runner-utils.d.ts +6 -13
  103. package/dist/runner/common/runner-utils.js +6 -47
  104. package/dist/runner/common/spawning-runner.d.ts +24 -70
  105. package/dist/runner/common/spawning-runner.js +98 -152
  106. package/dist/runner/common/usage.d.ts +2 -0
  107. package/dist/runner/common/usage.js +2 -0
  108. package/dist/runner/index.d.ts +5 -5
  109. package/dist/runner/index.js +4 -4
  110. package/dist/runner/kimi/acp/runner.d.ts +106 -0
  111. package/dist/runner/kimi/acp/runner.js +448 -0
  112. package/dist/runner/kimi/acp/translator.d.ts +124 -0
  113. package/dist/runner/kimi/acp/translator.js +336 -0
  114. package/dist/runner/kimi/index.d.ts +1 -1
  115. package/dist/runner/kimi/index.js +1 -1
  116. package/dist/runner/opencode/acp/index.d.ts +1 -0
  117. package/dist/runner/opencode/acp/index.js +2 -0
  118. package/dist/runner/opencode/acp/runner.d.ts +121 -0
  119. package/dist/runner/opencode/acp/runner.js +429 -0
  120. package/dist/runner/opencode/acp/translator.d.ts +119 -0
  121. package/dist/runner/opencode/acp/translator.js +345 -0
  122. package/dist/runner/opencode/index.d.ts +1 -1
  123. package/dist/runner/opencode/index.js +1 -1
  124. package/dist/runner/pi/index.d.ts +1 -1
  125. package/dist/runner/pi/index.js +1 -1
  126. package/dist/runner/pi/rpc/client.d.ts +52 -0
  127. package/dist/runner/pi/rpc/client.js +114 -0
  128. package/dist/runner/pi/rpc/index.d.ts +4 -0
  129. package/dist/runner/pi/rpc/index.js +4 -0
  130. package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
  131. package/dist/runner/pi/rpc/protocol-types.js +21 -0
  132. package/dist/runner/pi/rpc/runner.d.ts +80 -0
  133. package/dist/runner/pi/rpc/runner.js +199 -0
  134. package/dist/runner/pi/rpc/translator.d.ts +55 -0
  135. package/dist/runner/pi/rpc/translator.js +258 -0
  136. package/dist/runner/registry.d.ts +7 -8
  137. package/dist/runner/registry.js +3 -4
  138. package/dist/runner/resolve-agent-choices.js +18 -59
  139. package/dist/runner/sync-agent-choices.js +16 -67
  140. package/dist/runner/types.d.ts +153 -2
  141. package/dist/session/claude/session-index.js +2 -3
  142. package/dist/session/claude/session-reader.js +3 -6
  143. package/dist/session/claude/sessions.js +6 -13
  144. package/dist/session/codex/rollout-reader.d.ts +2 -2
  145. package/dist/session/codex/rollout-reader.js +57 -17
  146. package/dist/session/codex/sessions.d.ts +1 -1
  147. package/dist/session/codex/sessions.js +2 -2
  148. package/dist/session/common/content-blocks.d.ts +7 -0
  149. package/dist/session/common/content-blocks.js +13 -9
  150. package/dist/session/common/jsonl.d.ts +8 -25
  151. package/dist/session/common/jsonl.js +126 -261
  152. package/dist/session/common/pagination.d.ts +28 -0
  153. package/dist/session/common/pagination.js +33 -0
  154. package/dist/session/kimi/sessions.d.ts +34 -1
  155. package/dist/session/kimi/sessions.js +208 -98
  156. package/dist/session/opencode/sessions.d.ts +17 -3
  157. package/dist/session/opencode/sessions.js +29 -29
  158. package/dist/session/pi/sessions.d.ts +5 -1
  159. package/dist/session/pi/sessions.js +12 -16
  160. package/dist/session/session-store.d.ts +11 -1
  161. package/dist/session/session-store.js +75 -178
  162. package/dist/update/index.d.ts +2 -2
  163. package/dist/update/index.js +2 -2
  164. package/dist/update/install.d.ts +20 -2
  165. package/dist/update/install.js +51 -7
  166. package/package.json +1 -1
  167. package/dist/runner/codex/argv.d.ts +0 -33
  168. package/dist/runner/codex/argv.js +0 -40
  169. package/dist/runner/codex/jsonl.d.ts +0 -37
  170. package/dist/runner/codex/jsonl.js +0 -221
  171. package/dist/runner/codex/runner.d.ts +0 -63
  172. package/dist/runner/codex/runner.js +0 -124
  173. package/dist/runner/common/exec-translator.d.ts +0 -73
  174. package/dist/runner/common/exec-translator.js +0 -105
  175. package/dist/runner/kimi/jsonl.d.ts +0 -28
  176. package/dist/runner/kimi/jsonl.js +0 -143
  177. package/dist/runner/kimi/runner.d.ts +0 -49
  178. package/dist/runner/kimi/runner.js +0 -105
  179. package/dist/runner/opencode/argv.d.ts +0 -32
  180. package/dist/runner/opencode/argv.js +0 -35
  181. package/dist/runner/opencode/jsonl.d.ts +0 -68
  182. package/dist/runner/opencode/jsonl.js +0 -295
  183. package/dist/runner/opencode/runner.d.ts +0 -61
  184. package/dist/runner/opencode/runner.js +0 -93
  185. package/dist/runner/pi/jsonl.d.ts +0 -128
  186. package/dist/runner/pi/jsonl.js +0 -249
  187. package/dist/runner/pi/runner.d.ts +0 -36
  188. package/dist/runner/pi/runner.js +0 -76
  189. package/dist/session/common/catch-up.d.ts +0 -12
  190. package/dist/session/common/catch-up.js +0 -31
@@ -1,6 +1,4 @@
1
- import { truncateUtf8, truncateMarkdownTables, countMarkdownTables, FEISHU_MAX_TABLES, } from './text-truncate.js';
2
- // 飞书卡片大小限制(安全阈值,取 run-renderer 相同值)
3
- const CARD_BUDGET_BYTES = 28_000;
1
+ import { truncateUtf8, truncateMarkdownTables, countMarkdownTables, FEISHU_MAX_TABLES, CARD_BUDGET_BYTES, } from './text-truncate.js';
4
2
  /**
5
3
  * 静态卡片体积保护
6
4
  *
@@ -17,7 +15,6 @@ export function enforceCardBudget(card, opts = {}) {
17
15
  const totalTables = countCardTables(JSON.parse(cardStr));
18
16
  if (totalTables > FEISHU_MAX_TABLES) {
19
17
  // 深拷贝后对每个 lark_md 文本做 truncateMarkdownTables
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
18
  const fixed = JSON.parse(cardStr);
22
19
  truncateCardMarkdownTables(fixed);
23
20
  const fixedStr = JSON.stringify(fixed);
@@ -38,7 +35,6 @@ export function enforceCardBudget(card, opts = {}) {
38
35
  return { card, wasTruncated: false, bytesBefore, bytesAfter: bytesBefore };
39
36
  }
40
37
  // 字节超限但 table 不超限
41
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
38
  return enforceByteBudget(JSON.parse(cardStr), opts, bytesBefore);
43
39
  }
44
40
  /**
@@ -59,12 +55,9 @@ export function enforceCardBudget(card, opts = {}) {
59
55
  * 阶段4 截断剩余顶层 div 文本
60
56
  * 阶段5 最终兜底 minimal card(骨架本身已超限的病理场景)
61
57
  */
62
- function enforceByteBudget(
63
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
- truncated, opts = {}, bytesBefore) {
58
+ function enforceByteBudget(truncated, opts = {}, bytesBefore) {
65
59
  const { maxEventPanels = 5, maxPanelContentBytes = 2000, truncationHint = '📜 还有 {count} 个事件未显示', } = opts;
66
60
  const reasons = [];
67
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
61
  const elements = truncated.body?.elements ?? [];
69
62
  const size = () => Buffer.byteLength(JSON.stringify(truncated), 'utf8');
70
63
  const done = (wasTruncated) => ({
@@ -173,7 +166,6 @@ truncated, opts = {}, bytesBefore) {
173
166
  /**
174
167
  * 在卡片头部插入「N 个事件未显示」提示(仅在有面板被移除时)。
175
168
  */
176
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
177
169
  function insertOmittedHint(elements, omitted, truncationHint) {
178
170
  if (omitted <= 0)
179
171
  return;
@@ -185,7 +177,6 @@ function insertOmittedHint(elements, omitted, truncationHint) {
185
177
  /**
186
178
  * 获取所有 collapsible_panel 的索引(结构化判定,不看标题文本)。
187
179
  */
188
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
180
  function getPanelIndices(elements) {
190
181
  const indices = [];
191
182
  for (let i = 0; i < elements.length; i++) {
@@ -196,84 +187,47 @@ function getPanelIndices(elements) {
196
187
  return indices;
197
188
  }
198
189
  /**
199
- * Count all markdown tables across every lark_md text field in a card JSON.
200
- * Recursively walks elements, collapsible_panel.elements, column.elements, etc.
190
+ * 遍历卡片元素树(含 collapsible_panel.elements 与 column_set.columns[].elements)。
191
+ *
192
+ * count 与 truncate 两条路径共用同一遍历,避免两套递归漂移(G5 Duplication)。
201
193
  */
202
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
- function countCardTables(card) {
204
- let total = 0;
205
- const elements = card?.body?.elements ?? [];
194
+ function walkCardElements(elements, visit) {
206
195
  for (const el of elements) {
207
- total += countElementTables(el);
196
+ visit(el);
197
+ if (Array.isArray(el.elements))
198
+ walkCardElements(el.elements, visit);
199
+ if (Array.isArray(el.columns)) {
200
+ for (const col of el.columns) {
201
+ if (Array.isArray(col.elements))
202
+ walkCardElements(col.elements, visit);
203
+ }
204
+ }
208
205
  }
209
- return total;
210
206
  }
211
207
  /**
212
- * Recursively count markdown tables in a single element and its children.
208
+ * Count all markdown tables across every lark_md text field in a card JSON.
213
209
  */
214
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
- function countElementTables(el) {
216
- let count = 0;
217
- // lark_md div text
218
- if (el.text?.tag === 'lark_md' && typeof el.text.content === 'string') {
219
- count += countMarkdownTables(el.text.content);
220
- }
221
- // collapsible_panel inner elements
222
- if (Array.isArray(el.elements)) {
223
- for (const child of el.elements) {
224
- count += countElementTables(child);
225
- }
226
- }
227
- // column_set → columns → elements
228
- if (Array.isArray(el.columns)) {
229
- for (const col of el.columns) {
230
- if (Array.isArray(col.elements)) {
231
- for (const child of col.elements) {
232
- count += countElementTables(child);
233
- }
234
- }
210
+ function countCardTables(card) {
211
+ let total = 0;
212
+ walkCardElements(card?.body?.elements ?? [], (el) => {
213
+ if (el.text?.tag === 'lark_md' && typeof el.text.content === 'string') {
214
+ total += countMarkdownTables(el.text.content);
235
215
  }
236
- }
237
- return count;
216
+ });
217
+ return total;
238
218
  }
239
219
  /**
240
220
  * In-place truncate markdown tables in every lark_md text field of a card,
241
221
  * keeping at most FEISHU_MAX_TABLES per field.
242
222
  */
243
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
244
223
  function truncateCardMarkdownTables(card) {
245
- const elements = card?.body?.elements ?? [];
246
- for (const el of elements) {
247
- truncateElementTables(el);
248
- }
249
- }
250
- /**
251
- * Recursively truncate markdown tables in a single element and its children.
252
- */
253
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
- function truncateElementTables(el) {
255
- // lark_md div text
256
- if (el.text?.tag === 'lark_md' && typeof el.text.content === 'string') {
257
- const truncated = truncateMarkdownTables(el.text.content);
258
- if (truncated !== el.text.content) {
259
- el.text.content = truncated;
260
- }
261
- }
262
- // collapsible_panel inner elements
263
- if (Array.isArray(el.elements)) {
264
- for (const child of el.elements) {
265
- truncateElementTables(child);
266
- }
267
- }
268
- // column_set → columns → elements
269
- if (Array.isArray(el.columns)) {
270
- for (const col of el.columns) {
271
- if (Array.isArray(col.elements)) {
272
- for (const child of col.elements) {
273
- truncateElementTables(child);
274
- }
224
+ walkCardElements(card?.body?.elements ?? [], (el) => {
225
+ if (el.text?.tag === 'lark_md' && typeof el.text.content === 'string') {
226
+ const truncated = truncateMarkdownTables(el.text.content);
227
+ if (truncated !== el.text.content) {
228
+ el.text.content = truncated;
275
229
  }
276
230
  }
277
- }
231
+ });
278
232
  }
279
233
  //# sourceMappingURL=card-budget.js.map
@@ -52,10 +52,58 @@ export declare abstract class CardSession<S, RO = unknown> {
52
52
  renderOptions?: RO;
53
53
  startTimeoutMs?: number;
54
54
  settleTimeoutMs?: number;
55
+ /** update/push 合批窗口,默认 100ms(0 = 禁用合批,每事件立即 flush)。 */
56
+ coalesceMs?: number;
55
57
  });
58
+ /**
59
+ * 调度一次延迟 flush。已有 pending 调度则复用(合批核心);否则起一个
60
+ * coalesceMs 定时器。flushInFlight 期间不再起 timer(避免重入叠加),但置
61
+ * pendingReschedule 以便 in-flight flush 完成后重新调度——否则 in-flight 期间
62
+ * 到达的 push/update 会停留为 stale 中间帧(P2-1)。
63
+ */
64
+ protected scheduleFlush(): void;
65
+ /**
66
+ * 执行一次 render + updateCard。合批窗口到期或 finish 即时 flush 时调用。
67
+ * flushInFlight 防止 timer 回调与 finish 的即时 flush 重入。
68
+ *
69
+ * 终态守卫:若 finish() 已把 state 转为终态,跳过本次 patch——避免 in-flight flush
70
+ * 用 pre-terminal state 覆盖 finish 已发的终态卡。finish() 自身会 await 任何
71
+ * in-flight flush 保证顺序,此守卫是双保险。
72
+ */
73
+ protected flush(): Promise<void>;
74
+ /**
75
+ * Whether the current state still permits a flush (i.e. is not yet terminal).
76
+ * Subclasses define their terminal set. Run treats 'finalizing' as non-terminal.
77
+ */
78
+ protected abstract isFlushableState(): boolean;
79
+ /**
80
+ * 取消待 flush 的合批调度(finish/settle 前清理,避免延迟 update 与终态
81
+ * 渲染竞争)。返回是否有被取消的 pending 调度。同时清掉 pendingReschedule,
82
+ * 防止 in-flight flush 的 finally 在 finish await 之后又起 dangling timer
83
+ * (finish 自身会渲染含累积事件的终态卡,无需 follow-up)。
84
+ */
85
+ protected cancelPendingFlush(): boolean;
56
86
  get currentState(): S;
57
87
  /** The messageId of the streaming card, available after `start()` resolves. */
58
88
  get cardMessageId(): string | undefined;
89
+ /**
90
+ * P1-3 合批窗口(ms)。连续事件各触发一次全卡 render + patch 是流式 CPU 主要
91
+ * 来源;窗口内多个事件复用同一次延迟 flush。窗口大小可通过 constructor 覆盖
92
+ * (测试用小窗口/0 禁用)。run/bash 子类共用的 flush 骨架(scheduleFlush/flush/
93
+ * cancelPendingFlush/settle)下沉到基类。
94
+ */
95
+ protected readonly coalesceMs: number;
96
+ private flushTimer;
97
+ private flushInFlight; /** P2-1:in-flight flush 期间到达的 push/update 标记。scheduleFlush 在
98
+ * flushInFlight 时早退不调度新 timer,但事件已 reduce 进 state;若无此标志,
99
+ * in-flight flush 完成后该事件无 follow-up render,停留为 stale 中间帧。
100
+ * finally 检查此标志后重新 scheduleFlush,保证 in-flight 期间累积的事件最终被渲染。 */
101
+ private pendingReschedule;
102
+ /** 当前 in-flight flush 的 promise(若 flush 正在 await updateCard)。
103
+ * finish() 在终态 patch 前 await 它,保证 in-flight 的 pre-terminal patch 先落地、
104
+ * terminal patch 后落地——否则两条 controller.update 并发无 FIFO,
105
+ * pre-terminal 可能在 terminal 之后 resolve 留下"思考中"终帧(P2)。 */
106
+ protected flushP: Promise<void> | undefined;
59
107
  start(): Promise<void>;
60
108
  settle(): Promise<StreamSettleResult>;
61
109
  /**
@@ -40,6 +40,74 @@ export class CardSession {
40
40
  this.resolveController = resolve;
41
41
  this.rejectController = reject;
42
42
  });
43
+ this.coalesceMs = opts.coalesceMs ?? 100;
44
+ }
45
+ /**
46
+ * 调度一次延迟 flush。已有 pending 调度则复用(合批核心);否则起一个
47
+ * coalesceMs 定时器。flushInFlight 期间不再起 timer(避免重入叠加),但置
48
+ * pendingReschedule 以便 in-flight flush 完成后重新调度——否则 in-flight 期间
49
+ * 到达的 push/update 会停留为 stale 中间帧(P2-1)。
50
+ */
51
+ scheduleFlush() {
52
+ if (this.flushTimer)
53
+ return;
54
+ if (this.flushInFlight) {
55
+ this.pendingReschedule = true;
56
+ return;
57
+ }
58
+ this.flushTimer = setTimeout(() => {
59
+ this.flushTimer = undefined;
60
+ void this.flush();
61
+ }, this.coalesceMs);
62
+ }
63
+ /**
64
+ * 执行一次 render + updateCard。合批窗口到期或 finish 即时 flush 时调用。
65
+ * flushInFlight 防止 timer 回调与 finish 的即时 flush 重入。
66
+ *
67
+ * 终态守卫:若 finish() 已把 state 转为终态,跳过本次 patch——避免 in-flight flush
68
+ * 用 pre-terminal state 覆盖 finish 已发的终态卡。finish() 自身会 await 任何
69
+ * in-flight flush 保证顺序,此守卫是双保险。
70
+ */
71
+ async flush() {
72
+ if (this.flushInFlight)
73
+ return;
74
+ if (!this.isFlushableState())
75
+ return;
76
+ this.flushInFlight = true;
77
+ const flushPromise = (async () => {
78
+ try {
79
+ await this.updateCard();
80
+ }
81
+ finally {
82
+ this.flushInFlight = false;
83
+ // flushInFlight 守卫保证同一时刻只有一个 in-flight flush,故可无条件清空。
84
+ this.flushP = undefined;
85
+ // P2-1:in-flight 期间若有 push/update 到达(pendingReschedule),重新调度一次
86
+ // flush 渲染累积事件,避免 stale 中间帧。终态守卫在 flush() 入口已拦截,
87
+ // 故 finish 后不会误触发。
88
+ if (this.pendingReschedule) {
89
+ this.pendingReschedule = false;
90
+ this.scheduleFlush();
91
+ }
92
+ }
93
+ })();
94
+ this.flushP = flushPromise;
95
+ return flushPromise;
96
+ }
97
+ /**
98
+ * 取消待 flush 的合批调度(finish/settle 前清理,避免延迟 update 与终态
99
+ * 渲染竞争)。返回是否有被取消的 pending 调度。同时清掉 pendingReschedule,
100
+ * 防止 in-flight flush 的 finally 在 finish await 之后又起 dangling timer
101
+ * (finish 自身会渲染含累积事件的终态卡,无需 follow-up)。
102
+ */
103
+ cancelPendingFlush() {
104
+ this.pendingReschedule = false;
105
+ if (this.flushTimer) {
106
+ clearTimeout(this.flushTimer);
107
+ this.flushTimer = undefined;
108
+ return true;
109
+ }
110
+ return false;
43
111
  }
44
112
  get currentState() {
45
113
  return this.state;
@@ -48,6 +116,24 @@ export class CardSession {
48
116
  get cardMessageId() {
49
117
  return this.messageId;
50
118
  }
119
+ /**
120
+ * P1-3 合批窗口(ms)。连续事件各触发一次全卡 render + patch 是流式 CPU 主要
121
+ * 来源;窗口内多个事件复用同一次延迟 flush。窗口大小可通过 constructor 覆盖
122
+ * (测试用小窗口/0 禁用)。run/bash 子类共用的 flush 骨架(scheduleFlush/flush/
123
+ * cancelPendingFlush/settle)下沉到基类。
124
+ */
125
+ coalesceMs;
126
+ flushTimer;
127
+ flushInFlight = false; /** P2-1:in-flight flush 期间到达的 push/update 标记。scheduleFlush 在
128
+ * flushInFlight 时早退不调度新 timer,但事件已 reduce 进 state;若无此标志,
129
+ * in-flight flush 完成后该事件无 follow-up render,停留为 stale 中间帧。
130
+ * finally 检查此标志后重新 scheduleFlush,保证 in-flight 期间累积的事件最终被渲染。 */
131
+ pendingReschedule = false;
132
+ /** 当前 in-flight flush 的 promise(若 flush 正在 await updateCard)。
133
+ * finish() 在终态 patch 前 await 它,保证 in-flight 的 pre-terminal patch 先落地、
134
+ * terminal patch 后落地——否则两条 controller.update 并发无 FIFO,
135
+ * pre-terminal 可能在 terminal 之后 resolve 留下"思考中"终帧(P2)。 */
136
+ flushP;
51
137
  async start() {
52
138
  if (this.streamOutcome) {
53
139
  // 二次调用也套 timeout 保护:若 controllerReady 永不 settle(首次超时失败),
@@ -1,8 +1,4 @@
1
1
  import { type AgentKind } from '../runner/index.js';
2
- interface TruncateOptions {
3
- suffix?: string;
4
- normalizeWhitespace?: boolean;
5
- }
6
2
  /**
7
3
  * Display name for an agent kind in the run card header.
8
4
  *
@@ -11,7 +7,7 @@ interface TruncateOptions {
11
7
  * wire up the registry (notably unit tests of pure render helpers).
12
8
  */
13
9
  export declare function agentDisplayName(kind: string): string;
14
- export declare function truncate(str: string, max: number, options?: TruncateOptions): string;
10
+ export { truncate } from '../common/truncate.js';
15
11
  interface TerminalLabelOptions {
16
12
  prefix?: string;
17
13
  }
@@ -27,10 +23,17 @@ export declare function stopButton(runId: string): object;
27
23
  */
28
24
  export declare function newSessionButton(): object;
29
25
  /**
30
- * Create a CardKit 2.0 "resume session" button with behaviors callback.
31
- * Carries both sessionId and agent so the handler routes to the correct
32
- * agent's session reader (P2 fix: completion cards from non-default agents
33
- * must carry agent to avoid wrong-reader fallback).
26
+ * Create a CardKit 2.0 Compact button for Codex app-server mode.
27
+ * Triggers thread/compact/start via the codex.compact callback.
28
+ */
29
+ export declare function compactButton(runId: string): object;
30
+ /**
31
+ * Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
32
+ * Unlike compactButton (which targets a finished run's runId), this targets a
33
+ * sessionId directly: the bridge resolves the session and calls the app-server
34
+ * runner's runCompact() without a runId. Carries agent so the handler routes
35
+ * to the correct session reader + runner when the card was rendered for a
36
+ * non-default agent.
34
37
  */
38
+ export declare function resumeCompactButton(sessionId: string, agent: string): object;
35
39
  export declare function resumeUseButton(sessionId: string, agent: AgentKind): object;
36
- export {};
@@ -25,26 +25,7 @@ export function agentDisplayName(kind) {
25
25
  return 'Kimi';
26
26
  return kind;
27
27
  }
28
- export function truncate(str, max, options) {
29
- const suffix = options?.suffix ?? '…';
30
- const normalize = options?.normalizeWhitespace ?? false;
31
- let s = str;
32
- if (normalize)
33
- s = s.replace(/\s+/g, ' ').trim();
34
- if (s.length <= max)
35
- return s;
36
- const suffixLen = suffix.length;
37
- const budget = max - suffixLen;
38
- if (budget <= 0)
39
- return suffix;
40
- let end = 0;
41
- for (const ch of s) {
42
- if (end + ch.length > budget)
43
- break;
44
- end += ch.length;
45
- }
46
- return s.slice(0, end) + suffix;
47
- }
28
+ export { truncate } from '../common/truncate.js';
48
29
  const TERMINAL_LABELS = {
49
30
  done: '已完成',
50
31
  error: '出错',
@@ -97,11 +78,33 @@ export function newSessionButton() {
97
78
  };
98
79
  }
99
80
  /**
100
- * Create a CardKit 2.0 "resume session" button with behaviors callback.
101
- * Carries both sessionId and agent so the handler routes to the correct
102
- * agent's session reader (P2 fix: completion cards from non-default agents
103
- * must carry agent to avoid wrong-reader fallback).
81
+ * Create a CardKit 2.0 Compact button for Codex app-server mode.
82
+ * Triggers thread/compact/start via the codex.compact callback.
83
+ */
84
+ export function compactButton(runId) {
85
+ return {
86
+ tag: 'button',
87
+ text: { content: '🗜 Compact' },
88
+ type: 'primary',
89
+ behaviors: [{ type: 'callback', value: { cmd: 'codex.compact', runId } }],
90
+ };
91
+ }
92
+ /**
93
+ * Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
94
+ * Unlike compactButton (which targets a finished run's runId), this targets a
95
+ * sessionId directly: the bridge resolves the session and calls the app-server
96
+ * runner's runCompact() without a runId. Carries agent so the handler routes
97
+ * to the correct session reader + runner when the card was rendered for a
98
+ * non-default agent.
104
99
  */
100
+ export function resumeCompactButton(sessionId, agent) {
101
+ return {
102
+ tag: 'button',
103
+ text: { content: '🗜 Compact' },
104
+ type: 'primary',
105
+ behaviors: [{ type: 'callback', value: { cmd: 'resume.compact', sessionId, agent } }],
106
+ };
107
+ }
105
108
  export function resumeUseButton(sessionId, agent) {
106
109
  return {
107
110
  tag: 'button',
@@ -1,34 +1,9 @@
1
- import { CardSession, type CardChannel, type StreamSettleResult } from './card-session.js';
1
+ import { CardSession, type CardChannel } from './card-session.js';
2
2
  import type { AgentEvent } from '../runner/index.js';
3
3
  import { type FinishMeta, type RunState, type RunTerminal } from './run-state.js';
4
4
  import { type RunCardRenderOptions } from './run-renderer.js';
5
5
  export type { CardChannel as RunCardChannel };
6
6
  export declare class RunCardSession extends CardSession<RunState, RunCardRenderOptions> {
7
- /**
8
- * P1-3 push 合批窗口(ms)。连续 text delta 各触发一次全卡 render + patch 是
9
- * 流式 CPU 主要来源(§P1-3)。窗口内多个事件复用同一次
10
- * 延迟 flush,只 render + controller.update 一次。100ms 内人眼无感,且 text
11
- * 流式 patch 次数 -80%。窗口大小可通过 constructor 覆盖(测试用小窗口)。
12
- */
13
- private readonly coalesceMs;
14
- private flushTimer;
15
- private flushInFlight;
16
- /**
17
- * P2-1:in-flight flush 期间到达的 push 标记。scheduleFlush 在 flushInFlight
18
- * 时早退不调度新 timer,但事件已 reduce 进 state;若无此标志,in-flight flush
19
- * 完成后该事件无 follow-up render,停留为 stale 中间帧(生产 SDK 的
20
- * controller.update 经 throttle + setTimeout 异步 resolve,故 flush 与
21
- * 下一个 push 交叠是真实场景)。finally 检查此标志后重新 scheduleFlush,
22
- * 保证 in-flight 期间累积的事件最终被渲染。
23
- */
24
- private pendingReschedule;
25
- /**
26
- * 当前 in-flight flush 的 promise(若 flush 正在 await updateCard)。
27
- * finish() 在终态 patch 前 await 它,保证 in-flight 的 pre-terminal patch
28
- * 先落地、terminal patch 后落地——否则两条 controller.update 并发无 FIFO,
29
- * pre-terminal 可能在 terminal 之后 resolve 留下"思考中"终帧(P2)。
30
- */
31
- private flushP;
32
7
  constructor(opts: {
33
8
  connector: CardChannel;
34
9
  chatId: string;
@@ -44,35 +19,7 @@ export declare class RunCardSession extends CardSession<RunState, RunCardRenderO
44
19
  protected get errorPrefix(): string;
45
20
  protected renderCard(state: RunState, options: RunCardRenderOptions): object;
46
21
  protected hasOwnBudgetProtection(): boolean;
22
+ protected isFlushableState(): boolean;
47
23
  push(event: AgentEvent): Promise<void>;
48
- /**
49
- * 调度一次延迟 flush。已有 pending 调度则复用(合批核心);否则起一个
50
- * coalesceMs 定时器。flushInFlight 期间不再起 timer(避免重入叠加),但置
51
- * pendingReschedule 以便 in-flight flush 完成后重新调度——否则 in-flight 期间
52
- * 到达的 push 会停留为 stale 中间帧(P2-1)。
53
- */
54
- private scheduleFlush;
55
- /**
56
- * 执行一次 render + updateCard。合批窗口到期或 finish 立即 flush 时调用。
57
- * flushInFlight 防止 timer 回调与 finish 的即时 flush 重入。
58
- *
59
- * 终态守卫:若 finish() 已把 state 转为终态(done/error/interrupted/idle_timeout),
60
- * 跳过本次 patch——避免 in-flight flush 用 pre-terminal state 覆盖 finish 已发的
61
- * 终态卡。finish() 自身会 await 任何 in-flight flush 保证顺序,此守卫是双保险。
62
- */
63
- private flush;
64
- /**
65
- * 取消待 flush 的合批调度(finish/settle 前清理,避免延迟 update 与终态
66
- * 渲染竞争)。返回是否有被取消的 pending 调度。同时清掉 pendingReschedule,
67
- * 防止 in-flight flush 的 finally 在 finish await 之后又起 dangling timer
68
- * (finish 自身会渲染含累积事件的终态卡,无需 follow-up)。
69
- */
70
- private cancelPendingFlush;
71
24
  finish(terminal: Exclude<RunTerminal, 'running' | 'finalizing'>, meta?: FinishMeta): Promise<void>;
72
- /**
73
- * settle 前兜底取消合批调度。bridge 正常流程 finish→settle,finish 已 cancel;
74
- * 但若未来有直调 settle(未经 finish)的路径,避免 dangling timer 在 release
75
- * 后触发 void flush() 去更新已释放的 stream(harmless 但无谓)。
76
- */
77
- settle(): Promise<StreamSettleResult>;
78
25
  }
@@ -2,31 +2,6 @@ import { CardSession } from './card-session.js';
2
2
  import { createInitialRunState, finishRun, reduceRunState, } from './run-state.js';
3
3
  import { renderRunCard } from './run-renderer.js';
4
4
  export class RunCardSession extends CardSession {
5
- /**
6
- * P1-3 push 合批窗口(ms)。连续 text delta 各触发一次全卡 render + patch 是
7
- * 流式 CPU 主要来源(§P1-3)。窗口内多个事件复用同一次
8
- * 延迟 flush,只 render + controller.update 一次。100ms 内人眼无感,且 text
9
- * 流式 patch 次数 -80%。窗口大小可通过 constructor 覆盖(测试用小窗口)。
10
- */
11
- coalesceMs;
12
- flushTimer;
13
- flushInFlight = false;
14
- /**
15
- * P2-1:in-flight flush 期间到达的 push 标记。scheduleFlush 在 flushInFlight
16
- * 时早退不调度新 timer,但事件已 reduce 进 state;若无此标志,in-flight flush
17
- * 完成后该事件无 follow-up render,停留为 stale 中间帧(生产 SDK 的
18
- * controller.update 经 throttle + setTimeout 异步 resolve,故 flush 与
19
- * 下一个 push 交叠是真实场景)。finally 检查此标志后重新 scheduleFlush,
20
- * 保证 in-flight 期间累积的事件最终被渲染。
21
- */
22
- pendingReschedule = false;
23
- /**
24
- * 当前 in-flight flush 的 promise(若 flush 正在 await updateCard)。
25
- * finish() 在终态 patch 前 await 它,保证 in-flight 的 pre-terminal patch
26
- * 先落地、terminal patch 后落地——否则两条 controller.update 并发无 FIFO,
27
- * pre-terminal 可能在 terminal 之后 resolve 留下"思考中"终帧(P2)。
28
- */
29
- flushP;
30
5
  constructor(opts) {
31
6
  super(createInitialRunState(opts.runId), {
32
7
  connector: opts.connector,
@@ -35,8 +10,8 @@ export class RunCardSession extends CardSession {
35
10
  renderOptions: opts.renderOptions,
36
11
  startTimeoutMs: opts.startTimeoutMs,
37
12
  settleTimeoutMs: opts.settleTimeoutMs,
13
+ coalesceMs: opts.coalesceMs,
38
14
  });
39
- this.coalesceMs = opts.coalesceMs ?? 100;
40
15
  }
41
16
  get logPrefix() {
42
17
  return '[card]';
@@ -53,6 +28,11 @@ export class RunCardSession extends CardSession {
53
28
  // enforceCardBudget stringify pass is redundant for run cards.
54
29
  return true;
55
30
  }
31
+ isFlushableState() {
32
+ // finish() 已转终态(done/error/interrupted/idle_timeout)时不再 patch;
33
+ // 'finalizing' 是非终态,in-flight/延迟 flush 仍可渲染累积事件。
34
+ return this.state.terminal === 'running' || this.state.terminal === 'finalizing';
35
+ }
56
36
  async push(event) {
57
37
  // state 每事件立即 reduce(始终最新),但 updateCard 延迟到合批窗口末尾。
58
38
  // 窗口内多次 push 复用同一 flushTimer,只 render + controller.update 一次
@@ -70,75 +50,6 @@ export class RunCardSession extends CardSession {
70
50
  }
71
51
  this.scheduleFlush();
72
52
  }
73
- /**
74
- * 调度一次延迟 flush。已有 pending 调度则复用(合批核心);否则起一个
75
- * coalesceMs 定时器。flushInFlight 期间不再起 timer(避免重入叠加),但置
76
- * pendingReschedule 以便 in-flight flush 完成后重新调度——否则 in-flight 期间
77
- * 到达的 push 会停留为 stale 中间帧(P2-1)。
78
- */
79
- scheduleFlush() {
80
- if (this.flushTimer)
81
- return;
82
- if (this.flushInFlight) {
83
- this.pendingReschedule = true;
84
- return;
85
- }
86
- this.flushTimer = setTimeout(() => {
87
- this.flushTimer = undefined;
88
- void this.flush();
89
- }, this.coalesceMs);
90
- }
91
- /**
92
- * 执行一次 render + updateCard。合批窗口到期或 finish 立即 flush 时调用。
93
- * flushInFlight 防止 timer 回调与 finish 的即时 flush 重入。
94
- *
95
- * 终态守卫:若 finish() 已把 state 转为终态(done/error/interrupted/idle_timeout),
96
- * 跳过本次 patch——避免 in-flight flush 用 pre-terminal state 覆盖 finish 已发的
97
- * 终态卡。finish() 自身会 await 任何 in-flight flush 保证顺序,此守卫是双保险。
98
- */
99
- async flush() {
100
- if (this.flushInFlight)
101
- return;
102
- // finish() 已转终态:in-flight/延迟 flush 不再用旧 state patch
103
- if (this.state.terminal !== 'running' && this.state.terminal !== 'finalizing') {
104
- return;
105
- }
106
- this.flushInFlight = true;
107
- const flushPromise = (async () => {
108
- try {
109
- await this.updateCard();
110
- }
111
- finally {
112
- this.flushInFlight = false;
113
- // flushInFlight 守卫保证同一时刻只有一个 in-flight flush,故可无条件清空。
114
- this.flushP = undefined;
115
- // P2-1:in-flight 期间若有 push 到达(pendingReschedule),重新调度一次
116
- // flush 渲染累积事件,避免 stale 中间帧。终态守卫在 flush() 入口已拦截,
117
- // 故 finish 后不会误触发。
118
- if (this.pendingReschedule) {
119
- this.pendingReschedule = false;
120
- this.scheduleFlush();
121
- }
122
- }
123
- })();
124
- this.flushP = flushPromise;
125
- return flushPromise;
126
- }
127
- /**
128
- * 取消待 flush 的合批调度(finish/settle 前清理,避免延迟 update 与终态
129
- * 渲染竞争)。返回是否有被取消的 pending 调度。同时清掉 pendingReschedule,
130
- * 防止 in-flight flush 的 finally 在 finish await 之后又起 dangling timer
131
- * (finish 自身会渲染含累积事件的终态卡,无需 follow-up)。
132
- */
133
- cancelPendingFlush() {
134
- this.pendingReschedule = false;
135
- if (this.flushTimer) {
136
- clearTimeout(this.flushTimer);
137
- this.flushTimer = undefined;
138
- return true;
139
- }
140
- return false;
141
- }
142
53
  async finish(terminal, meta = {}) {
143
54
  // 终态必须即时显示(spec):取消合批窗口,不等延迟 flush 调度。
144
55
  this.cancelPendingFlush();
@@ -152,14 +63,5 @@ export class RunCardSession extends CardSession {
152
63
  await this.updateCard();
153
64
  this.release();
154
65
  }
155
- /**
156
- * settle 前兜底取消合批调度。bridge 正常流程 finish→settle,finish 已 cancel;
157
- * 但若未来有直调 settle(未经 finish)的路径,避免 dangling timer 在 release
158
- * 后触发 void flush() 去更新已释放的 stream(harmless 但无谓)。
159
- */
160
- async settle() {
161
- this.cancelPendingFlush();
162
- return super.settle();
163
- }
164
66
  }
165
67
  //# sourceMappingURL=run-card-session.js.map