lark-remote 0.1.12 → 0.1.13

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 (62) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/bridge/inbound-media.d.ts +0 -3
  3. package/dist/bridge/inbound-media.js +0 -1
  4. package/dist/bridge/index.d.ts +7 -0
  5. package/dist/bridge/index.js +79 -70
  6. package/dist/card/card-shared.d.ts +1 -4
  7. package/dist/card/card-shared.js +2 -4
  8. package/dist/common/truncate.d.ts +0 -6
  9. package/dist/common/truncate.js +1 -1
  10. package/dist/common/ttl-cache.d.ts +24 -0
  11. package/dist/common/ttl-cache.js +53 -0
  12. package/dist/config/codex-config.js +12 -11
  13. package/dist/config/index.d.ts +0 -3
  14. package/dist/config/index.js +0 -5
  15. package/dist/config/kimi-config.d.ts +1 -1
  16. package/dist/config/kimi-config.js +1 -1
  17. package/dist/config/opencode-config.d.ts +1 -1
  18. package/dist/config/opencode-config.js +3 -8
  19. package/dist/router/index.d.ts +7 -0
  20. package/dist/router/index.js +217 -279
  21. package/dist/runner/claude/session.d.ts +0 -3
  22. package/dist/runner/claude/session.js +0 -5
  23. package/dist/runner/codex/app-server/runner.d.ts +1 -16
  24. package/dist/runner/codex/app-server/runner.js +2 -2
  25. package/dist/runner/common/acp/protocol-helpers.d.ts +31 -0
  26. package/dist/runner/common/acp/protocol-helpers.js +48 -0
  27. package/dist/runner/common/connection-based-runner.d.ts +0 -2
  28. package/dist/runner/common/connection-based-runner.js +1 -1
  29. package/dist/runner/dsh/index.d.ts +0 -2
  30. package/dist/runner/dsh/index.js +0 -2
  31. package/dist/runner/dsh/runner.d.ts +0 -2
  32. package/dist/runner/dsh/runner.js +1 -1
  33. package/dist/runner/kimi/acp/runner.d.ts +18 -11
  34. package/dist/runner/kimi/acp/runner.js +95 -87
  35. package/dist/runner/kimi/acp/translator.d.ts +0 -1
  36. package/dist/runner/kimi/acp/translator.js +5 -13
  37. package/dist/runner/opencode/acp/runner.js +2 -42
  38. package/dist/runner/opencode/acp/translator.d.ts +0 -1
  39. package/dist/runner/opencode/acp/translator.js +3 -13
  40. package/dist/runner/pi/rpc/index.d.ts +0 -2
  41. package/dist/runner/pi/rpc/index.js +0 -2
  42. package/dist/runner/resolve-agent-choices.js +6 -2
  43. package/dist/runner/sync-agent-choices.js +1 -1
  44. package/dist/session/claude/session-index.js +3 -1
  45. package/dist/session/claude/sessions.js +3 -7
  46. package/dist/session/codex/rollout-reader.d.ts +0 -14
  47. package/dist/session/codex/rollout-reader.js +3 -7
  48. package/dist/session/common/pagination.d.ts +0 -2
  49. package/dist/session/common/pagination.js +1 -1
  50. package/dist/session/common/recency.d.ts +7 -0
  51. package/dist/session/common/recency.js +10 -0
  52. package/dist/session/common/usage-accumulator.d.ts +14 -0
  53. package/dist/session/common/usage-accumulator.js +15 -0
  54. package/dist/session/dsh/sessions.js +9 -6
  55. package/dist/session/kimi/sessions.d.ts +18 -4
  56. package/dist/session/kimi/sessions.js +49 -30
  57. package/dist/session/opencode/sessions.d.ts +2 -14
  58. package/dist/session/opencode/sessions.js +9 -7
  59. package/dist/session/pi/sessions.js +10 -22
  60. package/dist/update/index.d.ts +2 -2
  61. package/dist/update/index.js +1 -1
  62. package/package.json +1 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ 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.1.13] - 2026-09-04
9
+
10
+ ### 修复
11
+
12
+ - kimi ACP Compact 等待机制重构:等待协议终态(complete / cancel / unknown)再收尾,移除「30s 超时即视为成功」的假完成路径——Compact 实际失败或被取消时如实反馈
13
+
14
+ ### 变更
15
+
16
+ - 代码与测试清理:死导出/死字段清理、bridge usage 统计收敛、router 样板收敛、session 公共件下沉(`ttl-cache` / `recency`)、ACP 共享层下沉
17
+ - 测试治理:超大测试文件按主题拆分、agent choices 测试表驱动化、弱断言收敛、移除无效测试
18
+ - 移除未使用的 `@types/form-data` 依赖
19
+
8
20
  ## [0.1.12] - 2026-08-29
