lark-remote 0.5.9 → 0.5.12
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 +49 -0
- package/dist/binder.js +7 -1
- package/dist/bridge/index.d.ts +9 -0
- package/dist/bridge/index.js +28 -3
- package/dist/bridge/queue-manager.d.ts +31 -0
- package/dist/bridge/queue-manager.js +78 -0
- package/dist/card/approval-render.d.ts +14 -0
- package/dist/card/approval-render.js +155 -29
- package/dist/card/bash-card-session.js +8 -0
- package/dist/card/bash-renderer.js +11 -23
- package/dist/card/card-budget.js +24 -4
- package/dist/card/run-renderer.js +31 -9
- package/dist/common/text-width.js +5 -0
- package/dist/common/tool-summary.js +4 -2
- package/dist/config/codex-config.js +5 -3
- package/dist/config/index.js +13 -1
- package/dist/index.js +42 -24
- package/dist/instance-lock.d.ts +9 -1
- package/dist/instance-lock.js +42 -7
- package/dist/platform/identity.js +4 -3
- package/dist/platform/path.js +3 -1
- package/dist/platform/terminator-win32.d.ts +13 -1
- package/dist/platform/terminator-win32.js +4 -14
- package/dist/router/index.js +179 -122
- package/dist/runner/codex/app-server/runner.js +12 -0
- package/dist/runner/common/acp/base-translator.d.ts +12 -0
- package/dist/runner/common/acp/base-translator.js +22 -0
- package/dist/runner/common/jsonl-stream.js +12 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +6 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +34 -9
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -1
- package/dist/runner/common/jsonrpc/transport.js +7 -1
- package/dist/runner/dsh/approval.js +16 -3
- package/dist/runner/dsh/client.d.ts +31 -1
- package/dist/runner/dsh/client.js +126 -6
- package/dist/runner/dsh/exit-cancel.js +62 -23
- package/dist/runner/dsh/runner.d.ts +7 -0
- package/dist/runner/dsh/runner.js +42 -5
- package/dist/runner/kimi/acp/runner.js +42 -5
- package/dist/runner/kimi/acp/translator.d.ts +8 -0
- package/dist/runner/kimi/acp/translator.js +10 -0
- package/dist/runner/pi/rpc/runner.js +33 -5
- package/dist/runner/resolve-agent-choices.js +5 -1
- package/dist/runner/sync-agent-choices.js +4 -1
- package/dist/session/claude/session-index.js +9 -2
- package/dist/session/codex/rollout-reader.js +51 -26
- package/dist/session/common/usage-accumulator.d.ts +10 -1
- package/dist/session/common/usage-accumulator.js +10 -2
- package/dist/session/dsh/sessions.js +29 -9
- package/dist/session/kimi/sessions.js +9 -5
- package/dist/session/opencode/sessions.js +13 -6
- package/dist/session/session-store.js +4 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,55 @@ 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.12] - 2026-10-24
|
|
9
|
+
|
|
10
|
+
### 修复
|
|
11
|
+
|
|
12
|
+
- **Bash 工具结果不再偶发丢失**:kimi 的终端子进程退出后,runner 会等 stdout 尾部 flush(最多 200ms)再推 Bash 输出;但 `terminal/wait_for_exit` 此前在 flush 之前就返回,服务端拿到响应后立即 release 并结束回合,回合结果可能先于延迟推送的 tool_result 到达,卡片上的 Bash 面板变成空输出。现在 wait_for_exit 在 tool_result 入队后才返回,保证输出先于回合结束送达
|
|
13
|
+
|
|
14
|
+
### 维护
|
|
15
|
+
|
|
16
|
+
- **测试同步原语收紧**:pid 文件改为等完整内容再读(避免读到空串后把 0 当 PID 轮询进程组);kimi 压缩完成用例改由 mock server 在收到 prompt 后延迟落记录,消除并行负载下的假红。无行为变化
|
|
17
|
+
|
|
18
|
+
## [0.5.11] - 2026-10-24
|
|
19
|
+
|
|
20
|
+
### 修复
|
|
21
|
+
|
|
22
|
+
- **审批卡在超长或超量输入下不再整卡消失**:审批理由、题面、选项、文件变更等 agent 产出的文本此前没有上限,一条超长理由(实测 3 万字符)或 8 题 × 5 选项就能把卡片顶出字节/元素预算,被飞书整卡拒收——审批彻底不可见,回合只能挂到超时。现在自由文本按字节截断、题数/选项/变更条目按渲染上限折叠,最底层兜底卡改用紧凑审批区:审批按钮在任何降级档都保留
|
|
23
|
+
- **停止 bash 任务后卡片稳定显示「已手动终止」**:停止路径与自然收尾会并发写同一张卡的终态,后写者胜出;现在首个终态胜出,不再被随后的「执行失败」覆盖
|
|
24
|
+
- **内容审核截断后的自动续跑接回本轮会话**:续跑此前用入队时的会话快照,新会话第一条消息被截断时会落到一个全新的空会话,本轮上下文全部丢失;现在按卡片状态 → 运行中会话 → 起点快照三级取本轮实际使用的会话
|
|
25
|
+
- **DSH 停止后残留的排队 prompt 不再被下一个回合隐形执行**:服务端 cancel 不会清空 inbox,被停的 prompt 可能仍留在里面等下一个无人订阅的 turn 认领(耗 token、改工作区、卡片不可见)。现在 cancel 确认关闭后补一次撤回探测,确认不在或撤回成功才清在途标记;退出路径的 cancel 走同一收尾
|
|
26
|
+
- **DSH 断线重连后的历史缺口会回填**:重连快照按消息截取,断连期间积压超过 200 条消息时快照首条会越过上次游标,中间的运行事件此前对本次订阅永久缺失。现在用 `session/page` 的游标向前翻页补齐(最多 20×200 条);尾部是用户消息时事件探测返回 `unknown`,不再误报「已停止」
|
|
27
|
+
- **pi 会话切换后旧连接不再误杀新回合**:`/new`、`/resume` 切换会替换连接,旧连接迟到的关闭/事件此前会被推进新回合的共享队列,导致新消息未发出即丢失或新回合空转「成功」。现在连接回调带轮次代际锚点,非当前轮的迟到回调直接丢弃
|
|
28
|
+
- **上下文占用统计包含末轮输出**:统一口径改为「输入 + 输出 + 缓存读 + 缓存写」,与文档和压缩水位一致;长收尾回合不再系统性低估数千到数万 token
|
|
29
|
+
- **会话列表不再把 subagent 子会话当成父会话**:codex 按 id 读取与列表/active 同口径过滤子线程;kimi 的工作目录判定改用同路径比较,大小写或尾分隔符差异不再让会话从列表消失
|
|
30
|
+
- **配置 YAML 语法错误不再静默退出**:与配置校验失败同路径在终端输出原因并退出,而不是零输出地无声退出
|
|
31
|
+
- **工作区兜底与路径竞态收紧**:没有当前目录时取「最近使用」的工作区而不是最早保存的别名;`/cd`、`ls.switch`、`/ws use` 的目录检查补 TOCTOU 防护,路径在检查与使用之间被删时给出明确回复而不是静默失败
|
|
32
|
+
- **只有 chatId 的旧绑定记录可以重新认领**:这类记录此前会让所有消息被静默拒绝且永远无法重新绑定,现在视为未绑定,任意首条私聊可重新认领
|
|
33
|
+
- **卡片动作不再被在途媒体下载卡住**:会话变更类卡片动作执行前的入站冲刷改为 2 秒有界等待,大文件下载在途时点「新会话」不再让整个聊天停摆到下载超时;clone 引导期间到达的附件直接忽略,不再落盘或与引导话术冲突
|
|
34
|
+
- **`job.kill` 等纯远程操作立即执行**:终态卡上的「停止后台任务」不再排进串行队列等当前 run 结束
|
|
35
|
+
- **显式清空的模型配置重启后不再被旧选择填回**:`/config` 里清空模型/服务商等于回到默认,残留的旧选择不会再把它填回去
|
|
36
|
+
- **其他可靠性修复**:实例锁改为先写完整临时文件再原子链接,消除并发启动时的空文件窗口;pi 大上下文压缩不再受 60 秒请求超时影响;kimi 非终态的空工具结果不再把运行中的工具提前标成完成;DSH 会话读取遇到单条畸形事件只跳过该条,不再整张恢复卡变空;JSONL 流按字节边界解码,中文/emoji 被 chunk 切开时不再出现乱码
|
|
37
|
+
|
|
38
|
+
### 安全
|
|
39
|
+
|
|
40
|
+
- **退出时取消 DSH 回合的凭据临时文件不再可预测**:此前把短时 cookie 写到 `<tmpdir>/lark-remote-exit-cancel-<pid>.hdr`,同机其他用户可预建软链把凭据引到自己可读的文件。现在写入仅本人可进入的随机 0700 临时目录、文件 0600,正常结束与异常路径都会清理
|
|
41
|
+
|
|
42
|
+
### 变更
|
|
43
|
+
|
|
44
|
+
- **架构文档同步**:last-session 允许 cwd 缺失、两处终端直写豁免、标题显示宽度常量、DSH follow 快照与事件落盘顺序,中英两侧一致
|
|
45
|
+
|
|
46
|
+
## [0.5.10] - 2026-10-24
|
|
47
|
+
|
|
48
|
+
### 修复
|
|
49
|
+
|
|
50
|
+
- **点「立即执行」后,run 卡片不再抢在最终 toast 前面**:立即执行的 run 起始会等当前排队卡完成交接再建卡。否则 toast 会成为点击后最后一条新消息,聊天列表预览停在 toast 文案上,而 run 卡片后续的「思考中 / 工具 / 完成」变化不点进会话就看不到
|
|
51
|
+
- **长时间运行不再被过期的空闲定时器误杀**:工作区连接自行退出(如 codex app-server 偶发 exit 0)后,挂在旧连接上的空闲回收定时器此前不会被取消,约 30 分钟后会按工作目录回收后来接替的新连接,正在跑的回合被杀、卡片误报「连接已关闭」。现在定时器只回收自己武装时那个连接,进程自退时同时撤掉定时器,继任连接自己的空闲回收仍然照常生效
|
|
52
|
+
|
|
53
|
+
### 变更
|
|
54
|
+
|
|
55
|
+
- **英文架构文档补齐排队卡叙述**:立即执行时的 run 起始闸门与 toast 排序,中英两侧一致
|
|
56
|
+
|
|
8
57
|
## [0.5.9] - 2026-10-22
|
|
9
58
|
|
|
10
59
|
### 新增
|
package/dist/binder.js
CHANGED
|
@@ -44,7 +44,13 @@ export class OwnerBinder {
|
|
|
44
44
|
*/
|
|
45
45
|
authorize(userId, chatId) {
|
|
46
46
|
const bound = this.store.getContact();
|
|
47
|
-
|
|
47
|
+
// 只有 chatId 没有 userId 的记录(老版本写入/手改文件)视同未绑定:
|
|
48
|
+
// bound.userId === undefined 时任何真实 openId 都判不等,一切消息被
|
|
49
|
+
// 静默 rejected 且永远无法重新认领——用户只能靠文档提示删文件。
|
|
50
|
+
if (bound && bound.userId === undefined) {
|
|
51
|
+
getLogger().warn('[binder] startup contact has chatId but no userId; treating as unbound (re-claimable)');
|
|
52
|
+
}
|
|
53
|
+
else if (bound) {
|
|
48
54
|
if (userId === bound.userId)
|
|
49
55
|
return { kind: 'owner' };
|
|
50
56
|
this.rejectedTotal++;
|
package/dist/bridge/index.d.ts
CHANGED
|
@@ -270,6 +270,15 @@ export declare class Bridge {
|
|
|
270
270
|
* user edits a queued message (see router handleQueueImmediate).
|
|
271
271
|
*/
|
|
272
272
|
setTaskReplacement(cwd: string, messageId: string, task: () => Promise<void>): void;
|
|
273
|
+
/**
|
|
274
|
+
* One-shot: hold the target's run-card start until the returned release is
|
|
275
|
+
* called. queue.immediate uses it so its final toast is sent before the run
|
|
276
|
+
* card — the toast would otherwise be the newest message and the chat list
|
|
277
|
+
* would stop previewing the run card's title changes. The queue slot itself
|
|
278
|
+
* still begins normally; only run-card creation waits (see
|
|
279
|
+
* QueueManager.holdTaskRunStart).
|
|
280
|
+
*/
|
|
281
|
+
holdQueueTaskRun(cwd: string, messageId: string): () => void;
|
|
273
282
|
/** Update queue card to "cancelled" status when user clicks 撤销. */
|
|
274
283
|
updateQueueCardToCancelled(cwd: string, messageId: string): Promise<void>;
|
|
275
284
|
/** Mark a queued task's card as executing immediately (grey out buttons).
|
package/dist/bridge/index.js
CHANGED
|
@@ -488,6 +488,17 @@ export class Bridge {
|
|
|
488
488
|
setTaskReplacement(cwd, messageId, task) {
|
|
489
489
|
this.queueManager.setTaskReplacement(cwd, messageId, task);
|
|
490
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* One-shot: hold the target's run-card start until the returned release is
|
|
493
|
+
* called. queue.immediate uses it so its final toast is sent before the run
|
|
494
|
+
* card — the toast would otherwise be the newest message and the chat list
|
|
495
|
+
* would stop previewing the run card's title changes. The queue slot itself
|
|
496
|
+
* still begins normally; only run-card creation waits (see
|
|
497
|
+
* QueueManager.holdTaskRunStart).
|
|
498
|
+
*/
|
|
499
|
+
holdQueueTaskRun(cwd, messageId) {
|
|
500
|
+
return this.queueManager.holdTaskRunStart(cwd, messageId);
|
|
501
|
+
}
|
|
491
502
|
/** Update queue card to "cancelled" status when user clicks 撤销. */
|
|
492
503
|
async updateQueueCardToCancelled(cwd, messageId) {
|
|
493
504
|
await this.queueManager.updateQueueCardToCancelled(cwd, messageId);
|
|
@@ -671,8 +682,10 @@ export class Bridge {
|
|
|
671
682
|
for (const [runId, b] of bashTargets) {
|
|
672
683
|
getLogger().info(`[lark-remote] interrupt hit bash runId=${runId} cwd=${b.cwd} userId=${input.userId}`);
|
|
673
684
|
await b.bashRunner.stop({ immediate: true });
|
|
674
|
-
// Update card to show interrupted state
|
|
675
|
-
|
|
685
|
+
// Update card to show interrupted state. exitCode 不传(保持 null):
|
|
686
|
+
// bash-renderer 的「已手动终止」文案要求 exitCode === null;传 -1 会让
|
|
687
|
+
// 该分支不可达,中断卡退化成「退出码: -1」的失败样式。
|
|
688
|
+
await b.cardSession.finish('interrupted');
|
|
676
689
|
this.activeBashRuns.delete(runId);
|
|
677
690
|
getLogger().info(`[lark-remote] interrupt bash stop done runId=${runId}`);
|
|
678
691
|
stoppedCount++;
|
|
@@ -995,6 +1008,7 @@ export class Bridge {
|
|
|
995
1008
|
// 放在 finalize 之后:activeRuns 槽位已释放,续跑能正常开新卡片。
|
|
996
1009
|
await this.retryAfterContentFilter({
|
|
997
1010
|
cardSession,
|
|
1011
|
+
activeRun,
|
|
998
1012
|
ctx,
|
|
999
1013
|
cwd,
|
|
1000
1014
|
agentKind,
|
|
@@ -1031,6 +1045,12 @@ export class Bridge {
|
|
|
1031
1045
|
getLogger().info(`[lark-remote] content_filter auto-retry skipped: session epoch moved runId=${state.runId}`);
|
|
1032
1046
|
return;
|
|
1033
1047
|
}
|
|
1048
|
+
// 续跑必须接「本轮实际使用的 session」而非入队时刻快照(与 finalize 路径
|
|
1049
|
+
// 的三级优先同口径:卡片状态 → activeRun → 起点快照)。快照只在 run 未建立
|
|
1050
|
+
// 任何新会话时兜底。可证场景:新会话第一条消息(快照 undefined)被截断——
|
|
1051
|
+
// init 已建立 S1,epoch 未动,传快照会让续跑落到全新空会话,本轮上下文
|
|
1052
|
+
// 全部丢失;claude --resume fork 场景同理(init 上报的 id 才是权威)。
|
|
1053
|
+
const retrySessionId = state.sessionId ?? params.activeRun.sessionId ?? params.sessionId;
|
|
1034
1054
|
getLogger().warn(`[lark-remote] content_filter detected, auto-retrying once runId=${state.runId} cwd=${params.cwd}`);
|
|
1035
1055
|
await this.sendResult({
|
|
1036
1056
|
text: '🔁 上一回合被服务方内容审核截断(content_filter,通常是误报),已自动重试一次。',
|
|
@@ -1041,7 +1061,7 @@ export class Bridge {
|
|
|
1041
1061
|
cwdOverride: params.cwd,
|
|
1042
1062
|
binding: {
|
|
1043
1063
|
agent: params.agentKind,
|
|
1044
|
-
sessionId:
|
|
1064
|
+
sessionId: retrySessionId,
|
|
1045
1065
|
sessionEpoch: params.sessionEpochAtStart,
|
|
1046
1066
|
},
|
|
1047
1067
|
autoRetry: true,
|
|
@@ -1209,6 +1229,11 @@ export class Bridge {
|
|
|
1209
1229
|
});
|
|
1210
1230
|
try {
|
|
1211
1231
|
try {
|
|
1232
|
+
// queue.immediate 的 run-start hold:目标槽位可能在 handler 发最终
|
|
1233
|
+
// toast 之前就 begin(interrupt/PATCH 的 await 窗口)。activeRun 已
|
|
1234
|
+
// 登记(队列语义照旧),只把建卡这一步挡到 toast 之后——run 卡片才是
|
|
1235
|
+
// 点击后最后一条新消息,聊天列表预览跟随它的标题变化。
|
|
1236
|
+
await this.queueManager.awaitTaskRunStart(cwd);
|
|
1212
1237
|
getLogger().debug(`[lark-remote] cardSession.start() begin runId=${runId}`);
|
|
1213
1238
|
await cardSession.start();
|
|
1214
1239
|
getLogger().info(`[lark-remote] cardSession.start() ok runId=${runId}`);
|
|
@@ -69,6 +69,11 @@ export interface ExecutingInfo {
|
|
|
69
69
|
* settle 清掉后继任务的占用者(身份不用外部 id,同 §9.24 口径)。
|
|
70
70
|
*/
|
|
71
71
|
slotId: number;
|
|
72
|
+
/**
|
|
73
|
+
* 本次执行条目自己的队列 key(排队卡身份,见 §9.24)。run 起始闸门按它
|
|
74
|
+
* 找到「当前 lane 占用者」持有的 hold;无 taskMeta 的任务为 undefined。
|
|
75
|
+
*/
|
|
76
|
+
messageId?: string;
|
|
72
77
|
kind: LaneTaskKind;
|
|
73
78
|
/** 展示标签,例如 '💬 消息' / '🗜 Compact' / '📋 config.save'。 */
|
|
74
79
|
label: string;
|
|
@@ -172,6 +177,17 @@ export declare class QueueManager {
|
|
|
172
177
|
private interruptedSlots;
|
|
173
178
|
/** Per-workspace messageId → replacement closure, set by queue.edit + immediate. */
|
|
174
179
|
private taskReplacements;
|
|
180
|
+
/**
|
|
181
|
+
* Per-workspace messageId → run 起始 hold,单次消费。
|
|
182
|
+
*
|
|
183
|
+
* queue.immediate 在入口注册、finally 释放;目标任务的 begin 路径照常推进
|
|
184
|
+
* (槽位移除、排队卡翻执行态),但 `forwardToClaude` 在建 run 卡片前会等
|
|
185
|
+
* 当前 lane 占用者的 hold。这样「先 toast、后 run 卡片」与队列链推进速度
|
|
186
|
+
* 无关:run 卡片是点击后最后一条新消息,聊天列表预览才会跟随它的标题变化。
|
|
187
|
+
* hold 按队列 key 存(order.exec 的内部 key 也能命中),通过 executingInfo
|
|
188
|
+
* 找到当前占用者,不需要把队列 key 穿过 router/forwardToClaude 的参数。
|
|
189
|
+
*/
|
|
190
|
+
private taskRunHolds;
|
|
175
191
|
/**
|
|
176
192
|
* MessageIds of tasks that have actually begun executing (cancellation check
|
|
177
193
|
* passed, task removed from the queue). Entries are sticky: they remain
|
|
@@ -212,6 +228,21 @@ export declare class QueueManager {
|
|
|
212
228
|
* Used for / commands that should respond immediately.
|
|
213
229
|
*/
|
|
214
230
|
enqueueImmediate(cwd: string, task: () => Promise<void>): void;
|
|
231
|
+
/**
|
|
232
|
+
* 单次:hold 住某个排队任务的 run 起始,直到返回的 release 被调用。
|
|
233
|
+
*
|
|
234
|
+
* 与「hold 任务闭包」不同,这里只挡 `forwardToClaude` 建 run 卡片之前的
|
|
235
|
+
* 那一步:队列槽位照常 begin(排队卡翻执行态、began 记账都发生),所以
|
|
236
|
+
* queue.immediate 的成员资格判断不受影响,只是目标 run 卡片会等 toast。
|
|
237
|
+
* release 幂等;若任务在 begin 前被撤销,release 会清掉未消费的 hold。
|
|
238
|
+
*/
|
|
239
|
+
holdTaskRunStart(cwd: string, messageId: string): () => void;
|
|
240
|
+
/**
|
|
241
|
+
* 等待当前 lane 占用者的 run 起始 hold。由 `forwardToClaude` 在建 run 卡片
|
|
242
|
+
* 前调用;hold 在当前调用里同步摘除,之后才注册的 hold 不会延长本次 run
|
|
243
|
+
* (release 仍会 settle 已捕获的 promise)。
|
|
244
|
+
*/
|
|
245
|
+
awaitTaskRunStart(cwd: string): Promise<void>;
|
|
215
246
|
/**
|
|
216
247
|
* One-shot: replace the execution closure of an existing queued task in
|
|
217
248
|
* place, preserving its queue position (tasks queued behind it still run
|
|
@@ -87,6 +87,17 @@ export class QueueManager {
|
|
|
87
87
|
interruptedSlots = new Map();
|
|
88
88
|
/** Per-workspace messageId → replacement closure, set by queue.edit + immediate. */
|
|
89
89
|
taskReplacements = new Map();
|
|
90
|
+
/**
|
|
91
|
+
* Per-workspace messageId → run 起始 hold,单次消费。
|
|
92
|
+
*
|
|
93
|
+
* queue.immediate 在入口注册、finally 释放;目标任务的 begin 路径照常推进
|
|
94
|
+
* (槽位移除、排队卡翻执行态),但 `forwardToClaude` 在建 run 卡片前会等
|
|
95
|
+
* 当前 lane 占用者的 hold。这样「先 toast、后 run 卡片」与队列链推进速度
|
|
96
|
+
* 无关:run 卡片是点击后最后一条新消息,聊天列表预览才会跟随它的标题变化。
|
|
97
|
+
* hold 按队列 key 存(order.exec 的内部 key 也能命中),通过 executingInfo
|
|
98
|
+
* 找到当前占用者,不需要把队列 key 穿过 router/forwardToClaude 的参数。
|
|
99
|
+
*/
|
|
100
|
+
taskRunHolds = new Map();
|
|
90
101
|
/**
|
|
91
102
|
* MessageIds of tasks that have actually begun executing (cancellation check
|
|
92
103
|
* passed, task removed from the queue). Entries are sticky: they remain
|
|
@@ -308,6 +319,7 @@ export class QueueManager {
|
|
|
308
319
|
// is waiting for instead of reporting "前面还有 0 条".
|
|
309
320
|
this.executingInfo.set(cwd, {
|
|
310
321
|
slotId,
|
|
322
|
+
messageId,
|
|
311
323
|
kind: taskMeta?.kind ?? 'message',
|
|
312
324
|
label: laneOccupantLabel(taskMeta?.kind ?? 'message', livePreview),
|
|
313
325
|
preview: livePreview,
|
|
@@ -339,6 +351,72 @@ export class QueueManager {
|
|
|
339
351
|
getLogger().debug(`[queue-manager] enqueueImmediate cwd=${cwd}`);
|
|
340
352
|
void task().catch((err) => getLogger().error('[queue-manager] immediate task error:', err));
|
|
341
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* 单次:hold 住某个排队任务的 run 起始,直到返回的 release 被调用。
|
|
356
|
+
*
|
|
357
|
+
* 与「hold 任务闭包」不同,这里只挡 `forwardToClaude` 建 run 卡片之前的
|
|
358
|
+
* 那一步:队列槽位照常 begin(排队卡翻执行态、began 记账都发生),所以
|
|
359
|
+
* queue.immediate 的成员资格判断不受影响,只是目标 run 卡片会等 toast。
|
|
360
|
+
* release 幂等;若任务在 begin 前被撤销,release 会清掉未消费的 hold。
|
|
361
|
+
*/
|
|
362
|
+
holdTaskRunStart(cwd, messageId) {
|
|
363
|
+
let resolve;
|
|
364
|
+
const hold = {
|
|
365
|
+
promise: new Promise((r) => {
|
|
366
|
+
resolve = r;
|
|
367
|
+
}),
|
|
368
|
+
release: () => resolve(),
|
|
369
|
+
};
|
|
370
|
+
let workspaceHolds = this.taskRunHolds.get(cwd);
|
|
371
|
+
if (!workspaceHolds) {
|
|
372
|
+
workspaceHolds = new Map();
|
|
373
|
+
this.taskRunHolds.set(cwd, workspaceHolds);
|
|
374
|
+
}
|
|
375
|
+
const holds = workspaceHolds.get(messageId);
|
|
376
|
+
if (holds) {
|
|
377
|
+
holds.push(hold);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
workspaceHolds.set(messageId, [hold]);
|
|
381
|
+
}
|
|
382
|
+
getLogger().debug(`[queue-manager] hold task run start cwd=${cwd} messageId=${messageId}`);
|
|
383
|
+
return () => {
|
|
384
|
+
// Resolve before mutating the map: `awaitTaskRunStart` may already have
|
|
385
|
+
// captured (and deleted) this entry, and the captured promise still needs
|
|
386
|
+
// to settle.
|
|
387
|
+
hold.release();
|
|
388
|
+
const current = this.taskRunHolds.get(cwd)?.get(messageId);
|
|
389
|
+
if (!current)
|
|
390
|
+
return;
|
|
391
|
+
const idx = current.indexOf(hold);
|
|
392
|
+
if (idx >= 0)
|
|
393
|
+
current.splice(idx, 1);
|
|
394
|
+
if (current.length > 0)
|
|
395
|
+
return;
|
|
396
|
+
this.taskRunHolds.get(cwd)?.delete(messageId);
|
|
397
|
+
if (this.taskRunHolds.get(cwd)?.size === 0)
|
|
398
|
+
this.taskRunHolds.delete(cwd);
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* 等待当前 lane 占用者的 run 起始 hold。由 `forwardToClaude` 在建 run 卡片
|
|
403
|
+
* 前调用;hold 在当前调用里同步摘除,之后才注册的 hold 不会延长本次 run
|
|
404
|
+
* (release 仍会 settle 已捕获的 promise)。
|
|
405
|
+
*/
|
|
406
|
+
async awaitTaskRunStart(cwd) {
|
|
407
|
+
const messageId = this.executingInfo.get(cwd)?.messageId;
|
|
408
|
+
if (!messageId)
|
|
409
|
+
return;
|
|
410
|
+
const workspaceHolds = this.taskRunHolds.get(cwd);
|
|
411
|
+
const holds = workspaceHolds?.get(messageId);
|
|
412
|
+
if (!holds || holds.length === 0)
|
|
413
|
+
return;
|
|
414
|
+
workspaceHolds?.delete(messageId);
|
|
415
|
+
if (workspaceHolds?.size === 0)
|
|
416
|
+
this.taskRunHolds.delete(cwd);
|
|
417
|
+
getLogger().debug(`[queue-manager] awaiting task run start cwd=${cwd} messageId=${messageId} holds=${holds.length}`);
|
|
418
|
+
await Promise.all(holds.map((h) => h.promise));
|
|
419
|
+
}
|
|
342
420
|
/**
|
|
343
421
|
* One-shot: replace the execution closure of an existing queued task in
|
|
344
422
|
* place, preserving its queue position (tasks queued behind it still run
|
|
@@ -14,3 +14,17 @@ export declare function renderApprovalArea(approval: ApprovalView | undefined, o
|
|
|
14
14
|
runId?: string;
|
|
15
15
|
terminal?: string;
|
|
16
16
|
}): object[];
|
|
17
|
+
/**
|
|
18
|
+
* 紧凑审批区(run 卡 skeleton/truncatedSkeleton 兜底档专用):固定小体积的
|
|
19
|
+
* 「标题 + 摘要 reason + 决策按钮」,不渲染正文/选项/题面。
|
|
20
|
+
*
|
|
21
|
+
* 为什么需要:完整审批区是唯一不受估算器管辖、也不参与 degraded/extreme 分层
|
|
22
|
+
* 裁剪的区域; pathological 输入(超长 reason、题数×选项爆元素数)会让所有兜底
|
|
23
|
+
* 层一起超限,超限卡被飞书整卡拒收(11310)→ 审批不可见。兜底层换用本入口后
|
|
24
|
+
* 结构性保证 ≤ 几个元素 + ≤ 1KB 文本,审批入口(红线)在任何降级档都活着。
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderApprovalAreaCompact(approval: ApprovalView | undefined, opts?: {
|
|
27
|
+
expired?: boolean;
|
|
28
|
+
runId?: string;
|
|
29
|
+
terminal?: string;
|
|
30
|
+
}): object[];
|