lark-remote 0.1.7 → 0.1.9
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 +46 -0
- package/dist/bridge/approval-coordinator.d.ts +30 -1
- package/dist/bridge/approval-coordinator.js +93 -5
- package/dist/bridge/index.d.ts +36 -6
- package/dist/bridge/index.js +56 -10
- package/dist/card/approval-render.js +287 -48
- package/dist/card/card-shared.d.ts +6 -4
- package/dist/card/card-shared.js +6 -4
- package/dist/card/run-renderer.d.ts +7 -0
- package/dist/card/run-renderer.js +22 -11
- package/dist/config/index.d.ts +24 -1
- package/dist/config/index.js +34 -3
- package/dist/index.js +40 -16
- package/dist/order/alias-resolve.d.ts +6 -2
- package/dist/order/index.d.ts +9 -2
- package/dist/order/index.js +33 -0
- package/dist/router/card-action-payload.d.ts +30 -0
- package/dist/router/card-action-payload.js +39 -0
- package/dist/router/config/claude.js +8 -3
- package/dist/router/config/dsh.d.ts +51 -0
- package/dist/router/config/dsh.js +179 -0
- package/dist/router/config/index.d.ts +1 -1
- package/dist/router/config/index.js +4 -1
- package/dist/router/config/types.d.ts +6 -0
- package/dist/router/index.d.ts +39 -4
- package/dist/router/index.js +471 -90
- package/dist/router/utils.js +12 -2
- package/dist/runner/agent-choices-common.js +3 -0
- package/dist/runner/claude/runner.js +22 -0
- package/dist/runner/claude/session.d.ts +26 -0
- package/dist/runner/claude/session.js +105 -9
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -0
- package/dist/runner/codex/app-server/protocol-types.js +1 -1
- package/dist/runner/codex/app-server/runner.d.ts +10 -0
- package/dist/runner/codex/app-server/runner.js +56 -1
- package/dist/runner/codex/app-server/translator.d.ts +7 -0
- package/dist/runner/codex/app-server/translator.js +45 -0
- package/dist/runner/common/acp/protocol-types.d.ts +33 -0
- package/dist/runner/common/acp/protocol-types.js +2 -0
- package/dist/runner/common/connection-based-runner.js +10 -5
- package/dist/runner/dsh/client.d.ts +95 -0
- package/dist/runner/dsh/client.js +418 -0
- package/dist/runner/dsh/fake-dsh-server.d.ts +60 -0
- package/dist/runner/dsh/fake-dsh-server.js +156 -0
- package/dist/runner/dsh/index.d.ts +3 -0
- package/dist/runner/dsh/index.js +4 -0
- package/dist/runner/dsh/runner.d.ts +57 -0
- package/dist/runner/dsh/runner.js +211 -0
- package/dist/runner/dsh/translator.d.ts +28 -0
- package/dist/runner/dsh/translator.js +98 -0
- package/dist/runner/dsh/types.d.ts +155 -0
- package/dist/runner/dsh/types.js +14 -0
- package/dist/runner/index.d.ts +1 -0
- package/dist/runner/index.js +1 -0
- package/dist/runner/kimi/acp/runner.js +84 -1
- package/dist/runner/kimi/acp/translator.d.ts +14 -0
- package/dist/runner/kimi/acp/translator.js +108 -3
- package/dist/runner/pi/rpc/runner.d.ts +1 -7
- package/dist/runner/pi/rpc/runner.js +8 -12
- package/dist/runner/probe.js +6 -1
- package/dist/runner/question-common.d.ts +26 -0
- package/dist/runner/question-common.js +42 -0
- package/dist/runner/types.d.ts +94 -10
- package/dist/session/claude/sessions.js +37 -15
- package/dist/session/dsh/index.d.ts +1 -0
- package/dist/session/dsh/index.js +2 -0
- package/dist/session/dsh/sessions.d.ts +44 -0
- package/dist/session/dsh/sessions.js +252 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/workspace/index.d.ts +12 -0
- package/dist/workspace/index.js +28 -2
- package/package.json +2 -1
- package/dist/order/alias-store.d.ts +0 -38
- package/dist/order/alias-store.js +0 -106
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ 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.9] - 2026-08-20
|
|
9
|
+
|
|
10
|
+
### 新增
|
|
11
|
+
|
|
12
|
+
- ExitPlanMode 计划审批:计划全文折叠展示 + 五决策对齐 TUI(自动放行 / 附意见 / 采纳修改)
|
|
13
|
+
- 卡片别名管理重构:`alias-store` 并入 `alias-resolve`,`/order` 卡片新增「+别名」交互(绑定/修改/删除,已绑定指令显示 `$name` 标签 + ✕)
|
|
14
|
+
- DSH / connection-based runner 启动 / 配置热更 / 失败路径修复(CC-01~CC-08)
|
|
15
|
+
|
|
16
|
+
### 变更
|
|
17
|
+
|
|
18
|
+
- 别名 API 由「触发词 → 文本」改为「指令 → 别名」:`/order alias add <orderId|序号> <别名>` 绑定、`/order alias rm` 移除;别名随指令持久化在 `orders.json`,删除指令时别名一并删除;名称不能为保留子命令、不能数字开头
|
|
19
|
+
- 用户文档同步别名卡片化交互,移除 `/order alias <name> <text>` 旧用法
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- 别名卡片成功回调:携带 `card` 替换 pre-click 卡,删除文案对齐新交互
|
|
24
|
+
- kimi ACP runner 模型下发:迁移到纯 ACP 后 `session/set_config_option` 必须紧跟 `session/new|resume` 下发,否则实际跑的是默认模型(CC-07)
|
|
25
|
+
|
|
26
|
+
## [0.1.8] - 2026-08-19
|
|
27
|
+
|
|
28
|
+
### 新增
|
|
29
|
+
|
|
30
|
+
- DSH(DeepSeek Harness)Agent 接入:纯 HTTP 连接 DSH Web Host(无本地子进程),支持会话创建/续跑、`selectModel` 模型对齐、SSE mux 事件订阅、会话历史读取
|
|
31
|
+
- `/config` DSH 配置卡:Host 地址 / 会话 preset / 模型 / 推理强度,模型与预设目录预取动态加载(失败回退固定兜底清单)
|
|
32
|
+
- AskUserQuestion 公共契约(`question-common`):标准提问审批事件工厂 + 按序答案映射,claude / codex / kimi / pi 统一接入
|
|
33
|
+
- Codex AskUserQuestion:支持自由文本题、补充说明(user_note)、`autoResolutionMs` 透传为单请求审批超时
|
|
34
|
+
- Kimi elicitation 表单回编 + request_permission 兜底桥
|
|
35
|
+
- 工具权限审批卡(`kind === 'tool'`):ExitPlanMode 等非命令工具展示工具名 + 用途说明,计划审批语义
|
|
36
|
+
- 提问卡单选/多选图标区分(⚪/🔵 vs ⬜/☑️)、自由文本题输入框、自定义答案回显
|
|
37
|
+
|
|
38
|
+
### 变更
|
|
39
|
+
|
|
40
|
+
- claude session usage 扫描对齐 codex 语义:非累计字段为末轮(本 run)scope,累计走 `cumulative*` 字段
|
|
41
|
+
- Compact 按钮能力门控放开到所有 `runCompact` 能力 runner(claude / codex / kimi / opencode / pi)
|
|
42
|
+
- claude `/config` 权限模式下拉排除 `manual`(`default` 的别名,避免等价重复项)
|
|
43
|
+
- workspace 保存即触达:`save` 内部调用 `touch`,新保存的工作区排在列表最前
|
|
44
|
+
- DSH 可用性探测跳过二进制检查(HTTP-only Agent)
|
|
45
|
+
|
|
46
|
+
### 修复
|
|
47
|
+
|
|
48
|
+
- claude session usage 扫描:修复第三方网关(如 DeepSeek)将零 usage 写在占位行、真实 usage 只在末行导致的 per-run 统计全 0(改为按 message id 逐字段 max 聚合)
|
|
49
|
+
- 审批答案重复投递(重复 nonce 二次点击)改为中性提示,不再误报"提交失败"
|
|
50
|
+
- usage 统计一致性护栏:本 run 超出累计时显式标记 `⚠️ 累计异常`(只标记不修正,便于排查数据源问题)
|
|
51
|
+
|
|
8
52
|
## [0.1.7] - 2026-08-18
|
|
9
53
|
|
|
10
54
|
### 新增
|
|
@@ -175,3 +219,5 @@ Initial release.
|
|
|
175
219
|
[0.1.4]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.4
|
|
176
220
|
[0.1.5]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.5
|
|
177
221
|
[0.1.6]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.6
|
|
222
|
+
[0.1.7]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.7
|
|
223
|
+
[0.1.8]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.8
|
|
@@ -13,13 +13,17 @@ import type { ApprovalAction } from '../runner/types.js';
|
|
|
13
13
|
type ApprovalState = 'pending' | 'submitting' | 'resolved' | 'failed' | 'expired';
|
|
14
14
|
export interface TrackedApproval {
|
|
15
15
|
requestId: number | string;
|
|
16
|
-
kind: 'command' | 'file' | 'permissions' | 'question';
|
|
16
|
+
kind: 'command' | 'file' | 'permissions' | 'question' | 'tool';
|
|
17
17
|
state: ApprovalState;
|
|
18
18
|
view: ApprovalView;
|
|
19
19
|
createdAt: number;
|
|
20
20
|
timeoutTimer: ReturnType<typeof setTimeout> | null;
|
|
21
21
|
/** AskUserQuestion 已答问题索引 → 答案 label[](kind === 'question')。 */
|
|
22
22
|
answers?: Map<number, string[]>;
|
|
23
|
+
/** AskUserQuestion 补充说明(Codex user_note):问题索引 → 文本。 */
|
|
24
|
+
notes?: Map<number, string>;
|
|
25
|
+
/** 计划审批修改意见(ExitPlanMode):planFeedback 回流,供反馈类决策注入。 */
|
|
26
|
+
feedback?: string;
|
|
23
27
|
}
|
|
24
28
|
type ApprovalResponder = (requestId: number | string, response: unknown) => Promise<void>;
|
|
25
29
|
export type { ApprovalAction } from '../runner/types.js';
|
|
@@ -101,6 +105,29 @@ export declare class ApprovalCoordinator {
|
|
|
101
105
|
requestId: number | string;
|
|
102
106
|
nonce?: string;
|
|
103
107
|
}): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* AskUserQuestion 补充说明(Codex user_note):选项之外的可选文本,随答案
|
|
110
|
+
* 提交;不单独构成答案(仍需选项/自定义答案才能完成该问题)。note 更新
|
|
111
|
+
* 后回流视图(卡片回显),同一请求可多次修改(新 nonce 来自新渲染)。
|
|
112
|
+
*/
|
|
113
|
+
answerNote(action: {
|
|
114
|
+
questionIndex: number;
|
|
115
|
+
text: string;
|
|
116
|
+
}, ctx: {
|
|
117
|
+
requestId: number | string;
|
|
118
|
+
nonce?: string;
|
|
119
|
+
}): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* 计划审批修改意见(ExitPlanMode):输入框提交 → coordinator 记录并回流
|
|
122
|
+
* 卡片回显,随后「拒绝并附意见」/「批准并采纳修改」按钮复用该文本。
|
|
123
|
+
* 同一请求可多次修改(新 nonce 来自新渲染)。
|
|
124
|
+
*/
|
|
125
|
+
planFeedback(action: {
|
|
126
|
+
text: string;
|
|
127
|
+
}, ctx: {
|
|
128
|
+
requestId: number | string;
|
|
129
|
+
nonce?: string;
|
|
130
|
+
}): Promise<void>;
|
|
104
131
|
/**
|
|
105
132
|
* Update a pending approval's view (out-of-order item/started arrives late).
|
|
106
133
|
* Only applies while the approval is still pending — resolved/expired
|
|
@@ -118,6 +145,8 @@ export declare class ApprovalCoordinator {
|
|
|
118
145
|
private recordAnswer;
|
|
119
146
|
private getPendingQuestion;
|
|
120
147
|
private allQuestionsAnswered;
|
|
148
|
+
/** 推送审批卡视图更新(approval_view_updated 重渲染;失败仅记日志)。 */
|
|
149
|
+
private pushApprovalViewUpdate;
|
|
121
150
|
/** 推送 question 卡片视图更新(选项勾选状态变化)。 */
|
|
122
151
|
private pushQuestionViewUpdate;
|
|
123
152
|
/** 全部问题已答:构建 answers 并提交(非空校验 + 原子状态迁移)。 */
|
|
@@ -37,9 +37,12 @@ export class ApprovalCoordinator {
|
|
|
37
37
|
existing.view = event.view;
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
+
// 事件级 timeoutMs 优先(Codex autoResolutionMs / Pi extension timeout),
|
|
41
|
+
// 缺省回落 run 级默认超时。放在事件上而非桥按 agentKind 分支,公共层
|
|
42
|
+
// 保持无 agent 分支。
|
|
40
43
|
const timer = setTimeout(() => {
|
|
41
44
|
this.expireApproval(event.requestId);
|
|
42
|
-
}, this.approvalTimeoutMs);
|
|
45
|
+
}, event.timeoutMs ?? this.approvalTimeoutMs);
|
|
43
46
|
const tracked = {
|
|
44
47
|
requestId: event.requestId,
|
|
45
48
|
kind: event.kind,
|
|
@@ -94,12 +97,33 @@ export class ApprovalCoordinator {
|
|
|
94
97
|
if (!tracked.view.availableDecisions.includes(decision) && !safetyOverride) {
|
|
95
98
|
throw new Error(`Decision "${decision}" not in available decisions: ${tracked.view.availableDecisions.join(', ')}`);
|
|
96
99
|
}
|
|
100
|
+
// 反馈类决策:注入用户已填写的修改意见(卡片输入框经 planFeedback 回流到
|
|
101
|
+
// coordinator)。decisionToApprovalAction 只带决策名,payload 在此补齐;
|
|
102
|
+
// 调用方直接传完整 payload 时以显式值为准。
|
|
103
|
+
let effectiveAction = action;
|
|
104
|
+
if (action.action === 'decline_with_feedback' || action.action === 'accept_with_feedback') {
|
|
105
|
+
const explicit = action.action === 'decline_with_feedback' ? action.message : action.plan;
|
|
106
|
+
const text = explicit.trim() || tracked.feedback?.trim() || '';
|
|
107
|
+
if (!text) {
|
|
108
|
+
throw new Error('请先填写修改意见');
|
|
109
|
+
}
|
|
110
|
+
effectiveAction =
|
|
111
|
+
action.action === 'decline_with_feedback'
|
|
112
|
+
? { action: 'decline_with_feedback', message: text }
|
|
113
|
+
: {
|
|
114
|
+
action: 'accept_with_feedback',
|
|
115
|
+
// 批准并采纳修改:新计划 = 原计划 + 用户意见(无原计划时意见即计划)。
|
|
116
|
+
plan: tracked.view.plan?.trim()
|
|
117
|
+
? `${tracked.view.plan.trim()}\n\n## 用户修改意见\n${text}`
|
|
118
|
+
: `# 用户修改意见\n\n${text}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
97
121
|
// Atomic state transition: pending → submitting(同步,无 await)
|
|
98
122
|
tracked.state = 'submitting';
|
|
99
123
|
this.clearTimer(tracked);
|
|
100
124
|
// Send response via the responder
|
|
101
125
|
try {
|
|
102
|
-
await this.responder(ctx.requestId,
|
|
126
|
+
await this.responder(ctx.requestId, effectiveAction);
|
|
103
127
|
tracked.state = 'resolved';
|
|
104
128
|
// 提交成功后立即从卡片移除审批区(agent 无关):claude 控制协议没有
|
|
105
129
|
// resolved 回发,不推就会「已点允许按钮仍在」残留 UI;codex 的 server
|
|
@@ -224,6 +248,50 @@ export class ApprovalCoordinator {
|
|
|
224
248
|
}
|
|
225
249
|
this.pushQuestionViewUpdate(ctx.requestId, tracked);
|
|
226
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* AskUserQuestion 补充说明(Codex user_note):选项之外的可选文本,随答案
|
|
253
|
+
* 提交;不单独构成答案(仍需选项/自定义答案才能完成该问题)。note 更新
|
|
254
|
+
* 后回流视图(卡片回显),同一请求可多次修改(新 nonce 来自新渲染)。
|
|
255
|
+
*/
|
|
256
|
+
async answerNote(action, ctx) {
|
|
257
|
+
const tracked = this.getPendingQuestion(ctx.requestId);
|
|
258
|
+
const questions = tracked.view.questions;
|
|
259
|
+
const q = questions[action.questionIndex];
|
|
260
|
+
if (!q) {
|
|
261
|
+
throw new Error(`Question ${action.questionIndex} not found in request ${ctx.requestId}`);
|
|
262
|
+
}
|
|
263
|
+
const text = action.text.trim();
|
|
264
|
+
if (!text) {
|
|
265
|
+
throw new Error('请输入补充说明');
|
|
266
|
+
}
|
|
267
|
+
this.assertFreshNonce(ctx.requestId, ctx.nonce);
|
|
268
|
+
tracked.notes ??= new Map();
|
|
269
|
+
tracked.notes.set(action.questionIndex, text);
|
|
270
|
+
q.note = text;
|
|
271
|
+
this.pushQuestionViewUpdate(ctx.requestId, tracked);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 计划审批修改意见(ExitPlanMode):输入框提交 → coordinator 记录并回流
|
|
275
|
+
* 卡片回显,随后「拒绝并附意见」/「批准并采纳修改」按钮复用该文本。
|
|
276
|
+
* 同一请求可多次修改(新 nonce 来自新渲染)。
|
|
277
|
+
*/
|
|
278
|
+
async planFeedback(action, ctx) {
|
|
279
|
+
const tracked = this.approvals.get(ctx.requestId);
|
|
280
|
+
if (!tracked) {
|
|
281
|
+
throw new Error(`Approval request ${ctx.requestId} not found`);
|
|
282
|
+
}
|
|
283
|
+
if (tracked.state !== 'pending') {
|
|
284
|
+
throw new Error(`Approval request ${ctx.requestId} is no longer pending`);
|
|
285
|
+
}
|
|
286
|
+
const text = action.text.trim();
|
|
287
|
+
if (!text) {
|
|
288
|
+
throw new Error('请输入修改意见');
|
|
289
|
+
}
|
|
290
|
+
this.assertFreshNonce(ctx.requestId, ctx.nonce);
|
|
291
|
+
tracked.feedback = text;
|
|
292
|
+
tracked.view.planFeedback = text;
|
|
293
|
+
this.pushApprovalViewUpdate(ctx.requestId, tracked);
|
|
294
|
+
}
|
|
227
295
|
/**
|
|
228
296
|
* Update a pending approval's view (out-of-order item/started arrives late).
|
|
229
297
|
* Only applies while the approval is still pending — resolved/expired
|
|
@@ -295,8 +363,8 @@ export class ApprovalCoordinator {
|
|
|
295
363
|
}
|
|
296
364
|
return true;
|
|
297
365
|
}
|
|
298
|
-
/**
|
|
299
|
-
|
|
366
|
+
/** 推送审批卡视图更新(approval_view_updated 重渲染;失败仅记日志)。 */
|
|
367
|
+
pushApprovalViewUpdate(requestId, tracked) {
|
|
300
368
|
void this.pushToCard([
|
|
301
369
|
{
|
|
302
370
|
type: 'approval_view_updated',
|
|
@@ -308,6 +376,10 @@ export class ApprovalCoordinator {
|
|
|
308
376
|
getLogger().warn(`[approval-coordinator] question view update failed requestId=${requestId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
309
377
|
});
|
|
310
378
|
}
|
|
379
|
+
/** 推送 question 卡片视图更新(选项勾选状态变化)。 */
|
|
380
|
+
pushQuestionViewUpdate(requestId, tracked) {
|
|
381
|
+
this.pushApprovalViewUpdate(requestId, tracked);
|
|
382
|
+
}
|
|
311
383
|
/** 全部问题已答:构建 answers 并提交(非空校验 + 原子状态迁移)。 */
|
|
312
384
|
async respondQuestionAnswers(tracked, ctx) {
|
|
313
385
|
this.assertFreshNonce(ctx.requestId, ctx.nonce);
|
|
@@ -316,14 +388,22 @@ export class ApprovalCoordinator {
|
|
|
316
388
|
}
|
|
317
389
|
const questions = tracked.view.questions ?? [];
|
|
318
390
|
const answers = {};
|
|
391
|
+
const notes = {};
|
|
319
392
|
for (let i = 0; i < questions.length; i++) {
|
|
320
393
|
const selected = tracked.answers?.get(i) ?? [];
|
|
321
394
|
answers[questions[i].question] = questions[i].multiSelect ? selected : selected[0];
|
|
395
|
+
const note = tracked.notes?.get(i);
|
|
396
|
+
if (note)
|
|
397
|
+
notes[questions[i].question] = note;
|
|
322
398
|
}
|
|
323
399
|
tracked.state = 'submitting';
|
|
324
400
|
this.clearTimer(tracked);
|
|
325
401
|
try {
|
|
326
|
-
await this.responder(ctx.requestId, {
|
|
402
|
+
await this.responder(ctx.requestId, {
|
|
403
|
+
action: 'answer',
|
|
404
|
+
answers,
|
|
405
|
+
...(Object.keys(notes).length > 0 ? { notes } : {}),
|
|
406
|
+
});
|
|
327
407
|
tracked.state = 'resolved';
|
|
328
408
|
this.pushResolved(ctx.requestId);
|
|
329
409
|
}
|
|
@@ -380,6 +460,10 @@ export class ApprovalCoordinator {
|
|
|
380
460
|
return 'acceptAll';
|
|
381
461
|
case 'answer':
|
|
382
462
|
return 'answer';
|
|
463
|
+
case 'decline_with_feedback':
|
|
464
|
+
return 'declineWithFeedback';
|
|
465
|
+
case 'accept_with_feedback':
|
|
466
|
+
return 'acceptWithFeedback';
|
|
383
467
|
case 'decline':
|
|
384
468
|
return 'decline';
|
|
385
469
|
case 'cancel':
|
|
@@ -398,6 +482,10 @@ export function decisionToApprovalAction(decision) {
|
|
|
398
482
|
return { action: 'accept_with_execpolicy_amendment' };
|
|
399
483
|
case 'acceptAll':
|
|
400
484
|
return { action: 'accept_all' };
|
|
485
|
+
case 'declineWithFeedback':
|
|
486
|
+
return { action: 'decline_with_feedback', message: '' };
|
|
487
|
+
case 'acceptWithFeedback':
|
|
488
|
+
return { action: 'accept_with_feedback', plan: '' };
|
|
401
489
|
case 'decline':
|
|
402
490
|
return { action: 'decline' };
|
|
403
491
|
case 'cancel':
|
package/dist/bridge/index.d.ts
CHANGED
|
@@ -121,6 +121,13 @@ export declare class Bridge {
|
|
|
121
121
|
* blocked the queue forever.
|
|
122
122
|
*/
|
|
123
123
|
private runners;
|
|
124
|
+
/**
|
|
125
|
+
* Runner 槽位在「活跃运行期间配置变更」时被标 stale(CC-06/P1):clearRunners() 对
|
|
126
|
+
* 活跃 workspace 只标 stale、不立即 evict(避免误杀长驻连接),当前 run 结束后由
|
|
127
|
+
* finalizeRun 安全 evict+dispose,下一轮 getRunner 创建新配置的 runner。
|
|
128
|
+
* key 为 `${cwd}\u0000${agentKind}`。
|
|
129
|
+
*/
|
|
130
|
+
private staleRunnerSlots;
|
|
124
131
|
/** Queue manager for per-workspace serial processing queue. */
|
|
125
132
|
/** 普通消息队列管理器(public:测试注入 queue-card 状态用)。 */
|
|
126
133
|
queueManager: QueueManager;
|
|
@@ -129,8 +136,8 @@ export declare class Bridge {
|
|
|
129
136
|
/** Approval coordinators keyed by runId. */
|
|
130
137
|
private approvalCoordinators;
|
|
131
138
|
/**
|
|
132
|
-
*
|
|
133
|
-
* 已清空,Compact 需要它来校验 runId 并提供 sessionId。
|
|
139
|
+
* 最近一次已完成的、runner 有 runCompact 的 run(按 cwd)。run 结束后
|
|
140
|
+
* activeRuns 已清空,Compact 需要它来校验 runId 并提供 sessionId。
|
|
134
141
|
*/
|
|
135
142
|
private lastCompactableCodexRun;
|
|
136
143
|
/**
|
|
@@ -383,6 +390,16 @@ export declare class Bridge {
|
|
|
383
390
|
decision: string;
|
|
384
391
|
nonce: string;
|
|
385
392
|
}): Promise<void>;
|
|
393
|
+
/**
|
|
394
|
+
* 计划审批修改意见(ExitPlanMode 输入框):记录到 coordinator 并回流卡片。
|
|
395
|
+
* 「拒绝并附意见」/「批准并采纳修改」按钮复用该文本。
|
|
396
|
+
*/
|
|
397
|
+
handleApprovalPlanFeedback(opts: {
|
|
398
|
+
runId: string;
|
|
399
|
+
requestId: number | string;
|
|
400
|
+
text: string;
|
|
401
|
+
nonce: string;
|
|
402
|
+
}): Promise<void>;
|
|
386
403
|
/**
|
|
387
404
|
* Handle a permission toggle from a card button click.
|
|
388
405
|
* Routes to the ApprovalCoordinator for the given run.
|
|
@@ -426,15 +443,28 @@ export declare class Bridge {
|
|
|
426
443
|
nonce: string;
|
|
427
444
|
}): Promise<void>;
|
|
428
445
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
446
|
+
* Codex AskUserQuestion 补充说明(user_note,2026-08-18 live 验证模型理解
|
|
447
|
+
* "user_note: <text>" 条目):记录到 coordinator,随答案一起提交。
|
|
448
|
+
* 路由到 ApprovalCoordinator.answerNote。
|
|
449
|
+
*/
|
|
450
|
+
handleApprovalAnswerNote(opts: {
|
|
451
|
+
runId: string;
|
|
452
|
+
requestId: number | string;
|
|
453
|
+
questionIndex: number;
|
|
454
|
+
text: string;
|
|
455
|
+
nonce: string;
|
|
456
|
+
}): Promise<void>;
|
|
457
|
+
/**
|
|
458
|
+
* Handle codex.compact card action — trigger a compaction request for any
|
|
459
|
+
* runCompact-capable runner(codex/kimi/opencode/pi/claude,按
|
|
460
|
+
* lastCompactableCodexRun 记录的 agentKind 路由)。Validates the run exists
|
|
461
|
+
* and is in a terminal state, then calls the runner's runCompact().
|
|
432
462
|
*/
|
|
433
463
|
handleCodexCompact(value: {
|
|
434
464
|
runId?: string;
|
|
435
465
|
}, ctx: BridgeContext): Promise<void>;
|
|
436
466
|
/**
|
|
437
|
-
* Stream a
|
|
467
|
+
* Stream a compaction to a card: start a RunCardSession with
|
|
438
468
|
* operationKind='compaction' (no recursive Compact button), consume
|
|
439
469
|
* runner.runCompact() events, read authoritative jsonl usage, and finish
|
|
440
470
|
* the card. Shared by handleCodexCompact (run card button) and
|
package/dist/bridge/index.js
CHANGED
|
@@ -89,6 +89,13 @@ export class Bridge {
|
|
|
89
89
|
* blocked the queue forever.
|
|
90
90
|
*/
|
|
91
91
|
runners = new Map();
|
|
92
|
+
/**
|
|
93
|
+
* Runner 槽位在「活跃运行期间配置变更」时被标 stale(CC-06/P1):clearRunners() 对
|
|
94
|
+
* 活跃 workspace 只标 stale、不立即 evict(避免误杀长驻连接),当前 run 结束后由
|
|
95
|
+
* finalizeRun 安全 evict+dispose,下一轮 getRunner 创建新配置的 runner。
|
|
96
|
+
* key 为 `${cwd}\u0000${agentKind}`。
|
|
97
|
+
*/
|
|
98
|
+
staleRunnerSlots = new Set();
|
|
92
99
|
/** Queue manager for per-workspace serial processing queue. */
|
|
93
100
|
/** 普通消息队列管理器(public:测试注入 queue-card 状态用)。 */
|
|
94
101
|
queueManager;
|
|
@@ -97,8 +104,8 @@ export class Bridge {
|
|
|
97
104
|
/** Approval coordinators keyed by runId. */
|
|
98
105
|
approvalCoordinators = new Map();
|
|
99
106
|
/**
|
|
100
|
-
*
|
|
101
|
-
* 已清空,Compact 需要它来校验 runId 并提供 sessionId。
|
|
107
|
+
* 最近一次已完成的、runner 有 runCompact 的 run(按 cwd)。run 结束后
|
|
108
|
+
* activeRuns 已清空,Compact 需要它来校验 runId 并提供 sessionId。
|
|
102
109
|
*/
|
|
103
110
|
lastCompactableCodexRun = new Map();
|
|
104
111
|
/**
|
|
@@ -259,10 +266,18 @@ export class Bridge {
|
|
|
259
266
|
// keeping it is correct; finalizeRun deletes the cache entry when the run
|
|
260
267
|
// ends, and the next run picks up the new config/agent via getRunner.
|
|
261
268
|
for (const cwd of [...this.runners.keys()]) {
|
|
262
|
-
if (this.activeRuns.has(cwd))
|
|
269
|
+
if (this.activeRuns.has(cwd)) {
|
|
270
|
+
// CC-06/P1: 活跃 workspace 不能立即 evict(会误杀长驻连接),但必须标记 stale,
|
|
271
|
+
// 否则 finalizeRun 对 workspace-lifetime runner 也不 evict → 下一轮复用旧配置实例。
|
|
272
|
+
// 这里只标待淘汰,当前 run 结束后由 finalizeRun 安全 evict。
|
|
273
|
+
for (const kind of [...this.runners.get(cwd).keys()]) {
|
|
274
|
+
this.staleRunnerSlots.add(`${cwd}\u0000${kind}`);
|
|
275
|
+
}
|
|
263
276
|
continue;
|
|
277
|
+
}
|
|
264
278
|
for (const kind of [...this.runners.get(cwd).keys()]) {
|
|
265
279
|
this.evictRunnerSlot(cwd, kind);
|
|
280
|
+
this.staleRunnerSlots.delete(`${cwd}\u0000${kind}`);
|
|
266
281
|
}
|
|
267
282
|
}
|
|
268
283
|
}
|
|
@@ -287,6 +302,7 @@ export class Bridge {
|
|
|
287
302
|
void evicted.dispose();
|
|
288
303
|
}
|
|
289
304
|
workspaceMap.delete(kind);
|
|
305
|
+
this.staleRunnerSlots.delete(`${cwd}\u0000${kind}`);
|
|
290
306
|
if (workspaceMap.size === 0) {
|
|
291
307
|
this.runners.delete(cwd);
|
|
292
308
|
}
|
|
@@ -753,6 +769,9 @@ export class Bridge {
|
|
|
753
769
|
showToolResult: this.config.output.showToolResult,
|
|
754
770
|
// Run card header shows the current agent name (e.g. "Claude · 思考中")
|
|
755
771
|
agentKind,
|
|
772
|
+
// Compact 按钮能力门控与 resume 卡 / auto-resume 卡一致(hasRunCompact
|
|
773
|
+
// 鸭子探测):claude(stream-json,无 turn_started)也能拿到按钮。
|
|
774
|
+
compactSupported: this.hasRunCompact(cwd, agentKind),
|
|
756
775
|
},
|
|
757
776
|
});
|
|
758
777
|
const activeRun = {
|
|
@@ -1111,7 +1130,10 @@ export class Bridge {
|
|
|
1111
1130
|
// 由 formatUsageStats 的 max/兜底公式处理。
|
|
1112
1131
|
}
|
|
1113
1132
|
else {
|
|
1114
|
-
// codex(jsonl 优先)或无 live usage → jsonl 兜底所有 flow
|
|
1133
|
+
// codex(jsonl 优先)或无 live usage → jsonl 兜底所有 flow 字段。
|
|
1134
|
+
// jsonl 的非累计字段已是末轮(本 run)scope(claude reader 修复后对齐
|
|
1135
|
+
// codex `last_token_usage` 语义,见 scalarScan),不再是 session 累计;
|
|
1136
|
+
// session 累计只经 cumulative* 字段透传,卡片"本 run ≤ 累计"不变量成立。
|
|
1115
1137
|
finalInputTokens = finalUsage?.inputTokens ?? liveInputTokens;
|
|
1116
1138
|
finalOutputTokens = finalUsage?.outputTokens ?? liveOutputTokens;
|
|
1117
1139
|
finalCacheReadTokens = finalUsage?.cacheReadTokens ?? finalCacheReadTokens;
|
|
@@ -1296,9 +1318,13 @@ export class Bridge {
|
|
|
1296
1318
|
// evicted+disposed by clearRunners (/config) and interruptCurrentRun
|
|
1297
1319
|
// (/stop). Spawn-per-message runners are evicted as before to prevent
|
|
1298
1320
|
// stale process references accumulating.
|
|
1299
|
-
|
|
1321
|
+
// CC-06/P1: 配置变更期间(clearRunners 标了 stale)的 workspace-lifetime
|
|
1322
|
+
// runner 也必须在 run 结束后 evict,否则下一轮复用旧配置实例。
|
|
1323
|
+
const slotKey = `${cwd}\u0000${activeRun.agentKind}`;
|
|
1324
|
+
if (activeRun.runner.lifetime !== 'workspace' || this.staleRunnerSlots.has(slotKey)) {
|
|
1300
1325
|
this.evictRunnerSlot(cwd, activeRun.agentKind);
|
|
1301
1326
|
}
|
|
1327
|
+
this.staleRunnerSlots.delete(slotKey);
|
|
1302
1328
|
// Compact 卡片需要 runId + sessionId;record 供终态卡上的 Compact 按钮
|
|
1303
1329
|
// 校验与取参(activeRuns 已清空,无法再查到)。异常退出(error/
|
|
1304
1330
|
// interrupted/idle_timeout)同样记录——上下文往往更大,压缩后再续。
|
|
@@ -1441,6 +1467,15 @@ export class Bridge {
|
|
|
1441
1467
|
await coordinator.submit(action, { requestId: opts.requestId, nonce: opts.nonce });
|
|
1442
1468
|
});
|
|
1443
1469
|
}
|
|
1470
|
+
/**
|
|
1471
|
+
* 计划审批修改意见(ExitPlanMode 输入框):记录到 coordinator 并回流卡片。
|
|
1472
|
+
* 「拒绝并附意见」/「批准并采纳修改」按钮复用该文本。
|
|
1473
|
+
*/
|
|
1474
|
+
async handleApprovalPlanFeedback(opts) {
|
|
1475
|
+
return this.withCoordinator(opts.runId, async (coordinator) => {
|
|
1476
|
+
await coordinator.planFeedback({ text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce });
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1444
1479
|
/**
|
|
1445
1480
|
* Handle a permission toggle from a card button click.
|
|
1446
1481
|
* Routes to the ApprovalCoordinator for the given run.
|
|
@@ -1485,9 +1520,20 @@ export class Bridge {
|
|
|
1485
1520
|
return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerCustom({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
|
|
1486
1521
|
}
|
|
1487
1522
|
/**
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1523
|
+
* Codex AskUserQuestion 补充说明(user_note,2026-08-18 live 验证模型理解
|
|
1524
|
+
* "user_note: <text>" 条目):记录到 coordinator,随答案一起提交。
|
|
1525
|
+
* 路由到 ApprovalCoordinator.answerNote。
|
|
1526
|
+
*/
|
|
1527
|
+
async handleApprovalAnswerNote(opts) {
|
|
1528
|
+
const log = getLogger();
|
|
1529
|
+
log.info(`[bridge] handleApprovalAnswerNote runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
|
|
1530
|
+
return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerNote({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Handle codex.compact card action — trigger a compaction request for any
|
|
1534
|
+
* runCompact-capable runner(codex/kimi/opencode/pi/claude,按
|
|
1535
|
+
* lastCompactableCodexRun 记录的 agentKind 路由)。Validates the run exists
|
|
1536
|
+
* and is in a terminal state, then calls the runner's runCompact().
|
|
1491
1537
|
*/
|
|
1492
1538
|
async handleCodexCompact(value, ctx) {
|
|
1493
1539
|
const log = getLogger();
|
|
@@ -1529,7 +1575,7 @@ export class Bridge {
|
|
|
1529
1575
|
});
|
|
1530
1576
|
}
|
|
1531
1577
|
/**
|
|
1532
|
-
* Stream a
|
|
1578
|
+
* Stream a compaction to a card: start a RunCardSession with
|
|
1533
1579
|
* operationKind='compaction' (no recursive Compact button), consume
|
|
1534
1580
|
* runner.runCompact() events, read authoritative jsonl usage, and finish
|
|
1535
1581
|
* the card. Shared by handleCodexCompact (run card button) and
|
|
@@ -1659,7 +1705,7 @@ export class Bridge {
|
|
|
1659
1705
|
await this.sendResult({ text: `⚠️ 未找到 session ${sessionId}(当前目录: ${cwd})` }, ctx);
|
|
1660
1706
|
return;
|
|
1661
1707
|
}
|
|
1662
|
-
// Check that the runner has runCompact
|
|
1708
|
+
// Check that the runner has runCompact(codex/kimi/opencode/pi/claude 鸭子探测)。
|
|
1663
1709
|
const runner = this.getRunner(cwd, agentKind);
|
|
1664
1710
|
if (!('runCompact' in runner) ||
|
|
1665
1711
|
typeof runner.runCompact !== 'function') {
|