lark-remote 0.5.13 → 0.5.15
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 +31 -0
- package/dist/agent-kinds.d.ts +27 -0
- package/dist/agent-kinds.js +38 -0
- package/dist/bridge/approval-actions.d.ts +95 -0
- package/dist/bridge/approval-actions.js +104 -0
- package/dist/bridge/approval-coordinator.d.ts +13 -0
- package/dist/bridge/approval-coordinator.js +18 -0
- package/dist/bridge/final-usage.d.ts +8 -17
- package/dist/bridge/final-usage.js +1 -18
- package/dist/bridge/index.d.ts +46 -39
- package/dist/bridge/index.js +269 -240
- package/dist/bridge/queue-manager.d.ts +25 -1
- package/dist/bridge/queue-manager.js +179 -160
- package/dist/bridge/run-approval.d.ts +2 -1
- package/dist/bridge/run-usage.d.ts +21 -30
- package/dist/bridge/run-usage.js +47 -51
- package/dist/bridge/session-writeback.d.ts +3 -2
- package/dist/card/approval-render.d.ts +3 -2
- package/dist/card/approval-render.js +3 -2
- package/dist/{router → card}/card-helpers.d.ts +7 -7
- package/dist/{router → card}/card-helpers.js +8 -8
- package/dist/card/card-session.d.ts +4 -2
- package/dist/card/card-session.js +8 -6
- package/dist/card/card-shared.d.ts +1 -1
- package/dist/card/card-shared.js +1 -1
- package/dist/card/final-answer-overflow.d.ts +0 -6
- package/dist/card/final-answer-overflow.js +1 -1
- package/dist/card/run-card-session.d.ts +2 -2
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.js +29 -30
- package/dist/card/run-state.d.ts +28 -50
- package/dist/card/run-state.js +23 -0
- package/dist/card/usage-format.d.ts +30 -0
- package/dist/{router → card}/usage-format.js +3 -10
- package/dist/clone.js +6 -1
- package/dist/config/index.d.ts +16 -20
- package/dist/config/index.js +0 -16
- package/dist/config/pi-config.js +6 -4
- package/dist/connector/index.d.ts +2 -4
- package/dist/connector/index.js +7 -11
- package/dist/{runner/dsh → dsh}/auth.js +1 -1
- package/dist/dsh/protocol.d.ts +9 -0
- package/dist/dsh/protocol.js +61 -0
- package/dist/index.js +10 -5
- package/dist/model-catalog.d.ts +14 -0
- package/dist/model-catalog.js +25 -0
- package/dist/platform/path.d.ts +9 -0
- package/dist/platform/path.js +18 -0
- package/dist/router/active-run-usage.d.ts +4 -0
- package/dist/router/active-run-usage.js +12 -0
- package/dist/router/commands/ls.js +1 -1
- package/dist/router/commands/order.js +1 -1
- package/dist/router/commands/resume.d.ts +5 -4
- package/dist/router/commands/resume.js +4 -4
- package/dist/router/commands/ws.js +1 -1
- package/dist/router/config/claude.js +2 -1
- package/dist/router/config/common/render.d.ts +5 -1
- package/dist/router/config/common/render.js +6 -13
- package/dist/router/config/dsh.d.ts +1 -1
- package/dist/router/config/index.d.ts +1 -1
- package/dist/router/config/types.d.ts +11 -3
- package/dist/router/index.d.ts +41 -11
- package/dist/router/index.js +169 -233
- package/dist/runner/agent-choices-common.d.ts +1 -1
- package/dist/runner/claude/runner.d.ts +20 -2
- package/dist/runner/claude/runner.js +2 -15
- package/dist/runner/codex/app-server/runner.d.ts +3 -1
- package/dist/runner/codex/app-server/translator.d.ts +11 -0
- package/dist/runner/codex/app-server/translator.js +56 -149
- package/dist/runner/common/acp/base-acp-runner.d.ts +19 -1
- package/dist/runner/common/acp/base-acp-runner.js +37 -3
- package/dist/runner/common/connection-based-runner.d.ts +3 -1
- package/dist/runner/common/jsonrpc/transport.js +17 -7
- package/dist/runner/dsh/client.d.ts +1 -4
- package/dist/runner/dsh/client.js +7 -55
- package/dist/runner/dsh/daemon.js +9 -2
- package/dist/runner/dsh/exit-cancel.js +1 -1
- package/dist/runner/dsh/mux-stream.js +21 -2
- package/dist/runner/dsh/runner.d.ts +21 -1
- package/dist/runner/dsh/runner.js +97 -78
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +22 -1
- package/dist/runner/kimi/acp/runner.js +83 -60
- package/dist/runner/opencode/acp/error-monitor.d.ts +11 -0
- package/dist/runner/opencode/acp/error-monitor.js +31 -2
- package/dist/runner/opencode/acp/runner.d.ts +14 -1
- package/dist/runner/opencode/acp/runner.js +13 -2
- package/dist/runner/pi/rpc/runner.d.ts +2 -1
- package/dist/runner/probe.d.ts +1 -1
- package/dist/runner/probe.js +10 -9
- package/dist/runner/registry.d.ts +2 -1
- package/dist/runner/types.d.ts +20 -154
- package/dist/runner/types.js +4 -24
- package/dist/session/claude/session-index.d.ts +11 -4
- package/dist/session/claude/session-index.js +30 -8
- package/dist/session/claude/session-reader.d.ts +1 -1
- package/dist/session/claude/sessions.d.ts +2 -2
- package/dist/session/claude/sessions.js +41 -26
- package/dist/session/codex/rollout-reader.d.ts +1 -1
- package/dist/session/codex/rollout-reader.js +14 -13
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +4 -2
- package/dist/session/common/jsonl.js +7 -1
- package/dist/session/common/pagination.d.ts +2 -0
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/common/recency.d.ts +6 -0
- package/dist/session/common/recency.js +7 -5
- package/dist/session/common/two-pass.d.ts +1 -1
- package/dist/session/common/usage-accumulator.d.ts +1 -1
- package/dist/session/dsh/sessions.d.ts +1 -1
- package/dist/session/dsh/sessions.js +37 -12
- package/dist/session/kimi/sessions.d.ts +1 -1
- package/dist/session/kimi/sessions.js +72 -27
- package/dist/session/opencode/sessions.d.ts +1 -1
- package/dist/session/opencode/sessions.js +164 -81
- package/dist/session/pi/sessions.d.ts +1 -1
- package/dist/session/pi/sessions.js +20 -7
- package/dist/session/registry.d.ts +2 -1
- package/dist/session/session-store.d.ts +1 -2
- package/dist/session/session-store.js +14 -7
- package/dist/session-contract.d.ts +178 -0
- package/dist/session-contract.js +9 -0
- package/package.json +1 -1
- package/dist/router/usage-format.d.ts +0 -60
- /package/dist/{runner/dsh → dsh}/auth.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/home.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/home.js +0 -0
- /package/dist/{runner/dsh → dsh}/types.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/types.js +0 -0
package/dist/router/index.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
|
-
import { getConfigDir,
|
|
4
|
+
import { getConfigDir, setConfigValue, setConfigValues, mapAgentKey, getAgentConfig, } from '../config/index.js';
|
|
5
5
|
import { syncAgentChoices } from '../runner/index.js';
|
|
6
|
-
import { resolveDshCredentialsFile } from '../
|
|
6
|
+
import { resolveDshCredentialsFile } from '../dsh/auth.js';
|
|
7
7
|
import { WorkspaceStore } from '../workspace/index.js';
|
|
8
8
|
import { OrderStore, ORDER_NOT_FOUND_TEXT } from '../order/index.js';
|
|
9
9
|
import { resolveAlias } from '../order/alias-resolve.js';
|
|
10
10
|
import { diffConfig, setNestedValue } from './config-diff.js';
|
|
11
|
-
import { AGENT_KINDS } from '../
|
|
11
|
+
import { AGENT_KINDS, resolveAgentKind } from '../agent-kinds.js';
|
|
12
12
|
import { getLogger } from '../logger/index.js';
|
|
13
13
|
import { stripPlaceholders } from '../inbound/placeholder.js';
|
|
14
14
|
import { clampInt } from '../common/clamp.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { isRunTerminal } from '../card/run-state.js';
|
|
16
|
+
import { activeRunUsage } from './active-run-usage.js';
|
|
17
|
+
import { markdownDiv, buildSessionHistoryCard, paginationBar, parseJumpOffset, SEARCH_INPUT_NAME, PAGE_JUMP_INVALID_HINT, } from '../card/card-helpers.js';
|
|
17
18
|
import { MAX_FILE_UPLOAD_SIZE } from '../connector/file-limits.js';
|
|
18
19
|
import { cardButton, newSessionButton, resumeCompactButton, agentDisplayName, } from '../card/card-shared.js';
|
|
19
20
|
import { displayName } from '../platform/path.js';
|
|
@@ -290,7 +291,6 @@ const IMMEDIATE_ACTION_CMDS = new Set([
|
|
|
290
291
|
'order.aliasRemove',
|
|
291
292
|
'order.textEdit',
|
|
292
293
|
'order.textInput',
|
|
293
|
-
'config.toggle',
|
|
294
294
|
'config.set',
|
|
295
295
|
'config.input',
|
|
296
296
|
'config.save',
|
|
@@ -349,13 +349,20 @@ const CARD_PAYLOAD_MISSING = '⚠️ 卡片 payload 缺少必要信息';
|
|
|
349
349
|
* 卡片只有最近一两张,5 张足够覆盖;无上限会让长跑进程的 Set 随卡片数增长。
|
|
350
350
|
*/
|
|
351
351
|
const MAX_CONFIG_CARDS = 5;
|
|
352
|
+
/** 本次改动是否落进 `agents.<kind>` 子树。 */
|
|
353
|
+
function touchesAgentsSubtree(keys, kind) {
|
|
354
|
+
return keys.some((k) => k === `agents.${kind}` || k.startsWith(`agents.${kind}.`));
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* 本次改动是否触及某个 agent 的配置——两种键形态都要认:现行 `agents.<kind>[.*]`
|
|
358
|
+
* 与历史的顶层 `<kind>[.*]`(claude 至今是顶层键)。判定曾抄在三处(agent 变更检测 /
|
|
359
|
+
* agentChoices 同步 / approval 热推),每加一个 agent 都得记得改三遍,与
|
|
360
|
+
* AGENT_KINDS 名单漂移是同一种事故,这里收成一处。
|
|
361
|
+
*/
|
|
362
|
+
function touchesAgentConfig(keys, kind) {
|
|
363
|
+
return (touchesAgentsSubtree(keys, kind) || keys.some((k) => k === kind || k.startsWith(`${kind}.`)));
|
|
364
|
+
}
|
|
352
365
|
export class CommandRouter {
|
|
353
|
-
/**
|
|
354
|
-
* Valid agent kinds — single source of truth for resume.use / resume.page /
|
|
355
|
-
* cmdResume. 名单本体在 `runner/types.ts` 的 AGENT_KINDS(与 AgentKind 类型
|
|
356
|
-
* 同源),这里只做别名,避免人手抄第二份。
|
|
357
|
-
*/
|
|
358
|
-
static VALID_AGENTS = AGENT_KINDS;
|
|
359
366
|
sessionStore;
|
|
360
367
|
/** 飞书桥接实例(public:测试直接访问断言,替代 as unknown as)。 */
|
|
361
368
|
bridge;
|
|
@@ -428,8 +435,8 @@ export class CommandRouter {
|
|
|
428
435
|
/**
|
|
429
436
|
* 串行化 config.* 卡片回调(2026-07-04)。
|
|
430
437
|
* CardKit 2.0 input/button 回调经 `enqueueImmediate` 分发,**不进串行队列**,
|
|
431
|
-
*
|
|
432
|
-
* `updateCardInPlace` → Feishu API
|
|
438
|
+
* 多次快速点击下拉/文本框会并发读写 `pendingConfig` 并同时调
|
|
439
|
+
* `updateCardInPlace` → Feishu API 乱序到达,控件卡死在某个状态。
|
|
433
440
|
* 此 Promise chain 把所有 config.* 动作串成一个执行链,保证后到的动作
|
|
434
441
|
* 等前一个动作的卡片 patch 完成后再读 `pendingConfig`。
|
|
435
442
|
*/
|
|
@@ -583,11 +590,7 @@ export class CommandRouter {
|
|
|
583
590
|
case 'resume.use': {
|
|
584
591
|
// resume.use carries an agent field from /resume [agent] list cards
|
|
585
592
|
// (both active and completed cards include the agent field).
|
|
586
|
-
const
|
|
587
|
-
const validAgents = CommandRouter.VALID_AGENTS;
|
|
588
|
-
const resolvedAgent = validAgents.includes(resumeAgent)
|
|
589
|
-
? resumeAgent
|
|
590
|
-
: this.config.defaultAgent;
|
|
593
|
+
const resolvedAgent = resolveAgentKind(value.agent, this.config.defaultAgent);
|
|
591
594
|
const entry = this.sessionStore.get(ctx.userId);
|
|
592
595
|
const cwd = entry?.cwd;
|
|
593
596
|
if (!cwd) {
|
|
@@ -658,13 +661,12 @@ export class CommandRouter {
|
|
|
658
661
|
return await this.handleOrderTextEdit(value, ctx);
|
|
659
662
|
case 'order.textInput':
|
|
660
663
|
return await this.handleOrderTextInput(value, ctx);
|
|
661
|
-
case 'config.toggle':
|
|
662
664
|
case 'config.set':
|
|
663
665
|
case 'config.input':
|
|
664
666
|
case 'config.save':
|
|
665
667
|
// 2026-07-04: 串行化所有 config.* 动作。CardKit 2.0 回调经 enqueueImmediate
|
|
666
|
-
//
|
|
667
|
-
// 调 updateCardInPlace → Feishu API
|
|
668
|
+
// 不进串行队列,多次快速点击下拉/文本框会并发读写 pendingConfig 并同时
|
|
669
|
+
// 调 updateCardInPlace → Feishu API 乱序到达导致控件卡死。
|
|
668
670
|
// 2026-07-18: 返回 enqueueConfigAction 的结果以支持 toast 响应
|
|
669
671
|
return this.enqueueConfigAction(value, ctx);
|
|
670
672
|
case 'compact':
|
|
@@ -759,6 +761,20 @@ export class CommandRouter {
|
|
|
759
761
|
* Handle queue.immediate: stop current run, clear queue before this message, execute this message immediately.
|
|
760
762
|
* Instead of asking user to resend, we keep the target task in queue and let it execute.
|
|
761
763
|
*/
|
|
764
|
+
/**
|
|
765
|
+
* 把「执行编辑后的内容」注册成原队列槽位的一次性 replacement(复用入队时
|
|
766
|
+
* 捕获的 binding + 恢复 lane cwd,否则编辑后重跑的 run 会丢 cwd 与绑定)。
|
|
767
|
+
*
|
|
768
|
+
* 重新入队不行:`enqueue` 只能追加到 promise 链尾部,会把编辑任务甩到
|
|
769
|
+
* 后来者后面,违背「立即执行 = 下一个就跑」。三个调用点都必须在任何 await
|
|
770
|
+
* 之前注册——begin 路径消费 replacement 的时机不可预测(见
|
|
771
|
+
* handleQueueImmediate 步骤 1.5 的竞态说明)。
|
|
772
|
+
*/
|
|
773
|
+
registerEditedReplacement(cwd, messageId, content, binding, ctx) {
|
|
774
|
+
this.bridge.setTaskReplacement(cwd, messageId, async () => {
|
|
775
|
+
await this.handle(content, ctx, { cwdOverride: cwd, binding });
|
|
776
|
+
});
|
|
777
|
+
}
|
|
762
778
|
async handleQueueImmediate(value, ctx) {
|
|
763
779
|
const payload = await this.queuePayloadOrReply(value, ctx);
|
|
764
780
|
if (!payload)
|
|
@@ -808,15 +824,7 @@ export class CommandRouter {
|
|
|
808
824
|
// the queue position: enqueue can only append to the promise chain tail,
|
|
809
825
|
// which would put the edited task behind tasks queued after it.
|
|
810
826
|
if (targetTask.editedMessage) {
|
|
811
|
-
|
|
812
|
-
///Step3: 替换闭包复用原任务的 binding(不重新快照)+ 恢复丢失的
|
|
813
|
-
// cwdOverride(lane cwd 同源)。否则编辑后重新执行的 run 会丢 cwd 与绑定。
|
|
814
|
-
this.bridge.setTaskReplacement(cwd, messageId, async () => {
|
|
815
|
-
await this.handle(editedContent, ctx, {
|
|
816
|
-
cwdOverride: cwd,
|
|
817
|
-
binding: targetTask.binding,
|
|
818
|
-
});
|
|
819
|
-
});
|
|
827
|
+
this.registerEditedReplacement(cwd, messageId, targetTask.editedMessage, targetTask.binding, ctx);
|
|
820
828
|
}
|
|
821
829
|
// 2. Stop current running process
|
|
822
830
|
// (Note: queue executing count is reset inside Bridge.interruptCurrentRun
|
|
@@ -833,29 +841,7 @@ export class CommandRouter {
|
|
|
833
841
|
const stopUnconfirmed = interrupt.stop?.status === 'unconfirmed';
|
|
834
842
|
getLogger().info(`[router] queue.immediate: stopped=${stopped} unconfirmed=${stopUnconfirmed}`);
|
|
835
843
|
// 3. Remove all tasks BEFORE this one (by position in the current queue).
|
|
836
|
-
|
|
837
|
-
// advances concurrently): then it is already out of the snapshot and every
|
|
838
|
-
// remaining queued task is BEHIND it — clearing must stop, not run off the
|
|
839
|
-
// end and delete tasks queued after the target.
|
|
840
|
-
const tasks = this.bridge.getQueuedTasks(cwd);
|
|
841
|
-
const targetIdx = tasks.findIndex((t) => t.messageId === messageId);
|
|
842
|
-
let removedCount = 0;
|
|
843
|
-
// Phase 1: remove synchronously. The queue chain advances on any await
|
|
844
|
-
// (card PATCH round trips included), so every task before the target must
|
|
845
|
-
// be removed before yielding to the event loop — otherwise a task the user
|
|
846
|
-
// asked to clear can begin executing while its cancellation card is still
|
|
847
|
-
// in flight.
|
|
848
|
-
for (let i = 0; i < targetIdx; i++) {
|
|
849
|
-
const task = tasks[i];
|
|
850
|
-
// 撤销卡片由 removeFromQueue 统一触发(fire-and-forget),保持本循环
|
|
851
|
-
// 同步推进的既有不变式。
|
|
852
|
-
if (this.bridge.removeFromQueue(cwd, task.messageId)) {
|
|
853
|
-
removedCount++;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
if (targetIdx < 0) {
|
|
857
|
-
getLogger().debug(`[router] queue.immediate: target no longer queued (began/cancelled), not clearing tasks behind it`);
|
|
858
|
-
}
|
|
844
|
+
const removedCount = this.clearTasksAheadOfTarget(cwd, messageId);
|
|
859
845
|
// 4. DO NOT remove the target task - keep it in queue to execute immediately
|
|
860
846
|
// The queue will naturally execute it after the current task (which we just stopped)
|
|
861
847
|
// 4.4. Refresh the replacement with the latest edited content (idempotent:
|
|
@@ -864,66 +850,92 @@ export class CommandRouter {
|
|
|
864
850
|
// the task has begun, because getQueuedTask then returns undefined.
|
|
865
851
|
const latestTask = this.bridge.getQueuedTask(cwd, messageId);
|
|
866
852
|
if (latestTask?.editedMessage) {
|
|
867
|
-
|
|
868
|
-
///Step3: 复用原任务 binding + 恢复 cwdOverride(同上)。
|
|
869
|
-
this.bridge.setTaskReplacement(cwd, messageId, async () => {
|
|
870
|
-
await this.handle(editedContent, ctx, {
|
|
871
|
-
cwdOverride: cwd,
|
|
872
|
-
binding: latestTask.binding,
|
|
873
|
-
});
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
// 4.5/6. The target must only be marked executing (and the toast may only
|
|
877
|
-
// promise immediate execution) when it is really the next task after the
|
|
878
|
-
// stop. While interruptCurrentRun was awaiting, a task ahead of the target
|
|
879
|
-
// may have begun and now occupy the cwd — the target is still queued
|
|
880
|
-
// but is NOT next, so its card must stay queued and the toast must say so.
|
|
881
|
-
const targetStillQueued = this.bridge.getQueuedTask(cwd, messageId) !== undefined;
|
|
882
|
-
if (!targetStillQueued) {
|
|
883
|
-
// The target began or was cancelled while earlier awaits were in flight —
|
|
884
|
-
// a success toast must never promise execution of a task that will not
|
|
885
|
-
// run. The two missing-reason states get distinct feedback: a target
|
|
886
|
-
// that began while the interrupt was in flight is already executing, so
|
|
887
|
-
// the toast must acknowledge that instead of telling the user nothing
|
|
888
|
-
// was scheduled; only a target that was actually cancelled (never began)
|
|
889
|
-
// gets the "未安排执行" wording.
|
|
890
|
-
if (this.bridge.hasTaskBegan(messageId)) {
|
|
891
|
-
await this.bridge.sendResult({ text: `ℹ️ 目标消息已开始执行,无需重复操作。已清除 ${removedCount} 条排队消息。` }, ctx);
|
|
892
|
-
}
|
|
893
|
-
else {
|
|
894
|
-
await this.bridge.sendResult({
|
|
895
|
-
text: `⚠️ 目标消息已不在队列中(可能已被撤销),未安排执行。已清除 ${removedCount} 条排队消息。`,
|
|
896
|
-
}, ctx);
|
|
897
|
-
}
|
|
898
|
-
return;
|
|
853
|
+
this.registerEditedReplacement(cwd, messageId, latestTask.editedMessage, latestTask.binding, ctx);
|
|
899
854
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
855
|
+
// 4.5/6. 按「目标此刻真的是下一个吗」给唯一一份用户可见反馈。
|
|
856
|
+
await this.reportImmediateOutcome({
|
|
857
|
+
cwd,
|
|
858
|
+
messageId,
|
|
859
|
+
ctx,
|
|
860
|
+
removedCount,
|
|
861
|
+
stopped,
|
|
862
|
+
stopUnconfirmed,
|
|
863
|
+
agentKind: interrupt.agentKind,
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
finally {
|
|
867
|
+
releaseRunStart();
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* 清掉排在目标之前的所有排队任务,返回实际清除条数。
|
|
872
|
+
*
|
|
873
|
+
* 两条红线:
|
|
874
|
+
* - 目标可能在 interruptCurrentRun 等待期间已经接跑(链并发前进),那时它已不在
|
|
875
|
+
* 快照里,剩下的全部排在它**后面**——清除必须在目标位置之前停下,不得越界删掉
|
|
876
|
+
* 用户没要求清除的任务。
|
|
877
|
+
* - 移除循环必须整体同步:队列链在任何 await 处都会推进(卡片 PATCH 也算),
|
|
878
|
+
* 一旦让出事件循环,用户要求清除的任务可能趁撤销卡还在路上就跑起来了。
|
|
879
|
+
* 撤销卡片由 `removeFromQueue` 统一触发(fire-and-forget)。
|
|
880
|
+
*/
|
|
881
|
+
clearTasksAheadOfTarget(cwd, messageId) {
|
|
882
|
+
const tasks = this.bridge.getQueuedTasks(cwd);
|
|
883
|
+
const targetIdx = tasks.findIndex((t) => t.messageId === messageId);
|
|
884
|
+
let removedCount = 0;
|
|
885
|
+
for (let i = 0; i < targetIdx; i++) {
|
|
886
|
+
if (this.bridge.removeFromQueue(cwd, tasks[i].messageId)) {
|
|
887
|
+
removedCount++;
|
|
907
888
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
889
|
+
}
|
|
890
|
+
if (targetIdx < 0) {
|
|
891
|
+
getLogger().debug(`[router] queue.immediate: target no longer queued (began/cancelled), not clearing tasks behind it`);
|
|
892
|
+
}
|
|
893
|
+
return removedCount;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* `queue.immediate` 的终态反馈(唯一出口)。
|
|
897
|
+
*
|
|
898
|
+
* 承诺必须与真实调度一致:目标只有确实是停止后的下一个,才可以把它的卡翻成
|
|
899
|
+
* 「执行中」并说「将立即执行」;在途接跑/被撤销/被抢先/远程停止未确认四种状态
|
|
900
|
+
* 各给各的文案(把没发生的事说成发生了,比沉默更糟——用户会据文案决定要不要重发)。
|
|
901
|
+
*/
|
|
902
|
+
async reportImmediateOutcome(opts) {
|
|
903
|
+
const { cwd, messageId, ctx, removedCount, stopped, stopUnconfirmed, agentKind } = opts;
|
|
904
|
+
const targetStillQueued = this.bridge.getQueuedTask(cwd, messageId) !== undefined;
|
|
905
|
+
if (!targetStillQueued) {
|
|
906
|
+
// 目标在途接跑:已经在执行,说「无需重复操作」;真的被撤销了才说「未安排执行」。
|
|
907
|
+
if (this.bridge.hasTaskBegan(messageId)) {
|
|
908
|
+
await this.bridge.sendResult({ text: `ℹ️ 目标消息已开始执行,无需重复操作。已清除 ${removedCount} 条排队消息。` }, ctx);
|
|
917
909
|
}
|
|
918
910
|
else {
|
|
919
911
|
await this.bridge.sendResult({
|
|
920
|
-
text: `⚠️
|
|
912
|
+
text: `⚠️ 目标消息已不在队列中(可能已被撤销),未安排执行。已清除 ${removedCount} 条排队消息。`,
|
|
921
913
|
}, ctx);
|
|
922
914
|
}
|
|
915
|
+
return;
|
|
923
916
|
}
|
|
924
|
-
|
|
925
|
-
|
|
917
|
+
if (stopUnconfirmed) {
|
|
918
|
+
const name = agentKind ? agentDisplayName(agentKind) : 'agent';
|
|
919
|
+
await this.bridge.sendResult({
|
|
920
|
+
text: `⚠️ 未能确认 ${name} 的 turn 已停止,它可能仍在后台运行。\n` +
|
|
921
|
+
`已清除 ${removedCount} 条排队消息;您的消息已提交,但可能要等该 turn 结束后才开始。`,
|
|
922
|
+
}, ctx);
|
|
923
|
+
return;
|
|
926
924
|
}
|
|
925
|
+
if (!this.bridge.isBusyFor(cwd)) {
|
|
926
|
+
// Normal path: nothing new began during the stop, the target is next.
|
|
927
|
+
// Mark its own card as executing now so buttons grey out immediately.
|
|
928
|
+
// Done BEFORE any removal so getQueuedTask still returns the task with
|
|
929
|
+
// its (possibly edited) messagePreview for the card. The queue callback's
|
|
930
|
+
// later updateQueueCardToExecuting call becomes a no-op (idempotent).
|
|
931
|
+
await this.bridge.markQueueCardExecuting(cwd, messageId);
|
|
932
|
+
const stopPrefix = stopped ? '⚡ 已停止当前任务,' : '';
|
|
933
|
+
await this.bridge.sendResult({ text: `${stopPrefix}清除了 ${removedCount} 条排队消息。您的消息将立即执行。` }, ctx);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
await this.bridge.sendResult({
|
|
937
|
+
text: `⚠️ 排在目标之前的任务已开始执行,目标消息保持排队。已清除 ${removedCount} 条排队消息。`,
|
|
938
|
+
}, ctx);
|
|
927
939
|
}
|
|
928
940
|
/**
|
|
929
941
|
* Handle queue.diagnose: show diagnostic info for why message is queuing.
|
|
@@ -1095,8 +1107,6 @@ export class CommandRouter {
|
|
|
1095
1107
|
// replacement if the task starts during the subsequent await. Both
|
|
1096
1108
|
// setTaskReplacement and the begin-path consumption are synchronous, so
|
|
1097
1109
|
// there is no interleaving window.
|
|
1098
|
-
//
|
|
1099
|
-
///Step3:复用原任务 binding(不重新快照)+ 恢复 cwdOverride。
|
|
1100
1110
|
const inputTask = this.bridge.getQueuedTask(cwd, messageId);
|
|
1101
1111
|
if (!inputTask) {
|
|
1102
1112
|
// Task already left the queue (began or was cancelled) before we could
|
|
@@ -1106,9 +1116,7 @@ export class CommandRouter {
|
|
|
1106
1116
|
// toast/card field).
|
|
1107
1117
|
return { toast: { type: 'info', content: '任务已不在队列中(可能已开始执行或被撤销)' } };
|
|
1108
1118
|
}
|
|
1109
|
-
this.
|
|
1110
|
-
await this.handle(newMessage, ctx, { cwdOverride: cwd, binding: inputTask.binding });
|
|
1111
|
-
});
|
|
1119
|
+
this.registerEditedReplacement(cwd, messageId, newMessage, inputTask.binding, ctx);
|
|
1112
1120
|
// Update the message preview (and editedMessage so handleQueueImmediate
|
|
1113
1121
|
// re-enqueues the edited content instead of the stale original closure),
|
|
1114
1122
|
// and build the updated queue card. Returning the card in the callback
|
|
@@ -1247,11 +1255,7 @@ export class CommandRouter {
|
|
|
1247
1255
|
* Mirrors handleLsPage — updates the same card, never sends a new one.
|
|
1248
1256
|
*/
|
|
1249
1257
|
async handleResumePage(value, ctx) {
|
|
1250
|
-
const
|
|
1251
|
-
const validAgents = CommandRouter.VALID_AGENTS;
|
|
1252
|
-
const resolvedAgent = validAgents.includes(resumeAgent)
|
|
1253
|
-
? resumeAgent
|
|
1254
|
-
: this.config.defaultAgent;
|
|
1258
|
+
const resolvedAgent = resolveAgentKind(value.agent, this.config.defaultAgent);
|
|
1255
1259
|
const { pageSize, alignedOffset, error } = normalizePageArgs(value, RESUME_PAGE_SIZE);
|
|
1256
1260
|
if (error) {
|
|
1257
1261
|
return { toast: { type: 'error', content: error } };
|
|
@@ -1381,8 +1385,6 @@ export class CommandRouter {
|
|
|
1381
1385
|
* Returns null when the order no longer exists; index.ts surfaces the error.
|
|
1382
1386
|
*/
|
|
1383
1387
|
resolveOrderExecForQueue(orderId) {
|
|
1384
|
-
if (!this.orderStore.has(orderId))
|
|
1385
|
-
return null;
|
|
1386
1388
|
const order = this.orderStore.get().find((o) => o.id === orderId);
|
|
1387
1389
|
if (!order)
|
|
1388
1390
|
return null;
|
|
@@ -1812,6 +1814,26 @@ export class CommandRouter {
|
|
|
1812
1814
|
this.configCardIds.delete(oldest);
|
|
1813
1815
|
}
|
|
1814
1816
|
}
|
|
1817
|
+
/**
|
|
1818
|
+
* 把一次字段变更交给当前 agent 的 config builder(处理 provider→model 这类
|
|
1819
|
+
* 依赖关系),逐条写进草稿,再把草稿刷到所有配置卡。
|
|
1820
|
+
* `config.set`(下拉)与 `config.input`(输入框提交)共用同一套应用逻辑。
|
|
1821
|
+
*/
|
|
1822
|
+
async applyConfigFieldChange(key, newValue, ctx) {
|
|
1823
|
+
try {
|
|
1824
|
+
this.ensurePendingConfig();
|
|
1825
|
+
const defaultAgent = this.pendingConfig.defaultAgent ?? 'claude';
|
|
1826
|
+
const configBuilder = getConfigBuilder(defaultAgent);
|
|
1827
|
+
const patches = configBuilder.handleFieldChange(key, newValue, this.pendingConfig);
|
|
1828
|
+
for (const patch of patches) {
|
|
1829
|
+
this.setNestedValue(this.pendingConfig, patch.key, patch.value);
|
|
1830
|
+
}
|
|
1831
|
+
await this.refreshConfigCards(ctx);
|
|
1832
|
+
}
|
|
1833
|
+
catch (err) {
|
|
1834
|
+
await this.bridge.sendResult({ text: `设置失败: ${err.message}` }, ctx);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1815
1837
|
/**
|
|
1816
1838
|
* 把当前草稿刷到所有已知 config 卡片上(含本次点击的那张)。
|
|
1817
1839
|
*
|
|
@@ -1835,12 +1857,12 @@ export class CommandRouter {
|
|
|
1835
1857
|
}
|
|
1836
1858
|
}
|
|
1837
1859
|
/**
|
|
1838
|
-
* 串行化 config.* 卡片回调(2026-07-04
|
|
1860
|
+
* 串行化 config.* 卡片回调(2026-07-04 修复连击卡死 bug)。
|
|
1839
1861
|
*
|
|
1840
1862
|
* CardKit 2.0 input/button 回调经 `enqueueImmediate` 分发,不进 bridge 的
|
|
1841
|
-
*
|
|
1863
|
+
* 串行队列;多次快速点击下拉/文本框会并发读写 `pendingConfig` 并同时调
|
|
1842
1864
|
* `updateCardInPlace` → 两个 patch 请求乱序到达飞书,后到达的 patch(点击 1
|
|
1843
|
-
* 的旧卡片)覆盖先到达的 patch(点击 2
|
|
1865
|
+
* 的旧卡片)覆盖先到达的 patch(点击 2 的新卡片),用户看到控件卡死。
|
|
1844
1866
|
*
|
|
1845
1867
|
* 此方法把所有 config.* 动作串成一个 Promise chain:每个动作等前一个动作
|
|
1846
1868
|
* 的 `updateCardInPlace` 完全 settle 后再读 `pendingConfig`。
|
|
@@ -1863,28 +1885,6 @@ export class CommandRouter {
|
|
|
1863
1885
|
/** config.* 卡片动作分发(public:测试直接调用,替代 as unknown as)。 */
|
|
1864
1886
|
async dispatchConfigAction(value, ctx) {
|
|
1865
1887
|
switch (value.cmd) {
|
|
1866
|
-
case 'config.toggle': {
|
|
1867
|
-
// 通用 boolean 翻转:目前无卡片字段使用(output.show* 已移除),
|
|
1868
|
-
// 保留供未来 boolean 配置项复用;configActionQueue 串行化覆盖连击。
|
|
1869
|
-
const key = value.key;
|
|
1870
|
-
if (!key) {
|
|
1871
|
-
await this.bridge.sendResult({ text: '缺少配置项 key' }, ctx);
|
|
1872
|
-
return;
|
|
1873
|
-
}
|
|
1874
|
-
this.ensurePendingConfig();
|
|
1875
|
-
const current = getConfigValue(this.pendingConfig, key);
|
|
1876
|
-
// 布尔字段未设置时视为 false,而非报错(如布尔配置项尚未写入 config)
|
|
1877
|
-
const boolVal = typeof current === 'boolean' ? current : false;
|
|
1878
|
-
try {
|
|
1879
|
-
// 只改暂存区,不写盘;原地更新卡片而非发送新卡(2026-07-04)
|
|
1880
|
-
this.setNestedValue(this.pendingConfig, key, !boolVal);
|
|
1881
|
-
await this.refreshConfigCards(ctx);
|
|
1882
|
-
}
|
|
1883
|
-
catch (err) {
|
|
1884
|
-
await this.bridge.sendResult({ text: `设置失败: ${err.message}` }, ctx);
|
|
1885
|
-
}
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
1888
|
case 'config.set': {
|
|
1889
1889
|
// Set config value from select dropdown (option from CardKit 2.0)
|
|
1890
1890
|
const key = value.key;
|
|
@@ -1893,20 +1893,7 @@ export class CommandRouter {
|
|
|
1893
1893
|
await this.bridge.sendResult({ text: '缺少参数' }, ctx);
|
|
1894
1894
|
return;
|
|
1895
1895
|
}
|
|
1896
|
-
|
|
1897
|
-
this.ensurePendingConfig();
|
|
1898
|
-
// 使用 agent config builder 处理字段变更(处理依赖关系如 provider→model)
|
|
1899
|
-
const defaultAgent = this.pendingConfig.defaultAgent ?? 'claude';
|
|
1900
|
-
const configBuilder = getConfigBuilder(defaultAgent);
|
|
1901
|
-
const patches = configBuilder.handleFieldChange(key, newValue, this.pendingConfig);
|
|
1902
|
-
for (const patch of patches) {
|
|
1903
|
-
this.setNestedValue(this.pendingConfig, patch.key, patch.value);
|
|
1904
|
-
}
|
|
1905
|
-
await this.refreshConfigCards(ctx);
|
|
1906
|
-
}
|
|
1907
|
-
catch (err) {
|
|
1908
|
-
await this.bridge.sendResult({ text: `设置失败: ${err.message}` }, ctx);
|
|
1909
|
-
}
|
|
1896
|
+
await this.applyConfigFieldChange(key, newValue, ctx);
|
|
1910
1897
|
return;
|
|
1911
1898
|
}
|
|
1912
1899
|
case 'config.input': {
|
|
@@ -1924,20 +1911,7 @@ export class CommandRouter {
|
|
|
1924
1911
|
await this.bridge.sendResult({ text: '缺少输入值' }, ctx);
|
|
1925
1912
|
return;
|
|
1926
1913
|
}
|
|
1927
|
-
|
|
1928
|
-
this.ensurePendingConfig();
|
|
1929
|
-
// 使用 agent config builder 处理字段变更
|
|
1930
|
-
const defaultAgent = this.pendingConfig.defaultAgent ?? 'claude';
|
|
1931
|
-
const configBuilder = getConfigBuilder(defaultAgent);
|
|
1932
|
-
const patches = configBuilder.handleFieldChange(key, newValue, this.pendingConfig);
|
|
1933
|
-
for (const patch of patches) {
|
|
1934
|
-
this.setNestedValue(this.pendingConfig, patch.key, patch.value);
|
|
1935
|
-
}
|
|
1936
|
-
await this.refreshConfigCards(ctx);
|
|
1937
|
-
}
|
|
1938
|
-
catch (err) {
|
|
1939
|
-
await this.bridge.sendResult({ text: `设置失败: ${err.message}` }, ctx);
|
|
1940
|
-
}
|
|
1914
|
+
await this.applyConfigFieldChange(key, newValue, ctx);
|
|
1941
1915
|
return;
|
|
1942
1916
|
}
|
|
1943
1917
|
case 'config.save': {
|
|
@@ -2023,18 +1997,9 @@ export class CommandRouter {
|
|
|
2023
1997
|
this.bridge.setIdleTimeout(this.idleTimeoutMs);
|
|
2024
1998
|
// 2026-07-13: 检测是否有任何 agent 配置变更或 defaultAgent 切换
|
|
2025
1999
|
const hasDefaultAgentChange = oldDefaultAgent !== this.config.defaultAgent;
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
k.startsWith('codex.') ||
|
|
2030
|
-
k.startsWith('opencode.') ||
|
|
2031
|
-
k.startsWith('kimi.') ||
|
|
2032
|
-
k === 'defaultAgent' ||
|
|
2033
|
-
k === 'claude' ||
|
|
2034
|
-
k === 'pi' ||
|
|
2035
|
-
k === 'codex' ||
|
|
2036
|
-
k === 'opencode' ||
|
|
2037
|
-
k === 'kimi');
|
|
2000
|
+
const changedKeys = Object.keys(updates);
|
|
2001
|
+
const hasAgentConfigChange = changedKeys.includes('defaultAgent') ||
|
|
2002
|
+
AGENT_KINDS.some((kind) => touchesAgentConfig(changedKeys, kind));
|
|
2038
2003
|
// 当 defaultAgent 改变时,清除旧 agent 的 sessionId,
|
|
2039
2004
|
// 同时清除新 agent 的 sessionId(等效于 /new 命令),
|
|
2040
2005
|
// 并支持 session 恢复 - 切换回来时如果条件满足则恢复之前的 session。
|
|
@@ -2083,43 +2048,30 @@ export class CommandRouter {
|
|
|
2083
2048
|
switchSessionId = '';
|
|
2084
2049
|
}
|
|
2085
2050
|
}
|
|
2086
|
-
// DSH preset
|
|
2087
|
-
//
|
|
2088
|
-
// 旧 sessionId
|
|
2089
|
-
//
|
|
2090
|
-
|
|
2091
|
-
//
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
if (oldSessionId) {
|
|
2096
|
-
this.sessionStore.setPreviousSessionId(ctx.userId, 'dsh', oldSessionId);
|
|
2097
|
-
this.sessionStore.clearSessionId(ctx.userId, 'dsh');
|
|
2098
|
-
switchNotice = `DSH preset 已变更,旧会话已停放到恢复槽(可 /resume 校验后恢复),下次消息将新建会话`;
|
|
2099
|
-
}
|
|
2100
|
-
else {
|
|
2101
|
-
switchNotice = `DSH preset 已变更,下次消息将新建会话`;
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
// DSH host 变更 = 换服务:旧 host 的 sessionId 发往新 host 是错的。
|
|
2105
|
-
// 视为 session 边界,仿 preset 分支停车 + 清空当前 sessionId,下次 run 新建会话。
|
|
2106
|
-
const hasDshHostChange = Object.keys(updates).some((k) => k === 'agents.dsh.host');
|
|
2107
|
-
if (hasDshHostChange && this.config.defaultAgent === 'dsh' && !switchNotice) {
|
|
2051
|
+
// DSH 的两个「会话边界」变更共用一套处理:preset 在 session 创建时固定,
|
|
2052
|
+
// 中途切换会被服务端拒绝(agent-preset-conflict);host 变更等于换服务,
|
|
2053
|
+
// 旧 host 的 sessionId 发往新 host 是错的。两者都把旧 sessionId 停到
|
|
2054
|
+
// previousSessions 恢复槽(可 /resume 校验后恢复)并清空当前值,下次 run
|
|
2055
|
+
// 走 session.create 新建。已发生 agent 切换时(switchNotice 已设置)跳过,
|
|
2056
|
+
// 避免覆盖切换 notice——切换分支对 dsh 的 session 已做恢复/清空处理。
|
|
2057
|
+
const parkDshSessionBoundary = (changed, label) => {
|
|
2058
|
+
if (!changed || this.config.defaultAgent !== 'dsh' || switchNotice)
|
|
2059
|
+
return;
|
|
2108
2060
|
const oldSessionId = this.sessionStore.getSessionId(ctx.userId, 'dsh');
|
|
2109
2061
|
if (oldSessionId) {
|
|
2110
2062
|
this.sessionStore.setPreviousSessionId(ctx.userId, 'dsh', oldSessionId);
|
|
2111
2063
|
this.sessionStore.clearSessionId(ctx.userId, 'dsh');
|
|
2112
|
-
switchNotice = `DSH
|
|
2064
|
+
switchNotice = `DSH ${label} 已变更,旧会话已停放到恢复槽(可 /resume 校验后恢复),下次消息将新建会话`;
|
|
2113
2065
|
}
|
|
2114
2066
|
else {
|
|
2115
|
-
switchNotice = `DSH
|
|
2067
|
+
switchNotice = `DSH ${label} 已变更,下次消息将新建会话`;
|
|
2116
2068
|
}
|
|
2117
|
-
}
|
|
2069
|
+
};
|
|
2070
|
+
parkDshSessionBoundary(changedKeys.includes('agents.dsh.agentPreset'), 'preset');
|
|
2071
|
+
parkDshSessionBoundary(changedKeys.includes('agents.dsh.host'), 'host');
|
|
2118
2072
|
// 2026-07-13: 同步 agent 配置到 agentChoices(用于切换 agent 时恢复配置)
|
|
2119
2073
|
const currentAgent = this.config.defaultAgent;
|
|
2120
|
-
|
|
2121
|
-
const hasCurrentAgentUpdate = Object.keys(updates).some((k) => k === currentAgentConfigKey || k.startsWith(`${currentAgentConfigKey}.`));
|
|
2122
|
-
if (hasCurrentAgentUpdate) {
|
|
2074
|
+
if (touchesAgentsSubtree(changedKeys, currentAgent)) {
|
|
2123
2075
|
this.config = syncAgentChoices(this.config, currentAgent);
|
|
2124
2076
|
// agentChoices 同步写盘必须走原子写(tmp+rename),禁裸同步写盘
|
|
2125
2077
|
atomicWrite(this.configPath, YAML.stringify(this.config));
|
|
@@ -2131,19 +2083,8 @@ export class CommandRouter {
|
|
|
2131
2083
|
// duck method `updateApprovalMode` (codex thread/settings/update,
|
|
2132
2084
|
// kimi/opencode session/set_mode) so they take effect for subsequent
|
|
2133
2085
|
// turns without waiting for the runner to be recreated.
|
|
2134
|
-
const hasApprovalConfigChange =
|
|
2135
|
-
|
|
2136
|
-
k === 'codex' ||
|
|
2137
|
-
k.startsWith('codex.') ||
|
|
2138
|
-
k === 'agents.kimi' ||
|
|
2139
|
-
k.startsWith('agents.kimi.') ||
|
|
2140
|
-
k === 'kimi' ||
|
|
2141
|
-
k.startsWith('kimi.') ||
|
|
2142
|
-
k === 'agents.opencode' ||
|
|
2143
|
-
k.startsWith('agents.opencode.') ||
|
|
2144
|
-
k === 'opencode' ||
|
|
2145
|
-
k.startsWith('opencode.') ||
|
|
2146
|
-
k === 'agents.dsh.permissionPreset');
|
|
2086
|
+
const hasApprovalConfigChange = ['codex', 'kimi', 'opencode'].some((kind) => touchesAgentConfig(changedKeys, kind)) ||
|
|
2087
|
+
changedKeys.includes('agents.dsh.permissionPreset');
|
|
2147
2088
|
if (hasApprovalConfigChange) {
|
|
2148
2089
|
this.bridge.syncActiveApprovalModes();
|
|
2149
2090
|
}
|
|
@@ -2356,14 +2297,14 @@ export class CommandRouter {
|
|
|
2356
2297
|
cmdStatus(ctx) {
|
|
2357
2298
|
const entry = this.sessionStore.get(ctx.userId);
|
|
2358
2299
|
const cwd = entry?.cwd ?? '(未设置)';
|
|
2359
|
-
const sessionId = entry?.sessions
|
|
2300
|
+
const sessionId = entry?.sessions.get(this.config.defaultAgent) ?? '(无)';
|
|
2360
2301
|
// 查 bridge.activeRuns 而不是默认 runner.isRunning —— 因为 per-workspace
|
|
2361
2302
|
// runner 启动的 run 不会反映到默认 runner 状态。
|
|
2362
2303
|
const running = cwd !== '(未设置)' && this.bridge.isBusyFor(cwd);
|
|
2363
2304
|
const logger = getLogger();
|
|
2364
2305
|
const pid = process.pid;
|
|
2365
2306
|
const configDir = getConfigDir();
|
|
2366
|
-
const logFile =
|
|
2307
|
+
const logFile = logger.getCurrentLogFile();
|
|
2367
2308
|
// 构建 agent 状态信息
|
|
2368
2309
|
const agentLines = [];
|
|
2369
2310
|
if (cwd !== '(未设置)') {
|
|
@@ -2556,14 +2497,10 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2556
2497
|
}
|
|
2557
2498
|
const { events, usage, aiTitle, recap, displayTitle } = reader.readSessionContent(sessionId, cwd, { maxEvents: opts?.maxEvents });
|
|
2558
2499
|
const activeRun = this.bridge.getActiveRunFor(cwd);
|
|
2559
|
-
// 只检查内存中的 activeRun
|
|
2560
|
-
//
|
|
2500
|
+
// 只检查内存中的 activeRun 状态:存在且未收尾才算进行中。
|
|
2501
|
+
// 终态集合取 run-state 的单源判据(新增终态时不会漏掉这一处)。
|
|
2561
2502
|
const activeRunning = activeRun !== undefined &&
|
|
2562
|
-
activeRun.terminal
|
|
2563
|
-
activeRun.terminal !== 'error' &&
|
|
2564
|
-
activeRun.terminal !== 'interrupted' &&
|
|
2565
|
-
activeRun.terminal !== 'idle_timeout' &&
|
|
2566
|
-
activeRun.terminal !== 'goal_continuing' &&
|
|
2503
|
+
!isRunTerminal(activeRun.terminal) &&
|
|
2567
2504
|
activeRun.sessionId === sessionId;
|
|
2568
2505
|
const activeUsage = activeRunUsage(activeRun);
|
|
2569
2506
|
return {
|
|
@@ -2841,7 +2778,6 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2841
2778
|
cmdResume(args, ctx, offset = 0) {
|
|
2842
2779
|
return buildResumeCommand(args, ctx, offset, {
|
|
2843
2780
|
defaultAgent: this.config.defaultAgent,
|
|
2844
|
-
validAgents: CommandRouter.VALID_AGENTS,
|
|
2845
2781
|
getSessionEntry: (userId) => this.sessionStore.get(userId),
|
|
2846
2782
|
setSessionId: (userId, agent, sessionId, cwd) => this.sessionStore.setSessionId(userId, agent, sessionId, cwd),
|
|
2847
2783
|
defaultReader: () => this.sessionReader,
|