lark-remote 0.1.11 → 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.
- package/CHANGELOG.md +23 -0
- package/dist/bridge/inbound-media.d.ts +0 -3
- package/dist/bridge/inbound-media.js +0 -1
- package/dist/bridge/index.d.ts +7 -0
- package/dist/bridge/index.js +79 -70
- package/dist/card/card-shared.d.ts +1 -4
- package/dist/card/card-shared.js +2 -4
- package/dist/common/truncate.d.ts +0 -6
- package/dist/common/truncate.js +1 -1
- package/dist/common/ttl-cache.d.ts +24 -0
- package/dist/common/ttl-cache.js +53 -0
- package/dist/config/codex-config.d.ts +22 -0
- package/dist/config/codex-config.js +120 -25
- package/dist/config/index.d.ts +0 -3
- package/dist/config/index.js +0 -5
- package/dist/config/kimi-config.d.ts +1 -1
- package/dist/config/kimi-config.js +1 -1
- package/dist/config/opencode-config.d.ts +1 -1
- package/dist/config/opencode-config.js +3 -8
- package/dist/index.js +6 -0
- package/dist/router/index.d.ts +7 -0
- package/dist/router/index.js +229 -284
- package/dist/runner/claude/session.d.ts +0 -3
- package/dist/runner/claude/session.js +0 -5
- package/dist/runner/codex/app-server/runner.d.ts +1 -16
- package/dist/runner/codex/app-server/runner.js +2 -2
- package/dist/runner/common/acp/protocol-helpers.d.ts +31 -0
- package/dist/runner/common/acp/protocol-helpers.js +48 -0
- package/dist/runner/common/connection-based-runner.d.ts +0 -2
- package/dist/runner/common/connection-based-runner.js +1 -1
- package/dist/runner/dsh/index.d.ts +0 -2
- package/dist/runner/dsh/index.js +0 -2
- package/dist/runner/dsh/runner.d.ts +0 -2
- package/dist/runner/dsh/runner.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +18 -11
- package/dist/runner/kimi/acp/runner.js +95 -87
- package/dist/runner/kimi/acp/translator.d.ts +0 -1
- package/dist/runner/kimi/acp/translator.js +5 -13
- package/dist/runner/opencode/acp/error-monitor.d.ts +61 -0
- package/dist/runner/opencode/acp/error-monitor.js +150 -0
- package/dist/runner/opencode/acp/runner.d.ts +17 -0
- package/dist/runner/opencode/acp/runner.js +28 -42
- package/dist/runner/opencode/acp/translator.d.ts +0 -1
- package/dist/runner/opencode/acp/translator.js +3 -13
- package/dist/runner/pi/rpc/index.d.ts +0 -2
- package/dist/runner/pi/rpc/index.js +0 -2
- package/dist/runner/resolve-agent-choices.js +6 -2
- package/dist/runner/sync-agent-choices.js +1 -1
- package/dist/runner/types.d.ts +23 -0
- package/dist/session/claude/session-index.js +3 -1
- package/dist/session/claude/sessions.js +3 -7
- package/dist/session/codex/rollout-reader.d.ts +12 -2
- package/dist/session/codex/rollout-reader.js +259 -173
- package/dist/session/codex/sessions.d.ts +2 -1
- package/dist/session/codex/sessions.js +4 -1
- package/dist/session/common/pagination.d.ts +0 -2
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/common/recency.d.ts +7 -0
- package/dist/session/common/recency.js +10 -0
- package/dist/session/common/usage-accumulator.d.ts +14 -0
- package/dist/session/common/usage-accumulator.js +15 -0
- package/dist/session/dsh/sessions.js +9 -6
- package/dist/session/kimi/sessions.d.ts +18 -4
- package/dist/session/kimi/sessions.js +49 -30
- package/dist/session/opencode/sessions.d.ts +2 -14
- package/dist/session/opencode/sessions.js +9 -7
- package/dist/session/pi/sessions.js +10 -22
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +1 -1
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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
|
+
|
|
20
|
+
## [0.1.12] - 2026-08-29
|
|
21
|
+
|
|
22
|
+
### 修复
|
|
23
|
+
|
|
24
|
+
- opencode ACP stream error 实时上报:tail opencode 日志即时捕获错误行,配额类错误秒级报错,不再挂起卡片等待超时
|
|
25
|
+
|
|
26
|
+
### 变更
|
|
27
|
+
|
|
28
|
+
- codex 会话历史读取轻量化:长会话生成轻量摘要,模型目录异步加载,减少启动阻塞
|
|
29
|
+
- CI 覆盖率门禁纳入 `src/index.ts`,覆盖率统计更完整
|
|
30
|
+
|
|
8
31
|
## [0.1.11] - 2026-08-24
|
|
9
32
|
|
|
10
33
|
### 新增
|
|
@@ -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
|
* 极罕见情况(扩展名本身超限)直接截断全名。
|
package/dist/bridge/index.d.ts
CHANGED
|
@@ -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
|
package/dist/bridge/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1640
|
-
resultSubtype:
|
|
1641
|
-
errorMsg:
|
|
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
|
-
|
|
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.
|
package/dist/card/card-shared.js
CHANGED
|
@@ -34,10 +34,8 @@ const TERMINAL_LABELS = {
|
|
|
34
34
|
running: '运行中',
|
|
35
35
|
finalizing: '完成中',
|
|
36
36
|
};
|
|
37
|
-
export function terminalToLabel(terminal
|
|
38
|
-
|
|
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
|
package/dist/common/truncate.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -75,14 +75,36 @@ export declare function parseCodexModelsOutput(stdout: string): BundledModelInfo
|
|
|
75
75
|
* 缓存:键含 binary/home/mode/models.json mtime:size + config.toml mtime:size
|
|
76
76
|
* (P3-4/P3-15/P3-5);成功结果 TTL 1h,失败/空结果短 TTL 负缓存;stat 异常时
|
|
77
77
|
* 指纹为空(P1-1/P3-10)。
|
|
78
|
+
*
|
|
79
|
+
* 同步路径(卡片构建用):缓存命中直接返回;若有一个后台异步加载
|
|
80
|
+
* (`loadCodexCatalogModelsAsync` / 启动 warm)在途,则不阻塞事件循环、不重复
|
|
81
|
+
* spawn,直接返回 [] 走既有 fallback(FALLBACK_MODELS / currentModel)——该
|
|
82
|
+
* [] 状态与命令失败/超时的已有语义一致,由调用方既有 fallback 兜底。
|
|
78
83
|
*/
|
|
79
84
|
export declare function getCodexCatalogModels(codexHome?: string): BundledModelInfo[];
|
|
85
|
+
/**
|
|
86
|
+
* 异步加载模型目录(execFile,不阻塞事件循环),写入与同步路径共享的
|
|
87
|
+
* `catalogCache`。按缓存 key 复用 in-flight Promise:并发调用(多个 warm /
|
|
88
|
+
* 显式调用)只 spawn 一次 `codex debug models`。
|
|
89
|
+
*
|
|
90
|
+
* 与同步路径的职责划分:启动时用 `warmCodexCatalogCache` 后台预热,使首次打开
|
|
91
|
+
* Codex 配置卡(同步读)时缓存已温、无需 8s execFileSync 阻塞;若用户在 warm
|
|
92
|
+
* 完成前打开卡片,同步路径会检测到 in-flight 并直接返回 [] fallback。
|
|
93
|
+
*/
|
|
94
|
+
export declare function loadCodexCatalogModelsAsync(codexHome?: string): Promise<BundledModelInfo[]>;
|
|
95
|
+
/**
|
|
96
|
+
* 启动/后台预热:fire-and-forget 调用异步加载,消除首次打开 Codex 配置卡时的
|
|
97
|
+
* 8s 同步阻塞。非致命——失败/未安装 codex 时静默落负缓存。
|
|
98
|
+
*/
|
|
99
|
+
export declare function warmCodexCatalogCache(codexHome?: string): void;
|
|
80
100
|
/**
|
|
81
101
|
* Test utility: clear the catalog cache. Tests that exercise the bundled
|
|
82
102
|
* command path (`--bundled`) directly use `invalidateCodexBundledTestCache`
|
|
83
103
|
* from `tests/lib/codex-bundled-test-helpers.ts`.
|
|
84
104
|
*/
|
|
85
105
|
export declare function invalidateCodexBundledCache(): void;
|
|
106
|
+
/** Test utility: clear in-flight async catalog loads (see async loader tests). */
|
|
107
|
+
export declare function _clearCodexCatalogInFlightForTest(): void;
|
|
86
108
|
/**
|
|
87
109
|
* Get supported reasoning effort options for a model.
|
|
88
110
|
* @param codexHome 可选 codex 配置目录覆盖(与 loadCodexConfig 的 codexHome 对齐,
|