9
21
 
10
22
  ### 修复
@@ -23,9 +23,6 @@ export declare function sanitizeFileName(name: string): string;
23
23
  * 两者都无法识别时返回 undefined(调用方省略扩展名,避免错误标注格式)。
24
24
  */
25
25
  export declare function imageExtension(mimeType: string | undefined, head: Buffer): string | undefined;
26
- /** 按 UTF-8 字节数截断字符串(避免截在多字节字符中间)。 */
27
- import { truncateUtf8 } from '../common/truncate.js';
28
- export { truncateUtf8 };
29
26
  /**
30
27
  * 文件名长度限制(字节级,保留扩展名),避免 ENAMETOOLONG 整文件失败。
31
28
  * 极罕见情况(扩展名本身超限)直接截断全名。
@@ -99,7 +99,6 @@ export function imageExtension(mimeType, head) {
99
99
  }
100
100
  /** 按 UTF-8 字节数截断字符串(避免截在多字节字符中间)。 */
101
101
  import { truncateUtf8 } from '../common/truncate.js';
102
- export { truncateUtf8 };
103
102
  /**
104
103
  * 文件名长度限制(字节级,保留扩展名),避免 ENAMETOOLONG 整文件失败。
105
104
  * 极罕见情况(扩展名本身超限)直接截断全名。
@@ -463,6 +463,13 @@ export declare class Bridge {
463
463
  handleCodexCompact(value: {
464
464
  runId?: string;
465
465
  }, ctx: BridgeContext): Promise<void>;
466
+ /**
467
+ * §5.3: kimi 压缩在途检测——reader 鸭子实现 `readCompactionState` 时,若
468
+ * wire 状态机判定当前有未终结的压缩(最后一次 begin 无 terminal 记录),
469
+ * 直接回文本「已有一次 Compact 正在进行」并返回 true(调用方不开卡、不发
470
+ * session/prompt)。codex/claude 等 reader 无此方法 → 返回 false,行为不变。
471
+ */
472
+ private rejectIfCompactionInFlight;
466
473
  /**
467
474
  * Stream a compaction to a card: start a RunCardSession with
468
475
  * operationKind='compaction' (no recursive Compact button), consume
@@ -834,6 +834,26 @@ export class Bridge {
834
834
  let finalCumulativeOutputTokens;
835
835
  let finalCumulativeCacheReadTokens;
836
836
  let finalCumulativeCacheCreationTokens;
837
+ // 统一的 usage meta 构造(finalize 各分支共享,本方法内原先 5 处手写 11-15
838
+ // 字段对象;第 6 处 streamCodexCompact 形状不同未收敛)。
839
+ // catch 路径需覆盖 flow 字段时在展开后覆写即可。catch error 路径亦复用此
840
+ // 闭包,相比旧手写对象补齐了累计 cache 字段(cumulativeCacheReadTokens/
841
+ // cumulativeCacheCreationTokens),属有意的口径对齐(向 done 路径看齐)。
842
+ const usageMeta = () => ({
843
+ contextLength: finalContextLength,
844
+ contextLimit: finalContextLimit,
845
+ compactCount: finalCompactCount,
846
+ cacheReadTokens: finalCacheReadTokens,
847
+ cacheCreationTokens: finalCacheCreationTokens,
848
+ totalTokens: finalTotalTokens,
849
+ inputTokens: finalInputTokens,
850
+ outputTokens: finalOutputTokens,
851
+ cumulativeTotalTokens: finalCumulativeTotalTokens,
852
+ cumulativeInputTokens: finalCumulativeInputTokens,
853
+ cumulativeOutputTokens: finalCumulativeOutputTokens,
854
+ cumulativeCacheReadTokens: finalCumulativeCacheReadTokens,
855
+ cumulativeCacheCreationTokens: finalCumulativeCacheCreationTokens,
856
+ });
837
857
  try {
838
858
  try {
839
859
  getLogger().debug(`[bridge] cardSession.start() begin runId=${runId}`);
@@ -1156,57 +1176,19 @@ export class Bridge {
1156
1176
  await cardSession.finish(terminal, {
1157
1177
  resultSubtype: subtype,
1158
1178
  errorMsg: finalState.errorMsg,
1159
- contextLength: finalContextLength,
1160
- contextLimit: finalContextLimit,
1161
- compactCount: finalCompactCount,
1162
- cacheReadTokens: finalCacheReadTokens,
1163
- cacheCreationTokens: finalCacheCreationTokens,
1164
- totalTokens: finalTotalTokens,
1165
- inputTokens: finalInputTokens,
1166
- outputTokens: finalOutputTokens,
1167
- cumulativeTotalTokens: finalCumulativeTotalTokens,
1168
- cumulativeInputTokens: finalCumulativeInputTokens,
1169
- cumulativeOutputTokens: finalCumulativeOutputTokens,
1170
- cumulativeCacheReadTokens: finalCumulativeCacheReadTokens,
1171
- cumulativeCacheCreationTokens: finalCumulativeCacheCreationTokens,
1179
+ ...usageMeta(),
1172
1180
  });
1173
1181
  }
1174
1182
  else if (finalState.terminal === 'running') {
1175
1183
  await cardSession.finish('error', {
1176
- contextLength: finalContextLength,
1177
- contextLimit: finalContextLimit,
1178
- compactCount: finalCompactCount,
1179
- cacheReadTokens: finalCacheReadTokens,
1180
- cacheCreationTokens: finalCacheCreationTokens,
1181
- totalTokens: finalTotalTokens,
1182
- inputTokens: finalInputTokens,
1183
- outputTokens: finalOutputTokens,
1184
- cumulativeTotalTokens: finalCumulativeTotalTokens,
1185
- cumulativeInputTokens: finalCumulativeInputTokens,
1186
- cumulativeOutputTokens: finalCumulativeOutputTokens,
1187
- cumulativeCacheReadTokens: finalCumulativeCacheReadTokens,
1188
- cumulativeCacheCreationTokens: finalCumulativeCacheCreationTokens,
1184
+ ...usageMeta(),
1189
1185
  errorMsg: agentDisplayName(agentKind) + ' 输出流已结束,但未收到 result 事件',
1190
1186
  });
1191
1187
  }
1192
1188
  else if (sawResult) {
1193
1189
  // 已终态(interrupted/idle_timeout)+ result 已收到:首终态优先,
1194
1190
  // 仅补充 usage meta(finalizing 期间被 /stop 或 idle 超时也展示 token 统计)
1195
- await cardSession.finish(finalState.terminal, {
1196
- contextLength: finalContextLength,
1197
- contextLimit: finalContextLimit,
1198
- compactCount: finalCompactCount,
1199
- cacheReadTokens: finalCacheReadTokens,
1200
- cacheCreationTokens: finalCacheCreationTokens,
1201
- totalTokens: finalTotalTokens,
1202
- inputTokens: finalInputTokens,
1203
- outputTokens: finalOutputTokens,
1204
- cumulativeTotalTokens: finalCumulativeTotalTokens,
1205
- cumulativeInputTokens: finalCumulativeInputTokens,
1206
- cumulativeOutputTokens: finalCumulativeOutputTokens,
1207
- cumulativeCacheReadTokens: finalCumulativeCacheReadTokens,
1208
- cumulativeCacheCreationTokens: finalCumulativeCacheCreationTokens,
1209
- });
1191
+ await cardSession.finish(finalState.terminal, usageMeta());
1210
1192
  }
1211
1193
  // else: 已终态且未收到 result(如 spawn 失败后 interrupted)-> 无 usage 可补,跳过
1212
1194
  }
@@ -1225,38 +1207,16 @@ export class Bridge {
1225
1207
  const catchTerminal = cardSession.currentState.terminal;
1226
1208
  if (catchTerminal === 'running' || catchTerminal === 'finalizing') {
1227
1209
  await cardSession.finish('error', {
1228
- contextLength: finalContextLength,
1229
- contextLimit: finalContextLimit,
1230
- compactCount: finalCompactCount,
1231
- cacheReadTokens: finalCacheReadTokens,
1232
- cacheCreationTokens: finalCacheCreationTokens,
1233
- totalTokens: finalTotalTokens,
1210
+ ...usageMeta(),
1234
1211
  inputTokens: liveInputTokens,
1235
1212
  outputTokens: liveOutputTokens,
1236
- cumulativeTotalTokens: finalCumulativeTotalTokens,
1237
- cumulativeInputTokens: finalCumulativeInputTokens,
1238
- cumulativeOutputTokens: finalCumulativeOutputTokens,
1239
1213
  errorMsg: errorMessage(err),
1240
1214
  });
1241
1215
  }
1242
1216
  else {
1243
1217
  getLogger().info(`[bridge] skip error finish: state already terminal (${catchTerminal}) runId=${runId}`);
1244
1218
  if (sawResult) {
1245
- await cardSession.finish(catchTerminal, {
1246
- contextLength: finalContextLength,
1247
- contextLimit: finalContextLimit,
1248
- compactCount: finalCompactCount,
1249
- cacheReadTokens: finalCacheReadTokens,
1250
- cacheCreationTokens: finalCacheCreationTokens,
1251
- totalTokens: finalTotalTokens,
1252
- inputTokens: finalInputTokens,
1253
- outputTokens: finalOutputTokens,
1254
- cumulativeTotalTokens: finalCumulativeTotalTokens,
1255
- cumulativeInputTokens: finalCumulativeInputTokens,
1256
- cumulativeOutputTokens: finalCumulativeOutputTokens,
1257
- cumulativeCacheReadTokens: finalCumulativeCacheReadTokens,
1258
- cumulativeCacheCreationTokens: finalCumulativeCacheCreationTokens,
1259
- });
1219
+ await cardSession.finish(catchTerminal, usageMeta());
1260
1220
  }
1261
1221
  }
1262
1222
  }
@@ -1562,6 +1522,10 @@ export class Bridge {
1562
1522
  await this.sendResult({ text: '⚠️ 未找到可压缩的会话' }, ctx);
1563
1523
  return;
1564
1524
  }
1525
+ // §5.3: kimi 压缩在途检测(wire 状态机)。第二次点击在本地毫秒级如实回答,
1526
+ // 不开卡、不发 session/prompt——否则 kimi 服务端拒绝并产生「秒回」假完成。
1527
+ if (await this.rejectIfCompactionInFlight(last.sessionId, cwd, last.agentKind, ctx))
1528
+ return;
1565
1529
  // Check that the runner has runCompact method
1566
1530
  const runner = this.getRunner(cwd, last.agentKind);
1567
1531
  if (!('runCompact' in runner) ||
@@ -1578,6 +1542,36 @@ export class Bridge {
1578
1542
  ctx,
1579
1543
  });
1580
1544
  }
1545
+ /**
1546
+ * §5.3: kimi 压缩在途检测——reader 鸭子实现 `readCompactionState` 时,若
1547
+ * wire 状态机判定当前有未终结的压缩(最后一次 begin 无 terminal 记录),
1548
+ * 直接回文本「已有一次 Compact 正在进行」并返回 true(调用方不开卡、不发
1549
+ * session/prompt)。codex/claude 等 reader 无此方法 → 返回 false,行为不变。
1550
+ */
1551
+ async rejectIfCompactionInFlight(sessionId, cwd, agentKind, ctx) {
1552
+ const reader = this.sessionReaderRegistry.get(agentKind);
1553
+ if (!reader || !('readCompactionState' in reader))
1554
+ return false;
1555
+ try {
1556
+ const state = reader.readCompactionState(sessionId, cwd);
1557
+ if (state?.inFlight) {
1558
+ const since = state.inFlightSince;
1559
+ let timeLabel = '未知时刻';
1560
+ if (since !== undefined) {
1561
+ const d = new Date(since);
1562
+ const hh = String(d.getHours()).padStart(2, '0');
1563
+ const mm = String(d.getMinutes()).padStart(2, '0');
1564
+ timeLabel = `${hh}:${mm}`;
1565
+ }
1566
+ await this.sendResult({ text: `⚠️ 已有一次 Compact 正在进行(开始于 ${timeLabel}),请等待完成` }, ctx);
1567
+ return true;
1568
+ }
1569
+ }
1570
+ catch (err) {
1571
+ getLogger().warn(`[bridge] readCompactionState failed sessionId=${sessionId}: ${errorMessage(err)}`);
1572
+ }
1573
+ return false;
1574
+ }
1581
1575
  /**
1582
1576
  * Stream a compaction to a card: start a RunCardSession with
1583
1577
  * operationKind='compaction' (no recursive Compact button), consume
@@ -1623,11 +1617,23 @@ export class Bridge {
1623
1617
  operationKind: 'compaction',
1624
1618
  });
1625
1619
  let sawResult = false;
1620
+ let resultSubtype;
1621
+ let resultError;
1626
1622
  for await (const event of runner.runCompact('', { cwd, sessionId })) {
1627
- if (event.type === 'result')
1623
+ if (event.type === 'result') {
1628
1624
  sawResult = true;
1625
+ resultSubtype = event.subtype;
1626
+ resultError = event.errorMessage;
1627
+ }
1629
1628
  await cardSession.push(event);
1630
1629
  }
1630
+ // §5.3: 卡片终态必须等于引擎真实终态。旧实现只看 sawResult 布尔——error
1631
+ // subtype 的 result 也被 finish 'done'(放大器 bug,2026-08-31 事故)。
1632
+ const terminal = resultSubtype === 'success'
1633
+ ? 'done'
1634
+ : resultSubtype === 'interrupted'
1635
+ ? 'interrupted'
1636
+ : 'error';
1631
1637
  // 压缩结束后从会话 jsonl 读权威统计(压缩后上下文、压缩次数、本次压缩
1632
1638
  // token 消耗与会话累计)。thread/compacted 通知与 jsonl 落盘几乎同时
1633
1639
  // (实测差 ~9ms),compactCount 未读到则短重试,仍失败优雅降级(无统计)。
@@ -1636,9 +1642,9 @@ export class Bridge {
1636
1642
  await sleep(150);
1637
1643
  finalUsage = this.resolveFinalUsage(sessionId, cwd, agentKind);
1638
1644
  }
1639
- await cardSession.finish(sawResult ? 'done' : 'error', {
1640
- resultSubtype: sawResult ? 'success' : 'error',
1641
- errorMsg: sawResult ? undefined : '未收到压缩结果',
1645
+ await cardSession.finish(terminal, {
1646
+ resultSubtype: resultSubtype ?? 'error',
1647
+ errorMsg: terminal === 'error' ? (resultError ?? '未收到压缩结果') : undefined,
1642
1648
  contextLength: finalUsage?.contextLength,
1643
1649
  contextLimit: finalUsage?.contextLimit,
1644
1650
  compactPreContextLength: finalUsage?.compactPreContextLength,
@@ -1654,7 +1660,7 @@ export class Bridge {
1654
1660
  cumulativeCacheReadTokens: finalUsage?.cumulativeCacheReadTokens,
1655
1661
  cumulativeCacheCreationTokens: finalUsage?.cumulativeCacheCreationTokens,
1656
1662
  });
1657
- log.info(`[bridge] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult}`);
1663
+ log.info(`[bridge] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult} subtype=${resultSubtype ?? 'none'}`);
1658
1664
  }
1659
1665
  catch (err) {
1660
1666
  log.error(`[bridge] streamCodexCompact failed: ${errorMessage(err)}`);
@@ -1709,6 +1715,9 @@ export class Bridge {
1709
1715
  await this.sendResult({ text: `⚠️ 未找到 session ${sessionId}(当前目录: ${cwd})` }, ctx);
1710
1716
  return;
1711
1717
  }
1718
+ // §5.3: kimi 压缩在途检测(与 handleCodexCompact 同一入口)。
1719
+ if (await this.rejectIfCompactionInFlight(sessionId, cwd, agentKind, ctx))
1720
+ return;
1712
1721
  // Check that the runner has runCompact(codex/kimi/opencode/pi/claude 鸭子探测)。
1713
1722
  const runner = this.getRunner(cwd, agentKind);
1714
1723
  if (!('runCompact' in runner) ||
@@ -8,10 +8,7 @@ import { type AgentKind } from '../runner/index.js';
8
8
  */
