lark-remote 0.5.1 → 0.5.2
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
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.5.2] - 2026-10-11
|
|
9
|
+
|
|
10
|
+
### 修复
|
|
11
|
+
|
|
12
|
+
- **codex 运行结束卡片的 token 统计不再翻倍**:codex app-server 实时上报的输入 token 按协议口径已包含命中缓存的部分,而卡片契约要求它表示「未命中缓存的输入」。入口没做减法时,Input 会等于整个上下文用量、缓存占比的分母被重复计数、Total 再被「总量与分项之和取大」抬高一倍(实测同一轮 36 万显示成 72 万)。现在实时通知与 jsonl 兜底读取两条入口统一减掉缓存部分
|
|
13
|
+
- **压缩卡片的「压缩前」不再显示摘要调用自身的用量**:codex 把生成摘要那次调用自己的 token 记录写在压缩事件之前,此前「压缩前」取的是紧贴压缩事件的那一条,于是把摘要调用的消耗当成压缩前的上下文窗口(实测 36 万的窗口显示成 20 万)。现在按压缩事件自带的摘要调用用量回退一条,取压缩前最后一次真实窗口;更早的、不带该字段的数据保持原读法
|
|
14
|
+
- **排队卡片不再复述「你正在排队」**:占用执行位的是普通消息时,卡片仍写着「正在执行: 💬 消息」并附一句「消息将按顺序执行…」,这两行对用户没有新信息。现在只有可能挡住队列、需要用户归因的任务(Compact、命令)才自报身份;Compact 占用时仍额外说明压缩结束后会按顺序执行你的消息
|
|
15
|
+
|
|
16
|
+
### 变更
|
|
17
|
+
|
|
18
|
+
- **消除一处全量测试并发下的时序假红**:bash 退出清理用例在收到首个事件后立刻读取 shell 自己写入的 pid 文件,而重定向落盘可能晚于该事件,整套测试并发跑时会随机报文件不存在。改为与同一用例里另一处 pid 文件相同的有界等待。无行为变化
|
|
19
|
+
|
|
8
20
|
## [0.5.1] - 2026-10-09
|
|
9
21
|
|
|
10
22
|
### 修复
|
|
@@ -432,7 +432,7 @@ export class QueueManager {
|
|
|
432
432
|
];
|
|
433
433
|
// 「队列位置 / 前面还有」只在前面确实还有等待中的消息时渲染。前面为空时
|
|
434
434
|
// 这两个数是「第 1 位 / 0 条」,与「排队中」字面矛盾;此时挡住用户的只有
|
|
435
|
-
//
|
|
435
|
+
// 正在执行的任务,由下一段的占用者说明(若占用者是普通消息则不额外渲染)。
|
|
436
436
|
if (tasksAhead > 0) {
|
|
437
437
|
elements.push({
|
|
438
438
|
tag: 'div',
|
|
@@ -443,28 +443,26 @@ export class QueueManager {
|
|
|
443
443
|
});
|
|
444
444
|
}
|
|
445
445
|
if (occupant) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
446
|
+
// 只有可能挡住队列、需要用户归因的占用者才自报身份。「正在执行: 💬 消息」
|
|
447
|
+
// 对普通消息占用者没有信息量——用户刚发的就是消息,「💡 消息将按顺序执行…」
|
|
448
|
+
// 也只是复述排队本身,一并省去(2026-10-11)。压缩额外说明何时轮到用户。
|
|
449
|
+
if (occupant.kind !== 'message') {
|
|
450
|
+
elements.push({
|
|
451
|
+
tag: 'div',
|
|
452
|
+
text: { tag: 'lark_md', content: `**正在执行:** ${occupant.label}` },
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (occupant.kind === 'compact') {
|
|
456
|
+
elements.push({
|
|
457
|
+
tag: 'div',
|
|
458
|
+
text: { tag: 'lark_md', content: '💡 压缩结束后将按顺序执行你的消息' },
|
|
459
|
+
});
|
|
460
|
+
}
|
|
459
461
|
}
|
|
460
462
|
else {
|
|
461
463
|
// 防御分支:开卡判据是 lane 非空,所以「有排队卡但没有占用者」说明计数
|
|
462
464
|
// 与占用者记录漂移了。不改行为,只留证据(与 duplicate queue key 同口径)。
|
|
463
465
|
getLogger().warn(`[queue-manager] queue card without lane occupant cwd=${cwd} tasksAhead=${tasksAhead}`);
|
|
464
|
-
elements.push({
|
|
465
|
-
tag: 'div',
|
|
466
|
-
text: { tag: 'lark_md', content: '💡 消息将按顺序执行…' },
|
|
467
|
-
});
|
|
468
466
|
}
|
|
469
467
|
elements.push({ tag: 'hr' });
|
|
470
468
|
// Show message preview with edit button (only if not executing AND the
|
|
@@ -456,10 +456,18 @@ export class CodexAppServerTranslator {
|
|
|
456
456
|
}
|
|
457
457
|
handleTokenUsageUpdated(params) {
|
|
458
458
|
const last = params.tokenUsage.last;
|
|
459
|
+
// 协议原生 inputTokens 含 cached(total = input + output),而 lark-remote 的
|
|
460
|
+
// 卡片契约是 ccusage 口径「input_tokens 表示未命中缓存的输入」(design §9.21)。
|
|
461
|
+
// 在入口归一,否则 done 卡 Input 会等于 Context、cache% 分母重复计数、
|
|
462
|
+
// Total 被 max(total, input+output+cache) 抬高一倍(实测 364K → 727K)。
|
|
463
|
+
// jsonl 链路有同一处减法(session/codex/rollout-reader.ts buildUsageFromScan)。
|
|
464
|
+
const cachedInputTokens = last.cachedInputTokens === undefined
|
|
465
|
+
? undefined
|
|
466
|
+
: Math.min(last.cachedInputTokens, last.inputTokens);
|
|
459
467
|
this.usage = {
|
|
460
|
-
inputTokens: last.inputTokens,
|
|
468
|
+
inputTokens: last.inputTokens - (cachedInputTokens ?? 0),
|
|
461
469
|
outputTokens: last.outputTokens,
|
|
462
|
-
cachedInputTokens
|
|
470
|
+
cachedInputTokens,
|
|
463
471
|
totalTokens: last.totalTokens,
|
|
464
472
|
contextLimit: params.tokenUsage.modelContextWindow,
|
|
465
473
|
};
|
|
@@ -42,6 +42,15 @@ function subtractRawUsage(a, b) {
|
|
|
42
42
|
total_tokens: a.total_tokens - b.total_tokens,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* `compacted` 事件自带的摘要调用 usage(`latest_token_usage_record.usage`)。
|
|
47
|
+
* 用于把「压缩前窗口」与「摘要调用自身的消耗」区分开——两者都写在 compacted 前。
|
|
48
|
+
*/
|
|
49
|
+
function readCompactedCallTotal(payload) {
|
|
50
|
+
const record = payload?.latest_token_usage_record;
|
|
51
|
+
const total = record?.usage?.total_tokens;
|
|
52
|
+
return typeof total === 'number' ? total : undefined;
|
|
53
|
+
}
|
|
45
54
|
export function readCodexRollout(filePath) {
|
|
46
55
|
if (!fs.existsSync(filePath)) {
|
|
47
56
|
return null;
|
|
@@ -136,6 +145,8 @@ function scanCodexRollout(filePath, includeEvents) {
|
|
|
136
145
|
// pi/opencode /resume "last turn" display semantics). When
|
|
137
146
|
// last_token_usage is absent we derive it as total - previous_total.
|
|
138
147
|
let lastRawUsage;
|
|
148
|
+
/** 上一条被接受的 raw usage;compacted 分支回退一条时用(见下方注释)。 */
|
|
149
|
+
let previousRawUsage;
|
|
139
150
|
// Context window limit from the same token_count event as lastRawUsage
|
|
140
151
|
// (codex reports info.model_context_window per turn; absent on old data).
|
|
141
152
|
let lastContextLimit;
|
|
@@ -202,6 +213,7 @@ function scanCodexRollout(filePath, includeEvents) {
|
|
|
202
213
|
lastTotalUsage = total;
|
|
203
214
|
}
|
|
204
215
|
if (raw && (raw.input_tokens || raw.cached_input_tokens || raw.output_tokens)) {
|
|
216
|
+
previousRawUsage = lastRawUsage;
|
|
205
217
|
lastRawUsage = raw;
|
|
206
218
|
const ctxWindow = info?.model_context_window;
|
|
207
219
|
lastContextLimit = typeof ctxWindow === 'number' ? ctxWindow : undefined;
|
|
@@ -223,10 +235,19 @@ function scanCodexRollout(filePath, includeEvents) {
|
|
|
223
235
|
}
|
|
224
236
|
}
|
|
225
237
|
else if (parsed.type === 'compacted') {
|
|
226
|
-
// 顶层 compacted 事件:压缩次数 +1
|
|
238
|
+
// 顶层 compacted 事件:压缩次数 +1,压缩前水位取「压缩调用之前」那条非零
|
|
239
|
+
// token_count 的 total_tokens。真实 rollout 落盘顺序是
|
|
240
|
+
// 末轮 → 摘要调用 → compacted → 压缩后水位:摘要调用自身的 token_count 紧贴
|
|
241
|
+
// 在 compacted 之前,直接取 lastRawUsage 会把摘要调用的消耗当成压缩前窗口
|
|
242
|
+
// (实测把 363K 的窗口显示成 207K)。compacted 自带的摘要调用 usage 用于判定
|
|
243
|
+
// 并回退一条;旧数据没有 latest_token_usage_record 时保持原语义。
|
|
227
244
|
compactCount++;
|
|
228
|
-
|
|
229
|
-
|
|
245
|
+
const compactCallTotal = readCompactedCallTotal(parsed.payload);
|
|
246
|
+
const preCompactUsage = compactCallTotal !== undefined && lastRawUsage?.total_tokens === compactCallTotal
|
|
247
|
+
? previousRawUsage
|
|
248
|
+
: lastRawUsage;
|
|
249
|
+
if (preCompactUsage?.total_tokens !== undefined) {
|
|
250
|
+
compactPreContextLength = preCompactUsage.total_tokens;
|
|
230
251
|
}
|
|
231
252
|
}
|
|
232
253
|
else if (includeEvents && parsed.type === 'response_item' && parsed.payload) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lark-remote",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Feishu (Lark) private-chat bridge to local coding agents — Claude Code / Codex / opencode / pi / kimi, with live-streaming CardKit 2.0 cards",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|