lark-remote 0.1.12 → 0.2.0
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 +37 -0
- package/README.en.md +2 -6
- package/README.md +3 -7
- package/dist/bridge/final-usage.d.ts +24 -0
- package/dist/bridge/final-usage.js +55 -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 -7
- package/dist/bridge/index.js +100 -133
- package/dist/bridge/queue-manager.js +4 -3
- package/dist/card/approval-render.js +39 -94
- package/dist/card/bash-renderer.js +12 -21
- package/dist/card/card-shared.d.ts +14 -5
- package/dist/card/card-shared.js +23 -34
- package/dist/card/run-renderer.d.ts +0 -22
- package/dist/card/run-renderer.js +206 -153
- package/dist/card/run-state.d.ts +29 -1
- package/dist/card/run-state.js +172 -84
- package/dist/card/text-truncate.d.ts +0 -9
- package/dist/card/text-truncate.js +0 -9
- package/dist/card/tool-render.d.ts +4 -1
- package/dist/card/tool-render.js +76 -32
- package/dist/cli.js +2 -2
- package/dist/common/fs.d.ts +14 -0
- package/dist/common/fs.js +57 -3
- 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.js +14 -11
- package/dist/config/index.d.ts +0 -8
- package/dist/config/index.js +0 -16
- package/dist/config/kimi-config.d.ts +1 -1
- package/dist/config/kimi-config.js +2 -1
- package/dist/config/opencode-config.d.ts +1 -1
- package/dist/config/opencode-config.js +16 -10
- package/dist/config/qr.d.ts +36 -0
- package/dist/config/qr.js +124 -0
- package/dist/config/wizard.js +69 -27
- package/dist/connector/index.d.ts +3 -2
- package/dist/connector/index.js +5 -3
- package/dist/index.js +14 -41
- package/dist/instance-lock.js +7 -0
- package/dist/persistence/atomic-write.d.ts +2 -0
- package/dist/persistence/atomic-write.js +20 -5
- package/dist/persistence/load-json-file.d.ts +1 -2
- package/dist/persistence/load-json-file.js +2 -3
- package/dist/platform/agent-stopper.d.ts +22 -0
- package/dist/platform/agent-stopper.js +19 -0
- package/dist/platform/command.d.ts +11 -0
- package/dist/platform/command.js +107 -0
- package/dist/platform/identity.d.ts +33 -0
- package/dist/platform/identity.js +187 -0
- package/dist/platform/path.d.ts +24 -0
- package/dist/platform/path.js +110 -0
- package/dist/platform/select.d.ts +9 -0
- package/dist/platform/select.js +12 -0
- package/dist/platform/shell.d.ts +51 -0
- package/dist/platform/shell.js +75 -0
- package/dist/platform/spawn.d.ts +41 -0
- package/dist/platform/spawn.js +77 -0
- package/dist/platform/terminator-posix.d.ts +38 -0
- package/dist/platform/terminator-posix.js +103 -0
- package/dist/platform/terminator-win32.d.ts +21 -0
- package/dist/platform/terminator-win32.js +110 -0
- package/dist/platform/terminator.d.ts +44 -0
- package/dist/platform/terminator.js +24 -0
- package/dist/platform/types.d.ts +23 -0
- package/dist/platform/types.js +5 -0
- package/dist/restart.js +4 -2
- package/dist/router/card-helpers.d.ts +33 -6
- package/dist/router/card-helpers.js +67 -11
- package/dist/router/config/common/render.d.ts +1 -1
- package/dist/router/config/common/render.js +1 -1
- package/dist/router/config-diff.d.ts +4 -0
- package/dist/router/config-diff.js +70 -0
- package/dist/router/index.d.ts +73 -6
- package/dist/router/index.js +663 -590
- package/dist/router/utils.d.ts +6 -0
- package/dist/router/utils.js +12 -0
- package/dist/runner/bash/runner.d.ts +9 -2
- package/dist/runner/bash/runner.js +45 -20
- package/dist/runner/claude/session.d.ts +4 -4
- package/dist/runner/claude/session.js +18 -9
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
- package/dist/runner/codex/app-server/runner.d.ts +1 -16
- package/dist/runner/codex/app-server/runner.js +2 -2
- package/dist/runner/codex/app-server/translator.d.ts +18 -0
- package/dist/runner/codex/app-server/translator.js +146 -0
- package/dist/runner/common/acp/base-acp-runner.d.ts +116 -0
- package/dist/runner/common/acp/base-acp-runner.js +248 -0
- package/dist/runner/common/acp/base-translator.d.ts +141 -0
- package/dist/runner/common/acp/base-translator.js +271 -0
- package/dist/runner/common/acp/protocol-helpers.d.ts +70 -0
- package/dist/runner/common/acp/protocol-helpers.js +82 -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/common/jsonrpc/transport.d.ts +2 -0
- package/dist/runner/common/jsonrpc/transport.js +25 -5
- package/dist/runner/common/process-stopper.d.ts +4 -26
- package/dist/runner/common/process-stopper.js +4 -76
- package/dist/runner/common/runner-utils.d.ts +0 -5
- package/dist/runner/common/runner-utils.js +0 -7
- package/dist/runner/common/spawning-runner.d.ts +42 -132
- package/dist/runner/common/spawning-runner.js +52 -271
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- 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 +63 -53
- package/dist/runner/kimi/acp/runner.js +189 -334
- package/dist/runner/kimi/acp/translator.d.ts +18 -101
- package/dist/runner/kimi/acp/translator.js +61 -255
- package/dist/runner/opencode/acp/runner.d.ts +22 -46
- package/dist/runner/opencode/acp/runner.js +74 -297
- package/dist/runner/opencode/acp/translator.d.ts +28 -104
- package/dist/runner/opencode/acp/translator.js +54 -249
- package/dist/runner/pi/rpc/index.d.ts +0 -2
- package/dist/runner/pi/rpc/index.js +0 -2
- package/dist/runner/pi/rpc/translator.d.ts +3 -0
- package/dist/runner/pi/rpc/translator.js +47 -9
- package/dist/runner/probe.d.ts +8 -11
- package/dist/runner/probe.js +17 -21
- package/dist/runner/registry.d.ts +4 -4
- package/dist/runner/registry.js +4 -4
- package/dist/runner/resolve-agent-choices.js +6 -2
- package/dist/runner/sync-agent-choices.js +1 -1
- package/dist/runner/types.d.ts +38 -1
- package/dist/session/claude/session-index.js +3 -1
- package/dist/session/claude/sessions.js +37 -64
- package/dist/session/codex/rollout-reader.d.ts +10 -14
- package/dist/session/codex/rollout-reader.js +126 -48
- package/dist/session/common/constants.d.ts +7 -0
- package/dist/session/common/constants.js +9 -0
- 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/two-pass.d.ts +22 -0
- package/dist/session/common/two-pass.js +70 -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 +15 -11
- package/dist/session/kimi/sessions.d.ts +24 -4
- package/dist/session/kimi/sessions.js +99 -103
- package/dist/session/opencode/sessions.d.ts +2 -14
- package/dist/session/opencode/sessions.js +35 -15
- package/dist/session/pi/sessions.js +64 -95
- package/dist/update/index.d.ts +2 -3
- package/dist/update/index.js +1 -2
- package/dist/update/install.d.ts +2 -1
- package/dist/update/install.js +58 -9
- package/dist/update/version-check.d.ts +5 -0
- package/dist/update/version-check.js +9 -0
- package/package.json +9 -7
- package/dist/update/startup-hint.d.ts +0 -5
- package/dist/update/startup-hint.js +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,43 @@ 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.2.0] - 2026-09-11
|
|
9
|
+
|
|
10
|
+
### 新增
|
|
11
|
+
|
|
12
|
+
- **Windows 平台支持(预发布)**:新增跨平台平台层(`src/platform/`)——路径、可执行文件发现、进程终止、spawn、shell 的抽象与 posix / win32 双实现;命令拉起统一走 `cross-spawn`,posix 行为零变化。Windows 尚未正式发布,README 前置条件已相应标注
|
|
13
|
+
- **`/download <path>`(别名 `/d`)**:把本地文件直接发到会话,不经 `/ls` 卡片。路径规则同 `/ls`(`~` 展开、相对 cwd、30MB 上限),目录 / 不存在 / 超限给出明确错误
|
|
14
|
+
- **`/ls <file>` 单文件卡**:传入文件路径渲染单文件卡(完整路径、大小、修改时间、「📎 下载」+「上级」按钮),不再报 `ls: xxx: Not a directory`
|
|
15
|
+
- **分页栏页码跳转**:所有分页卡片(`/ls`、`/ws`、`/resume`、`/active`、`/order`)支持在分页栏输入页码直达;越界钳到末页,非数字输入报错且不刷新卡片
|
|
16
|
+
- **扫码向导跨平台**:二维码渲染改用 `qrcode-generator`,Windows 全块渲染 + 静区 + 图片兜底
|
|
17
|
+
|
|
18
|
+
### 修复
|
|
19
|
+
|
|
20
|
+
- `/ls` 的「返回」改为回到浏览起点(`/ls <dir>` 指定的目录),不再跳回 workspace cwd;浏览起点随卡片 payload 传递,翻页 / 刷新 / 上级 / 目录按钮不会丢失
|
|
21
|
+
- codex 会话兼容 rollout ≥0.153.4 的 `item_completed` 词表,修复 `/resume` 标题丢失
|
|
22
|
+
- compact 卡片空内容渲染「Compact 完成」,不再显示通用空占位
|
|
23
|
+
- 信息保真:notices 改为时间线消息渲染、省略提示固定置顶、工具卡片消除孤立代理项
|
|
24
|
+
- Windows:隐藏 cmd.exe 控制台窗口(`windowsHide`)
|
|
25
|
+
|
|
26
|
+
### 变更
|
|
27
|
+
|
|
28
|
+
- **移除 `output.showThinking` / `showToolUse` / `showToolResult` 配置项与 `/config` 的「📤 输出」页签**:thinking、工具调用与工具结果恒常展示;旧配置文件的 `output` 段在加载时自动忽略,并在下次保存时从磁盘清理
|
|
29
|
+
- 代码精简:ACP / translator / session 共享层下沉(`BaseAcpTranslator` / `BaseAcpRunner` / two-pass 扫描),死代码清理与平行重复收敛
|
|
30
|
+
- 依赖:`qrcode-terminal` 换为 `qrcode-generator`;新增 `cross-spawn`(Windows 命令拉起)
|
|
31
|
+
- 测试基建:新增 scoped typecheck(`tsconfig.test-infra.json`)覆盖 `tests/lib/**` 共享工厂;跨平台测试稳定化(win32 路径归一化、并行 vitest 内存上限)
|
|
32
|
+
|
|
33
|
+
## [0.1.13] - 2026-09-04
|
|
34
|
+
|
|
35
|
+
### 修复
|
|
36
|
+
|
|
37
|
+
- kimi ACP Compact 等待机制重构:等待协议终态(complete / cancel / unknown)再收尾,移除「30s 超时即视为成功」的假完成路径——Compact 实际失败或被取消时如实反馈
|
|
38
|
+
|
|
39
|
+
### 变更
|
|
40
|
+
|
|
41
|
+
- 代码与测试清理:死导出/死字段清理、bridge usage 统计收敛、router 样板收敛、session 公共件下沉(`ttl-cache` / `recency`)、ACP 共享层下沉
|
|
42
|
+
- 测试治理:超大测试文件按主题拆分、agent choices 测试表驱动化、弱断言收敛、移除无效测试
|
|
43
|
+
- 移除未使用的 `@types/form-data` 依赖
|
|
44
|
+
|
|
8
45
|
## [0.1.12] - 2026-08-29
|
|
9
46
|
|
|
10
47
|
### 修复
|
package/README.en.md
CHANGED
|
@@ -73,11 +73,6 @@ claude:
|
|
|
73
73
|
# permissionMode is hardcoded to bypassPermissions (inside the runner)
|
|
74
74
|
stopGraceMs: 5000
|
|
75
75
|
|
|
76
|
-
output:
|
|
77
|
-
showThinking: true
|
|
78
|
-
showToolUse: true
|
|
79
|
-
showToolResult: true
|
|
80
|
-
|
|
81
76
|
logging:
|
|
82
77
|
level: info # debug | info | warn | error
|
|
83
78
|
|
|
@@ -113,7 +108,8 @@ Your original message also gets an emoji reaction: `Typing` while the run is in
|
|
|
113
108
|
|---------|-------|----------|
|
|
114
109
|
| `/help` | `/h` | Command list |
|
|
115
110
|
| `/cd <path>` | - | Switch agent working directory (`~`, absolute/relative; clears the session) |
|
|
116
|
-
| `/ls [dir]` | - | Directory/file card; click a directory to
|
|
111
|
+
| `/ls [dir\|file]` | - | Directory/file card; click a directory to browse, click a file ≤30MB to send it to Feishu; paginates beyond 30 entries with a page-number jump input. Passing a file path lists that file itself |
|
|
112
|
+
| `/download <path>` | `/d` | Send a local file straight to the chat (`~`, absolute/relative paths, 30MB cap) |
|
|
117
113
|
| `/ws save\|use\|remove` | - | Named directory aliases (`/ws` lists by default) |
|
|
118
114
|
| `/resume [agent] [N\|id]` | `/r` | List/switch agent sessions for the current directory (card) |
|
|
119
115
|
| `/active` | - | List all running sessions |
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Codex 审批模式下,执行命令前会收到审批卡片,可直接在飞
|
|
|
29
29
|
|
|
30
30
|
## 前置条件
|
|
31
31
|
|
|
32
|
-
- 操作系统:macOS / Linux。**暂不支持 Windows
|
|
32
|
+
- 操作系统:macOS / Linux。**暂不支持 Windows**(原生 Windows 支持在建设中:平台 seam 已落地,待真机验证后发布)
|
|
33
33
|
- Node.js 20+(开发时用 [Bun](https://bun.sh/))
|
|
34
34
|
- 飞书自建应用(二选一):扫码创建(首次启动终端弹二维码,飞书 App 扫码即自动创建并写入凭据);或手动在开放平台建应用——开启机器人能力,订阅 `im.message.receive_v1` 和 `card.action.trigger`,订阅方式选「长连接」(WebSocket,无需公网地址),权限至少 `im:message`。
|
|
35
35
|
- Claude Code CLI:本地安装并在终端完成一次登录(`claude` → 浏览器 OAuth)。使用其他 agent(codex / opencode / pi / kimi)同理,先装好对应 CLI;DSH 需本地 DSH Web Host 在跑(默认 `http://127.0.0.1:3080`),lark-remote 通过 HTTP+WebSocket 直连、不 spawn 本地子进程。
|
|
@@ -73,11 +73,6 @@ claude:
|
|
|
73
73
|
# permissionMode 硬编码为 bypassPermissions(runner 内部)
|
|
74
74
|
stopGraceMs: 5000
|
|
75
75
|
|
|
76
|
-
output:
|
|
77
|
-
showThinking: true
|
|
78
|
-
showToolUse: true
|
|
79
|
-
showToolResult: true
|
|
80
|
-
|
|
81
76
|
logging:
|
|
82
77
|
level: info # debug | info | warn | error
|
|
83
78
|
|
|
@@ -113,7 +108,8 @@ bridge 启动后不在终端输出,运行日志写入 `~/.lark-remote/logs/YYY
|
|
|
113
108
|
|------|------|------|
|
|
114
109
|
| `/help` | `/h` | 命令列表 |
|
|
115
110
|
| `/cd <path>` | - | 切换 agent 工作目录(支持 `~`、绝对/相对路径,清空会话) |
|
|
116
|
-
| `/ls [dir]` | - | 弹出目录/文件卡片;点击目录切换,点击 30MB 内文件发送到飞书;条目 >30
|
|
111
|
+
| `/ls [dir\|file]` | - | 弹出目录/文件卡片;点击目录切换,点击 30MB 内文件发送到飞书;条目 >30 时支持翻页与直接输入页码跳转。传文件路径时直接列出该文件(可下载) |
|
|
112
|
+
| `/download <path>` | `/d` | 直接下载(发送)指定文件到飞书(支持 `~`、绝对/相对路径,上限 30MB) |
|
|
117
113
|
| `/ws save\|use\|remove` | - | 命名目录别名管理(`/ws` 默认列出) |
|
|
118
114
|
| `/resume [agent] [N\|id]` | `/r` | 列出/切换当前目录的 agent session(卡片) |
|
|
119
115
|
| `/active` | - | 列出所有正在进行中的 session |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SessionReaderRegistry } from '../session/index.js';
|
|
2
|
+
import type { AgentKind } from '../runner/index.js';
|
|
3
|
+
export declare function errorMessage(error: unknown): string;
|
|
4
|
+
export type FinalUsageSnapshot = {
|
|
5
|
+
contextLength?: number;
|
|
6
|
+
contextLimit?: number;
|
|
7
|
+
compactCount?: number;
|
|
8
|
+
compactPreContextLength?: number;
|
|
9
|
+
cacheReadTokens?: number;
|
|
10
|
+
cacheCreationTokens?: number;
|
|
11
|
+
totalTokens?: number;
|
|
12
|
+
inputTokens?: number;
|
|
13
|
+
outputTokens?: number;
|
|
14
|
+
cumulativeTotalTokens?: number;
|
|
15
|
+
cumulativeInputTokens?: number;
|
|
16
|
+
cumulativeOutputTokens?: number;
|
|
17
|
+
cumulativeCacheReadTokens?: number;
|
|
18
|
+
cumulativeCacheCreationTokens?: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Read final usage (contextLength + compactCount + cache tokens) from the
|
|
22
|
+
* session jsonl. Called after a run finishes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveFinalUsage(registry: SessionReaderRegistry, defaultAgent: AgentKind, sessionId: string | undefined, cwd: string, agentKind?: AgentKind): FinalUsageSnapshot | undefined;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Final usage resolution extracted from Bridge (2026-09 round-2
|
|
3
|
+
* simplification): after a run finishes, re-read the session transcript via
|
|
4
|
+
* the agent's session reader — live stream-json does not emit
|
|
5
|
+
* compact_boundary, so the live contextLength (fallback to result.usage
|
|
6
|
+
* input+output) is unreliable. The transcript jsonl is authoritative for
|
|
7
|
+
* postTokens + compact event count.
|
|
8
|
+
*
|
|
9
|
+
* Bridge state is passed in explicitly (registry + default agent); the
|
|
10
|
+
* function itself is stateless.
|
|
11
|
+
*/
|
|
12
|
+
import { getLogger } from '../logger/index.js';
|
|
13
|
+
export function errorMessage(error) {
|
|
14
|
+
return error instanceof Error ? error.message : String(error);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read final usage (contextLength + compactCount + cache tokens) from the
|
|
18
|
+
* session jsonl. Called after a run finishes.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveFinalUsage(registry, defaultAgent, sessionId, cwd, agentKind = defaultAgent) {
|
|
21
|
+
if (!sessionId)
|
|
22
|
+
return undefined;
|
|
23
|
+
try {
|
|
24
|
+
const content = registry.get(agentKind).readSessionContent(sessionId, cwd);
|
|
25
|
+
if (!content.usage) {
|
|
26
|
+
// EnterWorktree relocate (2026-08-04): jsonl read silently returned no
|
|
27
|
+
// usage (e.g. transcript moved mid-session). Surface it so token-stat
|
|
28
|
+
// fallback to per-run live usage is visible in logs, not silent.
|
|
29
|
+
getLogger().warn(`[bridge] resolveFinalUsage: no usage from jsonl sessionId=${sessionId} cwd=${cwd} agent=${agentKind}, card falls back to per-run live usage`);
|
|
30
|
+
}
|
|
31
|
+
return content.usage
|
|
32
|
+
? {
|
|
33
|
+
contextLength: content.usage.contextLength,
|
|
34
|
+
contextLimit: content.usage.contextLimit,
|
|
35
|
+
compactCount: content.usage.compactCount,
|
|
36
|
+
compactPreContextLength: content.usage.compactPreContextLength,
|
|
37
|
+
cacheReadTokens: content.usage.cacheReadTokens,
|
|
38
|
+
cacheCreationTokens: content.usage.cacheCreationTokens,
|
|
39
|
+
totalTokens: content.usage.totalTokens,
|
|
40
|
+
inputTokens: content.usage.inputTokens,
|
|
41
|
+
outputTokens: content.usage.outputTokens,
|
|
42
|
+
cumulativeTotalTokens: content.usage.cumulativeTotalTokens,
|
|
43
|
+
cumulativeInputTokens: content.usage.cumulativeInputTokens,
|
|
44
|
+
cumulativeOutputTokens: content.usage.cumulativeOutputTokens,
|
|
45
|
+
cumulativeCacheReadTokens: content.usage.cumulativeCacheReadTokens,
|
|
46
|
+
cumulativeCacheCreationTokens: content.usage.cumulativeCacheCreationTokens,
|
|
47
|
+
}
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
getLogger().warn(`[bridge] resolveFinalUsage failed sessionId=${sessionId}: ${errorMessage(err)}`);
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=final-usage.js.map
|
|
@@ -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
|
@@ -361,13 +361,6 @@ export declare class Bridge {
|
|
|
361
361
|
* Handles fallback card sending, completion notification, and cleanup.
|
|
362
362
|
*/
|
|
363
363
|
private finalizeRun;
|
|
364
|
-
/**
|
|
365
|
-
* Read final usage (contextLength + compactCount + cache tokens) from the session jsonl.
|
|
366
|
-
* Called after a run finishes: live stream-json does not emit compact_boundary,
|
|
367
|
-
* so the live contextLength (fallback to result.usage input+output) is
|
|
368
|
-
* unreliable. The jsonl is authoritative for postTokens + compact event count.
|
|
369
|
-
*/
|
|
370
|
-
private resolveFinalUsage;
|
|
371
364
|
/**
|
|
372
365
|
* Send a notification card when a run completes.
|
|
373
366
|
* This card contains the session summary and a button to resume the session.
|
|
@@ -463,6 +456,13 @@ export declare class Bridge {
|
|
|
463
456
|
handleCodexCompact(value: {
|
|
464
457
|
runId?: string;
|
|
465
458
|
}, ctx: BridgeContext): Promise<void>;
|
|
459
|
+
/**
|
|
460
|
+
* §5.3: kimi 压缩在途检测——reader 鸭子实现 `readCompactionState` 时,若
|
|
461
|
+
* wire 状态机判定当前有未终结的压缩(最后一次 begin 无 terminal 记录),
|
|
462
|
+
* 直接回文本「已有一次 Compact 正在进行」并返回 true(调用方不开卡、不发
|
|
463
|
+
* session/prompt)。codex/claude 等 reader 无此方法 → 返回 false,行为不变。
|
|
464
|
+
*/
|
|
465
|
+
private rejectIfCompactionInFlight;
|
|
466
466
|
/**
|
|
467
467
|
* Stream a compaction to a card: start a RunCardSession with
|
|
468
468
|
* operationKind='compaction' (no recursive Compact button), consume
|
package/dist/bridge/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { sleep } from '../common/sleep.js';
|
|
3
|
+
import { resolveFinalUsage, errorMessage } from './final-usage.js';
|
|
3
4
|
import { getLogger } from '../logger/index.js';
|
|
4
5
|
import { RunCardSession } from '../card/run-card-session.js';
|
|
5
6
|
import { renderRunCard } from '../card/run-renderer.js';
|
|
@@ -48,6 +49,13 @@ const TERMINAL_REACTION_EMOJI = {
|
|
|
48
49
|
function terminalReactionEmoji(terminal) {
|
|
49
50
|
return TERMINAL_REACTION_EMOJI[terminal] ?? 'Done';
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* W2.4 单点化:`'runCompact' in runner` 鸭子探测(compact 按钮门控,
|
|
54
|
+
* design doc §6.2-2)。codex app-server + kimi/opencode ACP runner 满足。
|
|
55
|
+
*/
|
|
56
|
+
function runnerHasRunCompact(runner) {
|
|
57
|
+
return ('runCompact' in runner && typeof runner.runCompact === 'function');
|
|
58
|
+
}
|
|
51
59
|
/**
|
|
52
60
|
* Bridge owns the per-workspace serial processing queue (§9.6) and the
|
|
53
61
|
* single-card streaming forwarding loop (§9.12 watchdog + session sync).
|
|
@@ -245,7 +253,7 @@ export class Bridge {
|
|
|
245
253
|
*/
|
|
246
254
|
hasRunCompact(workspace, kind = this.config.defaultAgent) {
|
|
247
255
|
const runner = this.getRunner(workspace, kind);
|
|
248
|
-
return (
|
|
256
|
+
return runnerHasRunCompact(runner);
|
|
249
257
|
}
|
|
250
258
|
/** 入队时刻快照:当前 defaultAgent + 该 agent 的 sessionId(无 session 则 undefined)。
|
|
251
259
|
* 唯一捕获点:排队消息在 T0 把 agent+session 钉进 AgentBinding,随任务闭包带到 T1
|
|
@@ -764,9 +772,6 @@ export class Bridge {
|
|
|
764
772
|
replyTo: ctx.messageId,
|
|
765
773
|
runId,
|
|
766
774
|
renderOptions: {
|
|
767
|
-
showThinking: this.config.output.showThinking,
|
|
768
|
-
showToolUse: this.config.output.showToolUse,
|
|
769
|
-
showToolResult: this.config.output.showToolResult,
|
|
770
775
|
// Run card header shows the current agent name (e.g. "Claude · 思考中")
|
|
771
776
|
agentKind,
|
|
772
777
|
// Compact 按钮能力门控与 resume 卡 / auto-resume 卡一致(hasRunCompact
|
|
@@ -834,6 +839,29 @@ export class Bridge {
|
|
|
834
839
|
let finalCumulativeOutputTokens;
|
|
835
840
|
let finalCumulativeCacheReadTokens;
|
|
836
841
|
let finalCumulativeCacheCreationTokens;
|
|
842
|
+
// Reasoning tokens (pi usage.reasoning) from the live result event.
|
|
843
|
+
let finalReasoningTokens;
|
|
844
|
+
// 统一的 usage meta 构造(finalize 各分支共享,本方法内原先 5 处手写 11-15
|
|
845
|
+
// 字段对象;第 6 处 streamCodexCompact 形状不同未收敛)。
|
|
846
|
+
// catch 路径需覆盖 flow 字段时在展开后覆写即可。catch error 路径亦复用此
|
|
847
|
+
// 闭包,相比旧手写对象补齐了累计 cache 字段(cumulativeCacheReadTokens/
|
|
848
|
+
// cumulativeCacheCreationTokens),属有意的口径对齐(向 done 路径看齐)。
|
|
849
|
+
const usageMeta = () => ({
|
|
850
|
+
contextLength: finalContextLength,
|
|
851
|
+
contextLimit: finalContextLimit,
|
|
852
|
+
compactCount: finalCompactCount,
|
|
853
|
+
cacheReadTokens: finalCacheReadTokens,
|
|
854
|
+
cacheCreationTokens: finalCacheCreationTokens,
|
|
855
|
+
totalTokens: finalTotalTokens,
|
|
856
|
+
inputTokens: finalInputTokens,
|
|
857
|
+
outputTokens: finalOutputTokens,
|
|
858
|
+
cumulativeTotalTokens: finalCumulativeTotalTokens,
|
|
859
|
+
cumulativeInputTokens: finalCumulativeInputTokens,
|
|
860
|
+
cumulativeOutputTokens: finalCumulativeOutputTokens,
|
|
861
|
+
cumulativeCacheReadTokens: finalCumulativeCacheReadTokens,
|
|
862
|
+
cumulativeCacheCreationTokens: finalCumulativeCacheCreationTokens,
|
|
863
|
+
reasoningTokens: finalReasoningTokens,
|
|
864
|
+
});
|
|
837
865
|
try {
|
|
838
866
|
try {
|
|
839
867
|
getLogger().debug(`[bridge] cardSession.start() begin runId=${runId}`);
|
|
@@ -1027,6 +1055,9 @@ export class Bridge {
|
|
|
1027
1055
|
if (u.totalTokens !== undefined) {
|
|
1028
1056
|
finalTotalTokens = u.totalTokens;
|
|
1029
1057
|
}
|
|
1058
|
+
if (u.reasoningTokens !== undefined) {
|
|
1059
|
+
finalReasoningTokens = u.reasoningTokens;
|
|
1060
|
+
}
|
|
1030
1061
|
}
|
|
1031
1062
|
}
|
|
1032
1063
|
// Approval events (Codex app-server mode)
|
|
@@ -1105,7 +1136,7 @@ export class Bridge {
|
|
|
1105
1136
|
const finalSessionId = this.sessionStore.getSessionId(ctx.userId, agentKind);
|
|
1106
1137
|
const finalUsage = resultNotSuccess
|
|
1107
1138
|
? undefined
|
|
1108
|
-
: this.
|
|
1139
|
+
: resolveFinalUsage(this.sessionReaderRegistry, this.config.defaultAgent, finalSessionId, cwd, agentKind);
|
|
1109
1140
|
finalContextLength = finalUsage?.contextLength ?? contextLength;
|
|
1110
1141
|
finalContextLimit = finalUsage?.contextLimit ?? liveContextLimit;
|
|
1111
1142
|
finalCompactCount = finalUsage?.compactCount;
|
|
@@ -1156,57 +1187,19 @@ export class Bridge {
|
|
|
1156
1187
|
await cardSession.finish(terminal, {
|
|
1157
1188
|
resultSubtype: subtype,
|
|
1158
1189
|
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,
|
|
1190
|
+
...usageMeta(),
|
|
1172
1191
|
});
|
|
1173
1192
|
}
|
|
1174
1193
|
else if (finalState.terminal === 'running') {
|
|
1175
1194
|
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,
|
|
1195
|
+
...usageMeta(),
|
|
1189
1196
|
errorMsg: agentDisplayName(agentKind) + ' 输出流已结束,但未收到 result 事件',
|
|
1190
1197
|
});
|
|
1191
1198
|
}
|
|
1192
1199
|
else if (sawResult) {
|
|
1193
1200
|
// 已终态(interrupted/idle_timeout)+ result 已收到:首终态优先,
|
|
1194
1201
|
// 仅补充 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
|
-
});
|
|
1202
|
+
await cardSession.finish(finalState.terminal, usageMeta());
|
|
1210
1203
|
}
|
|
1211
1204
|
// else: 已终态且未收到 result(如 spawn 失败后 interrupted)-> 无 usage 可补,跳过
|
|
1212
1205
|
}
|
|
@@ -1225,38 +1218,16 @@ export class Bridge {
|
|
|
1225
1218
|
const catchTerminal = cardSession.currentState.terminal;
|
|
1226
1219
|
if (catchTerminal === 'running' || catchTerminal === 'finalizing') {
|
|
1227
1220
|
await cardSession.finish('error', {
|
|
1228
|
-
|
|
1229
|
-
contextLimit: finalContextLimit,
|
|
1230
|
-
compactCount: finalCompactCount,
|
|
1231
|
-
cacheReadTokens: finalCacheReadTokens,
|
|
1232
|
-
cacheCreationTokens: finalCacheCreationTokens,
|
|
1233
|
-
totalTokens: finalTotalTokens,
|
|
1221
|
+
...usageMeta(),
|
|
1234
1222
|
inputTokens: liveInputTokens,
|
|
1235
1223
|
outputTokens: liveOutputTokens,
|
|
1236
|
-
cumulativeTotalTokens: finalCumulativeTotalTokens,
|
|
1237
|
-
cumulativeInputTokens: finalCumulativeInputTokens,
|
|
1238
|
-
cumulativeOutputTokens: finalCumulativeOutputTokens,
|
|
1239
1224
|
errorMsg: errorMessage(err),
|
|
1240
1225
|
});
|
|
1241
1226
|
}
|
|
1242
1227
|
else {
|
|
1243
1228
|
getLogger().info(`[bridge] skip error finish: state already terminal (${catchTerminal}) runId=${runId}`);
|
|
1244
1229
|
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
|
-
});
|
|
1230
|
+
await cardSession.finish(catchTerminal, usageMeta());
|
|
1260
1231
|
}
|
|
1261
1232
|
}
|
|
1262
1233
|
}
|
|
@@ -1283,9 +1254,6 @@ export class Bridge {
|
|
|
1283
1254
|
if (settleResult === 'unsent') {
|
|
1284
1255
|
const sent = await this.sendResult({
|
|
1285
1256
|
card: renderRunCard(cardSession.currentState, {
|
|
1286
|
-
showThinking: this.config.output.showThinking,
|
|
1287
|
-
showToolUse: this.config.output.showToolUse,
|
|
1288
|
-
showToolResult: this.config.output.showToolResult,
|
|
1289
1257
|
agentKind: activeRun.agentKind,
|
|
1290
1258
|
// 与 createRunSession 的 renderOptions 一致:fallback 卡也要按
|
|
1291
1259
|
// runner 能力门控 Compact 按钮(缺省 undefined = 显示按钮,
|
|
@@ -1335,8 +1303,7 @@ export class Bridge {
|
|
|
1335
1303
|
// §6.2-3:写入条件从「codex 终态」放宽为「runner 有 runCompact 的终态」
|
|
1336
1304
|
// (codex app-server + kimi acp 均满足)。
|
|
1337
1305
|
const runTerminal = cardSession.currentState.terminal;
|
|
1338
|
-
const runnerHasCompact =
|
|
1339
|
-
typeof activeRun.runner.runCompact === 'function';
|
|
1306
|
+
const runnerHasCompact = runnerHasRunCompact(activeRun.runner);
|
|
1340
1307
|
if (runnerHasCompact &&
|
|
1341
1308
|
activeRun.runner.lifetime === 'workspace' &&
|
|
1342
1309
|
(runTerminal === 'done' ||
|
|
@@ -1357,47 +1324,6 @@ export class Bridge {
|
|
|
1357
1324
|
}
|
|
1358
1325
|
}
|
|
1359
1326
|
}
|
|
1360
|
-
/**
|
|
1361
|
-
* Read final usage (contextLength + compactCount + cache tokens) from the session jsonl.
|
|
1362
|
-
* Called after a run finishes: live stream-json does not emit compact_boundary,
|
|
1363
|
-
* so the live contextLength (fallback to result.usage input+output) is
|
|
1364
|
-
* unreliable. The jsonl is authoritative for postTokens + compact event count.
|
|
1365
|
-
*/
|
|
1366
|
-
resolveFinalUsage(sessionId, cwd, agentKind = this.config.defaultAgent) {
|
|
1367
|
-
if (!sessionId)
|
|
1368
|
-
return undefined;
|
|
1369
|
-
try {
|
|
1370
|
-
const content = this.sessionReaderRegistry.get(agentKind).readSessionContent(sessionId, cwd);
|
|
1371
|
-
if (!content.usage) {
|
|
1372
|
-
// EnterWorktree relocate (2026-08-04): jsonl read silently returned no
|
|
1373
|
-
// usage (e.g. transcript moved mid-session). Surface it so token-stat
|
|
1374
|
-
// fallback to per-run live usage is visible in logs, not silent.
|
|
1375
|
-
getLogger().warn(`[bridge] resolveFinalUsage: no usage from jsonl sessionId=${sessionId} cwd=${cwd} agent=${agentKind}, card falls back to per-run live usage`);
|
|
1376
|
-
}
|
|
1377
|
-
return content.usage
|
|
1378
|
-
? {
|
|
1379
|
-
contextLength: content.usage.contextLength,
|
|
1380
|
-
contextLimit: content.usage.contextLimit,
|
|
1381
|
-
compactCount: content.usage.compactCount,
|
|
1382
|
-
compactPreContextLength: content.usage.compactPreContextLength,
|
|
1383
|
-
cacheReadTokens: content.usage.cacheReadTokens,
|
|
1384
|
-
cacheCreationTokens: content.usage.cacheCreationTokens,
|
|
1385
|
-
totalTokens: content.usage.totalTokens,
|
|
1386
|
-
inputTokens: content.usage.inputTokens,
|
|
1387
|
-
outputTokens: content.usage.outputTokens,
|
|
1388
|
-
cumulativeTotalTokens: content.usage.cumulativeTotalTokens,
|
|
1389
|
-
cumulativeInputTokens: content.usage.cumulativeInputTokens,
|
|
1390
|
-
cumulativeOutputTokens: content.usage.cumulativeOutputTokens,
|
|
1391
|
-
cumulativeCacheReadTokens: content.usage.cumulativeCacheReadTokens,
|
|
1392
|
-
cumulativeCacheCreationTokens: content.usage.cumulativeCacheCreationTokens,
|
|
1393
|
-
}
|
|
1394
|
-
: undefined;
|
|
1395
|
-
}
|
|
1396
|
-
catch (err) {
|
|
1397
|
-
getLogger().warn(`[bridge] resolveFinalUsage failed sessionId=${sessionId}: ${errorMessage(err)}`);
|
|
1398
|
-
return undefined;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
1327
|
/**
|
|
1402
1328
|
* Send a notification card when a run completes.
|
|
1403
1329
|
* This card contains the session summary and a button to resume the session.
|
|
@@ -1562,10 +1488,13 @@ export class Bridge {
|
|
|
1562
1488
|
await this.sendResult({ text: '⚠️ 未找到可压缩的会话' }, ctx);
|
|
1563
1489
|
return;
|
|
1564
1490
|
}
|
|
1491
|
+
// §5.3: kimi 压缩在途检测(wire 状态机)。第二次点击在本地毫秒级如实回答,
|
|
1492
|
+
// 不开卡、不发 session/prompt——否则 kimi 服务端拒绝并产生「秒回」假完成。
|
|
1493
|
+
if (await this.rejectIfCompactionInFlight(last.sessionId, cwd, last.agentKind, ctx))
|
|
1494
|
+
return;
|
|
1565
1495
|
// Check that the runner has runCompact method
|
|
1566
1496
|
const runner = this.getRunner(cwd, last.agentKind);
|
|
1567
|
-
if (!(
|
|
1568
|
-
typeof runner.runCompact !== 'function') {
|
|
1497
|
+
if (!runnerHasRunCompact(runner)) {
|
|
1569
1498
|
await this.sendResult({ text: '⚠️ 当前运行模式不支持 Compact' }, ctx);
|
|
1570
1499
|
return;
|
|
1571
1500
|
}
|
|
@@ -1578,6 +1507,36 @@ export class Bridge {
|
|
|
1578
1507
|
ctx,
|
|
1579
1508
|
});
|
|
1580
1509
|
}
|
|
1510
|
+
/**
|
|
1511
|
+
* §5.3: kimi 压缩在途检测——reader 鸭子实现 `readCompactionState` 时,若
|
|
1512
|
+
* wire 状态机判定当前有未终结的压缩(最后一次 begin 无 terminal 记录),
|
|
1513
|
+
* 直接回文本「已有一次 Compact 正在进行」并返回 true(调用方不开卡、不发
|
|
1514
|
+
* session/prompt)。codex/claude 等 reader 无此方法 → 返回 false,行为不变。
|
|
1515
|
+
*/
|
|
1516
|
+
async rejectIfCompactionInFlight(sessionId, cwd, agentKind, ctx) {
|
|
1517
|
+
const reader = this.sessionReaderRegistry.get(agentKind);
|
|
1518
|
+
if (!reader || !('readCompactionState' in reader))
|
|
1519
|
+
return false;
|
|
1520
|
+
try {
|
|
1521
|
+
const state = reader.readCompactionState(sessionId, cwd);
|
|
1522
|
+
if (state?.inFlight) {
|
|
1523
|
+
const since = state.inFlightSince;
|
|
1524
|
+
let timeLabel = '未知时刻';
|
|
1525
|
+
if (since !== undefined) {
|
|
1526
|
+
const d = new Date(since);
|
|
1527
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
1528
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
1529
|
+
timeLabel = `${hh}:${mm}`;
|
|
1530
|
+
}
|
|
1531
|
+
await this.sendResult({ text: `⚠️ 已有一次 Compact 正在进行(开始于 ${timeLabel}),请等待完成` }, ctx);
|
|
1532
|
+
return true;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
catch (err) {
|
|
1536
|
+
getLogger().warn(`[bridge] readCompactionState failed sessionId=${sessionId}: ${errorMessage(err)}`);
|
|
1537
|
+
}
|
|
1538
|
+
return false;
|
|
1539
|
+
}
|
|
1581
1540
|
/**
|
|
1582
1541
|
* Stream a compaction to a card: start a RunCardSession with
|
|
1583
1542
|
* operationKind='compaction' (no recursive Compact button), consume
|
|
@@ -1607,9 +1566,6 @@ export class Bridge {
|
|
|
1607
1566
|
replyTo: ctx.messageId,
|
|
1608
1567
|
runId: compactRunId,
|
|
1609
1568
|
renderOptions: {
|
|
1610
|
-
showThinking: this.config.output.showThinking,
|
|
1611
|
-
showToolUse: this.config.output.showToolUse,
|
|
1612
|
-
showToolResult: this.config.output.showToolResult,
|
|
1613
1569
|
agentKind,
|
|
1614
1570
|
},
|
|
1615
1571
|
});
|
|
@@ -1623,22 +1579,34 @@ export class Bridge {
|
|
|
1623
1579
|
operationKind: 'compaction',
|
|
1624
1580
|
});
|
|
1625
1581
|
let sawResult = false;
|
|
1582
|
+
let resultSubtype;
|
|
1583
|
+
let resultError;
|
|
1626
1584
|
for await (const event of runner.runCompact('', { cwd, sessionId })) {
|
|
1627
|
-
if (event.type === 'result')
|
|
1585
|
+
if (event.type === 'result') {
|
|
1628
1586
|
sawResult = true;
|
|
1587
|
+
resultSubtype = event.subtype;
|
|
1588
|
+
resultError = event.errorMessage;
|
|
1589
|
+
}
|
|
1629
1590
|
await cardSession.push(event);
|
|
1630
1591
|
}
|
|
1592
|
+
// §5.3: 卡片终态必须等于引擎真实终态。旧实现只看 sawResult 布尔——error
|
|
1593
|
+
// subtype 的 result 也被 finish 'done'(放大器 bug,2026-08-31 事故)。
|
|
1594
|
+
const terminal = resultSubtype === 'success'
|
|
1595
|
+
? 'done'
|
|
1596
|
+
: resultSubtype === 'interrupted'
|
|
1597
|
+
? 'interrupted'
|
|
1598
|
+
: 'error';
|
|
1631
1599
|
// 压缩结束后从会话 jsonl 读权威统计(压缩后上下文、压缩次数、本次压缩
|
|
1632
1600
|
// token 消耗与会话累计)。thread/compacted 通知与 jsonl 落盘几乎同时
|
|
1633
1601
|
// (实测差 ~9ms),compactCount 未读到则短重试,仍失败优雅降级(无统计)。
|
|
1634
|
-
let finalUsage = this.
|
|
1602
|
+
let finalUsage = resolveFinalUsage(this.sessionReaderRegistry, this.config.defaultAgent, sessionId, cwd, agentKind);
|
|
1635
1603
|
for (let attempt = 0; !finalUsage?.compactCount && attempt < 2; attempt++) {
|
|
1636
1604
|
await sleep(150);
|
|
1637
|
-
finalUsage = this.
|
|
1605
|
+
finalUsage = resolveFinalUsage(this.sessionReaderRegistry, this.config.defaultAgent, sessionId, cwd, agentKind);
|
|
1638
1606
|
}
|
|
1639
|
-
await cardSession.finish(
|
|
1640
|
-
resultSubtype:
|
|
1641
|
-
errorMsg:
|
|
1607
|
+
await cardSession.finish(terminal, {
|
|
1608
|
+
resultSubtype: resultSubtype ?? 'error',
|
|
1609
|
+
errorMsg: terminal === 'error' ? (resultError ?? '未收到压缩结果') : undefined,
|
|
1642
1610
|
contextLength: finalUsage?.contextLength,
|
|
1643
1611
|
contextLimit: finalUsage?.contextLimit,
|
|
1644
1612
|
compactPreContextLength: finalUsage?.compactPreContextLength,
|
|
@@ -1654,7 +1622,7 @@ export class Bridge {
|
|
|
1654
1622
|
cumulativeCacheReadTokens: finalUsage?.cumulativeCacheReadTokens,
|
|
1655
1623
|
cumulativeCacheCreationTokens: finalUsage?.cumulativeCacheCreationTokens,
|
|
1656
1624
|
});
|
|
1657
|
-
log.info(`[bridge] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult}`);
|
|
1625
|
+
log.info(`[bridge] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult} subtype=${resultSubtype ?? 'none'}`);
|
|
1658
1626
|
}
|
|
1659
1627
|
catch (err) {
|
|
1660
1628
|
log.error(`[bridge] streamCodexCompact failed: ${errorMessage(err)}`);
|
|
@@ -1709,10 +1677,12 @@ export class Bridge {
|
|
|
1709
1677
|
await this.sendResult({ text: `⚠️ 未找到 session ${sessionId}(当前目录: ${cwd})` }, ctx);
|
|
1710
1678
|
return;
|
|
1711
1679
|
}
|
|
1680
|
+
// §5.3: kimi 压缩在途检测(与 handleCodexCompact 同一入口)。
|
|
1681
|
+
if (await this.rejectIfCompactionInFlight(sessionId, cwd, agentKind, ctx))
|
|
1682
|
+
return;
|
|
1712
1683
|
// Check that the runner has runCompact(codex/kimi/opencode/pi/claude 鸭子探测)。
|
|
1713
1684
|
const runner = this.getRunner(cwd, agentKind);
|
|
1714
|
-
if (!(
|
|
1715
|
-
typeof runner.runCompact !== 'function') {
|
|
1685
|
+
if (!runnerHasRunCompact(runner)) {
|
|
1716
1686
|
await this.sendResult({ text: '⚠️ 当前运行模式不支持 Compact' }, ctx);
|
|
1717
1687
|
return;
|
|
1718
1688
|
}
|
|
@@ -1852,7 +1822,4 @@ export class Bridge {
|
|
|
1852
1822
|
}
|
|
1853
1823
|
}
|
|
1854
1824
|
}
|
|
1855
|
-
function errorMessage(error) {
|
|
1856
|
-
return error instanceof Error ? error.message : String(error);
|
|
1857
|
-
}
|
|
1858
1825
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getLogger } from '../logger/index.js';
|
|
2
|
+
import { displayName } from '../platform/path.js';
|
|
2
3
|
/**
|
|
3
4
|
* Per-workspace cap on the number of tasks WAITING to execute (review P2-5).
|
|
4
5
|
* Without a bound, a message flood grows `queuedTasks` / the promise chain /
|
|
@@ -377,7 +378,7 @@ export class QueueManager {
|
|
|
377
378
|
}
|
|
378
379
|
/** Build queue status card elements. */
|
|
379
380
|
buildQueueStatusCardElements(workspace, actualPosition, tasksAhead, messageId, messagePreview) {
|
|
380
|
-
const workspaceName = workspace
|
|
381
|
+
const workspaceName = displayName(workspace);
|
|
381
382
|
const isRunning = this.isWorkspaceRunning(workspace);
|
|
382
383
|
const elements = [
|
|
383
384
|
{
|
|
@@ -471,7 +472,7 @@ export class QueueManager {
|
|
|
471
472
|
getLogger().debug(`[queue-manager] no queue card to update, messageId=${messageId}`);
|
|
472
473
|
return;
|
|
473
474
|
}
|
|
474
|
-
const workspaceName = workspace
|
|
475
|
+
const workspaceName = displayName(workspace);
|
|
475
476
|
try {
|
|
476
477
|
// Prefer the live preview: an edited task's messagePreview is updated in
|
|
477
478
|
// place, and the passed-in snapshot may be stale if the card send was in
|
|
@@ -547,7 +548,7 @@ export class QueueManager {
|
|
|
547
548
|
getLogger().debug(`[queue-manager] no queue card to update (cancelled), messageId=${messageId}`);
|
|
548
549
|
return;
|
|
549
550
|
}
|
|
550
|
-
const workspaceName = workspace
|
|
551
|
+
const workspaceName = displayName(workspace);
|
|
551
552
|
const card = {
|
|
552
553
|
schema: '2.0',
|
|
553
554
|
config: { wide_screen_mode: true },
|