9
9
  export declare function agentDisplayName(kind: string): string;
10
10
  export { truncate } from '../common/truncate.js';
11
- interface TerminalLabelOptions {
12
- prefix?: string;
13
- }
14
- export declare function terminalToLabel(terminal: string, options?: TerminalLabelOptions): string;
11
+ export declare function terminalToLabel(terminal: string): string;
15
12
  export declare function terminalToColor(terminal: string): string;
16
13
  /**
17
14
  * Create a CardKit 2.0 stop button with behaviors callback.
@@ -34,10 +34,8 @@ const TERMINAL_LABELS = {
34
34
  running: '运行中',
35
35
  finalizing: '完成中',
36
36
  };
37
- export function terminalToLabel(terminal, options) {
38
- const label = TERMINAL_LABELS[terminal] ?? '运行中';
39
- const prefix = options?.prefix ?? '';
40
- return prefix + label;
37
+ export function terminalToLabel(terminal) {
38
+ return TERMINAL_LABELS[terminal] ?? '运行中';
41
39
  }
42
40
  export function terminalToColor(terminal) {
43
41
  if (terminal === 'done')
@@ -16,12 +16,6 @@ export declare const DEFAULT_TRUNCATE_SUFFIX = "\u2026\uFF08\u5DF2\u622A\u65AD\u
16
16
  * pathological tool_result never inflates the replay payload to megabytes.
17
17
  */
18
18
  export declare const TOOL_RESULT_MAX_BYTES = 4000;
19
- /**
20
- * Fit a UTF-8 string into `maxBytes` by iterating Unicode codepoints, never
21
- * splitting a multi-byte sequence. When `fromEnd` is true, keeps the TAIL
22
- * (useful for logs/tails); otherwise keeps the HEAD.
23
- */
24
- export declare function fitUtf8(value: string, maxBytes: number, fromEnd?: boolean): string;
25
19
  /**
26
20
  * Truncate a UTF-8 string to fit within `maxBytes`, appending `suffix`
27
21
  * (default `…(已截断)`) when truncation occurs. When `fromEnd` is true, keeps
@@ -21,7 +21,7 @@ export const TOOL_RESULT_MAX_BYTES = 4000;
21
21
  * splitting a multi-byte sequence. When `fromEnd` is true, keeps the TAIL
22
22
  * (useful for logs/tails); otherwise keeps the HEAD.
23
23
  */
24
- export function fitUtf8(value, maxBytes, fromEnd = false) {
24
+ function fitUtf8(value, maxBytes, fromEnd = false) {
25
25
  if (fromEnd) {
26
26
  const codepoints = Array.from(value);
27
27
  let bytes = 0;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Generic TTL + bounded LRU cache.
3
+ *
4
+ * Consolidates the per-reader memory caches that previously hand-rolled the
5
+ * same "mtime/TTL + entry cap" Map pattern (kimi/pi/opencode session readers).
6
+ * Semantics follow the kimi implementation:
7
+ * - TTL is measured from the cache-write time (P1-17 defect 2), not key mtime.
8
+ * Note: ttlMs = 0 effectively disables caching — `Date.now() - at >= 0` is
9
+ * always true, so every entry expires immediately.
10
+ * - A hit refreshes recency (LRU); eviction removes the least recently
11
+ * inserted/used entry past `maxEntries` (P1-17 defect 1: unbounded growth).
12
+ * The old pi implementation did NOT re-insert on hit (FIFO eviction); the
13
+ * eviction order for pi therefore changes from FIFO to LRU here.
14
+ */
15
+ export declare class TtlCache<K, V> {
16
+ private readonly ttlMs;
17
+ private readonly maxEntries?;
18
+ private map;
19
+ constructor(ttlMs: number, maxEntries?: number | undefined);
20
+ /** Return the fresh value for `key` (refreshing LRU order), or undefined. */
21
+ get(key: K): V | undefined;
22
+ set(key: K, value: V): void;
23
+ clear(): void;
24
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Generic TTL + bounded LRU cache.
3
+ *
4
+ * Consolidates the per-reader memory caches that previously hand-rolled the
5
+ * same "mtime/TTL + entry cap" Map pattern (kimi/pi/opencode session readers).
6
+ * Semantics follow the kimi implementation:
7
+ * - TTL is measured from the cache-write time (P1-17 defect 2), not key mtime.
8
+ * Note: ttlMs = 0 effectively disables caching — `Date.now() - at >= 0` is
9
+ * always true, so every entry expires immediately.
10
+ * - A hit refreshes recency (LRU); eviction removes the least recently
11
+ * inserted/used entry past `maxEntries` (P1-17 defect 1: unbounded growth).
12
+ * The old pi implementation did NOT re-insert on hit (FIFO eviction); the
13
+ * eviction order for pi therefore changes from FIFO to LRU here.
14
+ */
15
+ export class TtlCache {
16
+ ttlMs;
17
+ maxEntries;
18
+ map = new Map();
19
+ constructor(ttlMs, maxEntries) {
20
+ this.ttlMs = ttlMs;
21
+ this.maxEntries = maxEntries;
22
+ }
23
+ /** Return the fresh value for `key` (refreshing LRU order), or undefined. */
24
+ get(key) {
25
+ const entry = this.map.get(key);
26
+ if (!entry)
27
+ return undefined;
28
+ if (Date.now() - entry.at >= this.ttlMs) {
29
+ this.map.delete(key);
30
+ return undefined;
31
+ }
32
+ // LRU refresh: re-insert to move to the tail of iteration order.
33
+ this.map.delete(key);
34
+ this.map.set(key, entry);
35
+ return entry.value;
36
+ }
37
+ set(key, value) {
38
+ this.map.delete(key);
39
+ this.map.set(key, { at: Date.now(), value });
40
+ if (this.maxEntries !== undefined) {
41
+ while (this.map.size > this.maxEntries) {
42
+ const oldest = this.map.keys().next().value;
43
+ if (oldest === undefined)
44
+ break;
45
+ this.map.delete(oldest);
46
+ }
47
+ }
48
+ }
49
+ clear() {
50
+ this.map.clear();
51
+ }
52
+ }
53
+ //# sourceMappingURL=ttl-cache.js.map
@@ -31,16 +31,13 @@ export function resolveCodexHome(codexHome) {
31
31
  * 配置 [model_providers.anthropic] 才存在,fallback 阶段不得虚构。
32
32
  */
33
33
  const FALLBACK_PROVIDERS = ['openai'];
34
- const FALLBACK_MODELS = [
35
- 'o3',
36
- 'o4-mini',
37
- 'gpt-4o',
38
- 'gpt-4.1',
39
- 'gpt-4.1-mini',
40
- 'gpt-4.1-nano',
41
- 'claude-sonnet-4-20250514',
42
- 'claude-opus-4-20250115',
43
- ];
34
+ const FALLBACK_MODELS = ['o3', 'o4-mini', 'gpt-4o', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano'];
35
+ /**
36
+ * anthropic 的独立兜底模型表:anthropic 非内置 provider,仅在用户显式配置后使用。
37
+ * 不得混入 FALLBACK_MODELS——无 config.toml 时 openai 的模型列表会原样暴露这些
38
+ * claude 条目(claude 模型列在 openai provider 下是错的)。
39
+ */
40
+ const ANTHROPIC_FALLBACK_MODELS = ['claude-sonnet-4-20250514', 'claude-opus-4-20250115'];
44
41
  /** Bundled models from codex binary, 1h TTL is enough (only change on binary upgrade) */
45
42
  const BUNDLED_CACHE_TTL_MS = 60 * 60 * 1000;
46
43
  /** 失败/空结果负缓存 TTL:避免每次卡片构建同步阻塞最长 8s(P3-5) */
@@ -351,7 +348,11 @@ export function loadCodexConfig(opts = {}) {
351
348
  // 否则 = bundled(与原行为一致)
352
349
  const catalogSlugs = getCodexCatalogModels(codexHome).map((m) => m.slug);
353
350
  const allModels = catalogSlugs.length > 0 ? catalogSlugs : FALLBACK_MODELS;
354
- const anthropicModels = allModels.filter((m) => m.startsWith('claude-'));
351
+ // anthropic 模型列表仅在用户显式配置 anthropic 时使用:catalog 可用取 catalog 中的
352
+ // claude-*,否则用 ANTHROPIC_FALLBACK_MODELS(与 openai 兜底表隔离)
353
+ const anthropicModels = catalogSlugs.length > 0
354
+ ? catalogSlugs.filter((m) => m.startsWith('claude-'))
355
+ : ANTHROPIC_FALLBACK_MODELS;
355
356
  if (!fs.existsSync(configFile)) {
356
357
  // 无 config.toml:默认模型取目录首个可用模型(codex default_model_from_available),
357
358
  // 目录不可用时回退 'o3'(FALLBACK_MODELS 首项,非 catalog 默认)
@@ -237,9 +237,6 @@ export declare const AppConfigSchema: z.ZodObject<{
237
237
  model: z.ZodOptional<z.ZodString>;
238
238
  thinkingEffort: z.ZodOptional<z.ZodString>;
239
239
  }, z.core.$strip>>;
240
- dsh: z.ZodOptional<z.ZodObject<{
241
- host: z.ZodOptional<z.ZodString>;
242
- }, z.core.$strip>>;
243
240
  }, z.core.$strip>>;
244
241
  idle: z.ZodDefault<z.ZodObject<{
245
242
  watchdogMinutes: z.ZodDefault<z.ZodNumber>;
@@ -246,11 +246,6 @@ const AgentChoicesSchema = z.object({
246
246
  thinkingEffort: z.string().optional(),
247
247
  })
248
248
  .optional(),
249
- dsh: z
250
- .object({
251
- host: z.string().optional(),
252
- })
253
- .optional(),
254
249
  });
255
250
  export const AppConfigSchema = z.object({
256
251
  feishu: FeishuConfigSchema,
@@ -22,7 +22,7 @@ interface KimiConfigResult {
22
22
  /** Model -> default effort mapping */
23
23
  modelDefaultEfforts: Record<string, string>;
24
24
  }
25
- /** 清空缓存(测试与配置热更新用,与 codex/opencode 的 invalidate 对齐) */
25
+ /** 清空缓存(仅供测试使用,生产代码无调用点) */
26
26
  export declare function invalidateKimiConfigCache(): void;
27
27
  /**
28
28
  * Load Kimi models from `kimi provider list --json`.
@@ -10,7 +10,7 @@ import { getLogger } from '../logger/index.js';
10
10
  export const KIMI_THINKING_EFFORTS = ['low', 'high', 'max'];
11
11
  /** Fallback efforts when all values are filtered out */
12
12
  export const FALLBACK_EFFORTS = ['low', 'high', 'max'];
13
- /** 清空缓存(测试与配置热更新用,与 codex/opencode 的 invalidate 对齐) */
13
+ /** 清空缓存(仅供测试使用,生产代码无调用点) */
14
14
  export function invalidateKimiConfigCache() {
15
15
  kimiConfigCache = null;
16
16
  }
@@ -9,7 +9,7 @@ interface OpencodeConfigResult {
9
9
  /** 根据 provider 获取可用模型列表(返回纯 model ID,不带 provider 前缀) */
10
10
  modelOptions: (provider?: string) => string[];
11
11
  }
12
- /** 清空缓存(测试与配置热更新用,与 codex-config 的 invalidateCodexBundledCache 对齐) */
12
+ /** 清空缓存(仅供测试使用,生产代码无调用点) */
13
13
  export declare function invalidateOpencodeConfigCache(): void;
14
14
  /**
15
15
  * 加载 opencode 配置,执行 opencode models --verbose 并解析
@@ -10,18 +10,13 @@ import { getLogger } from '../logger/index.js';
10
10
  * 当 opencode 命令失败时使用
11
11
  * 格式:provider/model(内部用,modelOptions 返回时会去掉 provider 前缀)
12
12
  */
13
- const FALLBACK_PROVIDERS = [
14
- 'opencode',
15
- 'deepseek',
16
- 'minimax-cn-coding-plan',
17
- 'myprovider',
18
- 'volcengine-plan',
19
- ];
20
13
  const FALLBACK_MODELS = {
21
14
  opencode: ['big-pickle'],
22
15
  deepseek: ['deepseek-chat'],
23
16
  'minimax-cn-coding-plan': ['MiniMax-M2.5'],
24
17
  };
18
+ /** fallback provider 列表从模型表派生,保证不会出现空模型列表的 provider(历史曾混入无模型的 myprovider/volcengine-plan) */
19
+ const FALLBACK_PROVIDERS = Object.keys(FALLBACK_MODELS);
25
20
  /** 正缓存 TTL:opencode models 列表短时间不变,1 分钟(与 kimi-config 对齐) */
26
21
  const OPENCODE_CONFIG_CACHE_TTL_MS = 60_000;
27
22
  /**
@@ -31,7 +26,7 @@ const OPENCODE_CONFIG_CACHE_TTL_MS = 60_000;
31
26
  const OPENCODE_NEGATIVE_CACHE_TTL_MS = 30_000;
32
27
  let opencodeConfigCache = null;
33
28
  let opencodeConfigFailedAt = null;
34
- /** 清空缓存(测试与配置热更新用,与 codex-config 的 invalidateCodexBundledCache 对齐) */
29
+ /** 清空缓存(仅供测试使用,生产代码无调用点) */
35
30
  export function invalidateOpencodeConfigCache() {
36
31
  opencodeConfigCache = null;
37
32
  opencodeConfigFailedAt = null;
@@ -203,6 +203,8 @@ export declare class CommandRouter {
203
203
  * Routes to the appropriate bridge method based on value.cmd.
204
204
  * Returns a toast response for immediate user feedback.
205
205
  */
206
+ /** queue.* 卡片动作通用守卫:workspace/messageId 缺失时回复错误并返回 null。 */
207
+ private queuePayloadOrReply;
206
208
  /** 答案类操作的统一失败反馈:重复投递(同一 nonce 第二次点击)是已生效的
207
209
  * 中性事件,报 error 会误导(首次点击实际已成功)。 */
208
210
  private answerFailureToast;
@@ -300,6 +302,11 @@ export declare class CommandRouter {
300
302
  * Handle order.aliasEdit: 展示别名编辑卡(input + ✓ 提交图标)。
301
303
  * 有别名时预填;提交 `order.aliasInput` 处理(含校验与唯一性)。
302
304
  */
305
+ /**
306
+ * order 编辑卡公共骨架:payload 校验 → 查指令 → 构造 input 编辑卡 → 原地更新。
307
+ * handleOrderAliasEdit / handleOrderTextEdit 原先各持一份 ~90% 相同的 4 段结构。
308
+ */
309
+ private showOrderEditCard;
303
310
  private handleOrderAliasEdit;
304
311
  /**
305
312
  * Handle order.aliasInput: 提交别名(绑/改/解绑),随后重渲染 /order 列表卡。