lark-remote 0.4.3 → 0.4.5
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 +21 -0
- package/dist/binder.d.ts +11 -10
- package/dist/binder.js +17 -16
- package/dist/bridge/inbound-media.d.ts +1 -27
- package/dist/bridge/inbound-media.js +13 -14
- package/dist/bridge/index.d.ts +49 -49
- package/dist/bridge/index.js +99 -92
- package/dist/bridge/queue-manager.d.ts +33 -26
- package/dist/bridge/queue-manager.js +117 -117
- package/dist/card/bash-renderer.js +4 -4
- package/dist/card/card-shared.d.ts +1 -1
- package/dist/card/card-shared.js +2 -2
- package/dist/card/run-renderer.js +8 -8
- package/dist/card/run-state.js +81 -31
- package/dist/card/tool-render.js +6 -2
- package/dist/cli.js +5 -6
- package/dist/clone.d.ts +1 -6
- package/dist/clone.js +4 -4
- package/dist/common/clamp.d.ts +2 -0
- package/dist/common/clamp.js +5 -0
- package/dist/common/fs.d.ts +4 -3
- package/dist/common/fs.js +5 -4
- package/dist/config/dir.d.ts +4 -5
- package/dist/config/dir.js +7 -8
- package/dist/config/wizard.js +2 -2
- package/dist/connector/index.d.ts +0 -2
- package/dist/connector/index.js +5 -5
- package/dist/inbound/receipt.d.ts +1 -3
- package/dist/inbound/receipt.js +2 -2
- package/dist/inbound/turn-assembler.d.ts +1 -3
- package/dist/inbound/turn-assembler.js +2 -2
- package/dist/index.js +30 -22
- package/dist/instance-lock.js +4 -4
- package/dist/persistence/atomic-write.js +3 -3
- package/dist/platform/agent-stopper.d.ts +1 -1
- package/dist/platform/terminator-posix.js +5 -5
- package/dist/platform/terminator-win32.js +1 -1
- package/dist/router/card-helpers.d.ts +1 -3
- package/dist/router/card-helpers.js +2 -2
- package/dist/router/config/codex.js +1 -1
- package/dist/router/config/common/model-patch.d.ts +1 -2
- package/dist/router/config/common/model-patch.js +1 -2
- package/dist/router/config/opencode.js +1 -1
- package/dist/router/config/pi.js +1 -1
- package/dist/router/index.d.ts +22 -22
- package/dist/router/index.js +71 -52
- package/dist/router/order-exec-dispatch.d.ts +3 -10
- package/dist/router/order-exec-dispatch.js +3 -3
- package/dist/router/{utils.d.ts → usage-format.d.ts} +2 -4
- package/dist/router/{utils.js → usage-format.js} +3 -8
- package/dist/runner/claude/runner.d.ts +1 -1
- package/dist/runner/claude/runner.js +1 -2
- package/dist/runner/claude/session.d.ts +2 -4
- package/dist/runner/claude/session.js +7 -9
- package/dist/runner/common/acp/{protocol-helpers.d.ts → acp-approval.d.ts} +2 -8
- package/dist/runner/common/acp/{protocol-helpers.js → acp-approval.js} +4 -3
- package/dist/runner/common/acp/base-acp-runner.d.ts +1 -1
- package/dist/runner/common/acp/base-acp-runner.js +1 -1
- package/dist/runner/common/acp/base-translator.d.ts +43 -0
- package/dist/runner/common/acp/base-translator.js +83 -18
- package/dist/runner/common/connection-based-runner.d.ts +4 -4
- package/dist/runner/common/connection-based-runner.js +3 -3
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +12 -12
- package/dist/runner/common/jsonrpc/connection-manager.js +36 -36
- package/dist/runner/common/{usage.d.ts → result-usage.d.ts} +1 -1
- package/dist/runner/common/{usage.js → result-usage.js} +2 -2
- package/dist/runner/common/spawning-runner.d.ts +2 -2
- package/dist/runner/common/spawning-runner.js +8 -8
- package/dist/runner/common/{runner-utils.d.ts → synthetic-events.d.ts} +4 -4
- package/dist/runner/common/{runner-utils.js → synthetic-events.js} +5 -5
- package/dist/runner/dsh/client.d.ts +1 -1
- package/dist/runner/dsh/client.js +1 -1
- package/dist/runner/dsh/runner.js +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.js +1 -1
- package/dist/runner/kimi/acp/translator.js +1 -1
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +1 -1
- package/dist/runner/opencode/acp/translator.d.ts +22 -1
- package/dist/runner/opencode/acp/translator.js +61 -1
- package/dist/runner/pi/rpc/runner.d.ts +1 -1
- package/dist/runner/pi/rpc/translator.d.ts +0 -1
- package/dist/runner/pi/rpc/translator.js +0 -2
- package/dist/runner/registry.d.ts +3 -3
- package/dist/runner/registry.js +3 -3
- package/dist/session/claude/session-index.d.ts +1 -1
- package/dist/session/claude/session-index.js +1 -1
- package/dist/session/claude/session-reader.d.ts +1 -1
- package/dist/session/claude/session-reader.js +1 -1
- package/dist/session/codex/rollout-reader.js +2 -2
- package/dist/session/dsh/sessions.d.ts +1 -1
- package/dist/session/dsh/sessions.js +1 -1
- package/dist/session/kimi/sessions.d.ts +0 -4
- package/dist/session/kimi/sessions.js +2 -8
- package/dist/session/opencode/sessions.js +3 -3
- package/dist/session/registry.d.ts +1 -1
- package/dist/session/registry.js +1 -1
- package/dist/session/session-store.d.ts +2 -3
- package/dist/session/session-store.js +2 -4
- package/dist/update/index.d.ts +1 -1
- package/dist/update/index.js +1 -1
- package/dist/update/version-check.d.ts +5 -3
- package/dist/update/version-check.js +10 -8
- package/dist/workspace/index.d.ts +4 -4
- package/dist/workspace/index.js +5 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { probeAllAgents } from './runner/probe.js';
|
|
|
9
9
|
import { warmCodexCatalogCache } from './config/codex-config.js';
|
|
10
10
|
import { SessionReaderRegistry, SessionStore } from './session/index.js';
|
|
11
11
|
import { ClaudeSessionReader, CodexSessionReader, OpencodeSessionReader, PiSessionReader, KimiSessionReader, DshSessionReader, } from './session/index.js';
|
|
12
|
-
import { CommandRouter, isImmediateAction, DIRECT_RETURN_CMDS, } from './router/index.js';
|
|
12
|
+
import { CommandRouter, isImmediateAction, DIRECT_RETURN_CMDS, SESSION_MUTATING_ACTION_CMDS, } from './router/index.js';
|
|
13
13
|
import { dispatchOrderExecForQueue } from './router/order-exec-dispatch.js';
|
|
14
14
|
import { buildCardActionFullValue } from './router/card-action-payload.js';
|
|
15
15
|
import { actionFeedbackText } from './router/card-action-feedback.js';
|
|
@@ -22,7 +22,7 @@ import { InstanceAlreadyRunningError, InstanceLock } from './instance-lock.js';
|
|
|
22
22
|
import { spawnReplacementBridge, waitForPreviousInstance } from './restart.js';
|
|
23
23
|
import { currentPlatform } from './platform/select.js';
|
|
24
24
|
import { startSleepBlocker } from './platform/sleep-blocker.js';
|
|
25
|
-
import { checkLatestVersion,
|
|
25
|
+
import { checkLatestVersion, compareVersions, runInstallLatest, formatUpdateHint, } from './update/index.js';
|
|
26
26
|
import { classifyRejection } from './error-classification.js';
|
|
27
27
|
import { WorkspaceStore } from './workspace/index.js';
|
|
28
28
|
import { InboundTurnAssembler } from './inbound/turn-assembler.js';
|
|
@@ -31,7 +31,7 @@ import { buildSessionHistoryCard } from './router/card-helpers.js';
|
|
|
31
31
|
import { newSessionButton, resumeCompactButton, agentDisplayName } from './card/card-shared.js';
|
|
32
32
|
import path from 'node:path';
|
|
33
33
|
import fs from 'node:fs';
|
|
34
|
-
import {
|
|
34
|
+
import { bestEffortUnlink } from './common/fs.js';
|
|
35
35
|
/**
|
|
36
36
|
* /stop 命令别名单源:普通消息的停止分支与 clone 活跃期拦截的排除条件
|
|
37
37
|
* 共用同一判定,防止将来加别名时两处漂移。
|
|
@@ -147,7 +147,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
147
147
|
stopGraceMs: claudeConfig.stopGraceMs,
|
|
148
148
|
settings: cliArgs.settings,
|
|
149
149
|
pidDir: configDir,
|
|
150
|
-
|
|
150
|
+
cwd: ws,
|
|
151
151
|
permissionMode: claudeConfig.permissionMode,
|
|
152
152
|
idleTtlMs: claudeConfig.idleTtlMinutes != null ? claudeConfig.idleTtlMinutes * 60_000 : undefined,
|
|
153
153
|
});
|
|
@@ -228,7 +228,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
228
228
|
.split(',')
|
|
229
229
|
.map((t) => t.trim())
|
|
230
230
|
.filter(Boolean),
|
|
231
|
-
|
|
231
|
+
cwd: ws,
|
|
232
232
|
sessionReader: piSessionReader,
|
|
233
233
|
});
|
|
234
234
|
});
|
|
@@ -308,7 +308,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
308
308
|
// 附件路径统一注入 prompt;无文本纯附件只回执(决策 2)。
|
|
309
309
|
const assembler = new InboundTurnAssembler({
|
|
310
310
|
onCommit: (turn, prompt) => {
|
|
311
|
-
// 入队时刻(T0)快照
|
|
311
|
+
// 入队时刻(T0)快照 cwd + agent/session 绑定,语义与旧路径一致
|
|
312
312
|
// (排队期间 /cd、/config 不再导致语义漂移)。
|
|
313
313
|
const messageId = turn.messageIds[turn.messageIds.length - 1] ?? '';
|
|
314
314
|
// turnMessageIds:终态收尾要覆盖本轮每一条消息(逐条挂的 Typing 得逐条撤)
|
|
@@ -318,17 +318,17 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
318
318
|
messageId,
|
|
319
319
|
turnMessageIds: turn.messageIds,
|
|
320
320
|
};
|
|
321
|
-
let
|
|
322
|
-
if (!
|
|
321
|
+
let cwd = sessionStore.getCwd(turn.userId) ?? '';
|
|
322
|
+
if (!cwd && workspaceStore) {
|
|
323
323
|
const workspaces = workspaceStore.list();
|
|
324
324
|
if (workspaces.length > 0)
|
|
325
|
-
|
|
325
|
+
cwd = workspaces[0].path;
|
|
326
326
|
}
|
|
327
327
|
const binding = bridge.currentBinding(turn.userId);
|
|
328
|
-
bridge.enqueue(
|
|
328
|
+
bridge.enqueue(cwd, async () => {
|
|
329
329
|
// allowCommandPrefix: false —— prompt 已剥离占位符,绝不再做命令分发。
|
|
330
330
|
await router.handle(prompt, ctx, {
|
|
331
|
-
cwdOverride:
|
|
331
|
+
cwdOverride: cwd,
|
|
332
332
|
binding,
|
|
333
333
|
allowCommandPrefix: false,
|
|
334
334
|
});
|
|
@@ -386,7 +386,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
386
386
|
// save() 的正常路径会清理临时文件;这里兜底 save 进入 mkdir
|
|
387
387
|
// try 之前意外抛错的情况,避免 os.tmpdir 残留。
|
|
388
388
|
for (const item of payload.media) {
|
|
389
|
-
|
|
389
|
+
bestEffortUnlink(item.tempPath);
|
|
390
390
|
}
|
|
391
391
|
throw err;
|
|
392
392
|
}
|
|
@@ -402,8 +402,8 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
402
402
|
});
|
|
403
403
|
});
|
|
404
404
|
connector.setMessageHandler((msg) => {
|
|
405
|
-
// 绑定/授权闸门:仅 owner 放行;非 owner
|
|
406
|
-
const decision = binder.
|
|
405
|
+
// 绑定/授权闸门:仅 owner 放行;非 owner 静默丢弃;未绑定时首条消息认领
|
|
406
|
+
const decision = binder.authorize(msg.userId, msg.chatId);
|
|
407
407
|
if (decision.kind === 'rejected')
|
|
408
408
|
return;
|
|
409
409
|
if (decision.kind === 'bind_success') {
|
|
@@ -562,11 +562,19 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
562
562
|
return;
|
|
563
563
|
}
|
|
564
564
|
const isImmediate = isImmediateAction(actionValue.cmd);
|
|
565
|
-
const
|
|
565
|
+
const cwd = sessionStore.getCwd(userId) ?? '';
|
|
566
566
|
// Build full value object — spread all fields from actionValue, then merge
|
|
567
567
|
// component out-of-band fields (option/formValue/inputValue/options) where
|
|
568
568
|
// present. See card-action-payload.ts for the component-type contract.
|
|
569
569
|
const fullValue = buildCardActionFullValue(actionValue, action);
|
|
570
|
+
// 定序(2026-10-07):会话 / cwd / agent 变更类卡片动作(new-session、
|
|
571
|
+
// resume.use、ls.switch、ws.use、config.save)执行前先冲刷入站装配窗口,
|
|
572
|
+
// 保证窗口内先到的文本消息在本动作之前 commit(绑定旧 session/cwd/agent)。
|
|
573
|
+
// 否则 700ms 静默窗内的消息会漂移到动作之后的新状态:例如输入「重新构建」
|
|
574
|
+
// 后立即点「新会话」,消息会落到新会话。名单以 router 单源为准。
|
|
575
|
+
if (SESSION_MUTATING_ACTION_CMDS.has(actionValue.cmd)) {
|
|
576
|
+
await assembler.flush(userId, chatId, 'flush');
|
|
577
|
+
}
|
|
570
578
|
// queue.input / config.save / approval.respond / approval.toggle /
|
|
571
579
|
// approval.answer 系列返回 CardActionResponse toast 给点击用户即时反馈。
|
|
572
580
|
// 必须直接返回值 -- enqueueImmediate / enqueueConfigAction 是
|
|
@@ -591,7 +599,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
591
599
|
void dispatchOrderExecForQueue({
|
|
592
600
|
router,
|
|
593
601
|
bridge,
|
|
594
|
-
|
|
602
|
+
cwd,
|
|
595
603
|
orderId: actionValue.orderId,
|
|
596
604
|
ctx: { userId, chatId, messageId },
|
|
597
605
|
}).catch((err) => logger.error('[control] order.exec dispatch failed:', err));
|
|
@@ -608,13 +616,13 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
608
616
|
.catch((err) => logger.error('[control] card action feedback failed:', err));
|
|
609
617
|
};
|
|
610
618
|
if (isImmediate) {
|
|
611
|
-
bridge.enqueueImmediate(
|
|
619
|
+
bridge.enqueueImmediate(cwd, async () => {
|
|
612
620
|
const res = await router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
613
621
|
forwardActionFeedback(res);
|
|
614
622
|
});
|
|
615
623
|
}
|
|
616
624
|
else {
|
|
617
|
-
bridge.enqueue(
|
|
625
|
+
bridge.enqueue(cwd, async () => {
|
|
618
626
|
const res = await router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
619
627
|
forwardActionFeedback(res);
|
|
620
628
|
}, {
|
|
@@ -624,7 +632,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
624
632
|
messageId,
|
|
625
633
|
messagePreview: `card action: ${actionValue.cmd}`,
|
|
626
634
|
// Compact 是单向操作,排队卡不允许编辑(编辑预览无意义)。
|
|
627
|
-
editable: actionValue.cmd !== '
|
|
635
|
+
editable: actionValue.cmd !== 'compact' && actionValue.cmd !== 'resume.compact',
|
|
628
636
|
},
|
|
629
637
|
});
|
|
630
638
|
}
|
|
@@ -651,7 +659,7 @@ async function main() {
|
|
|
651
659
|
initLogger({ dir: path.join(updateConfigDir, 'logs') });
|
|
652
660
|
try {
|
|
653
661
|
const { current, latest } = await checkLatestVersion();
|
|
654
|
-
if (
|
|
662
|
+
if (compareVersions(current, latest) !== 1) {
|
|
655
663
|
console.log(`Already up to date: ${current}`);
|
|
656
664
|
process.exit(0);
|
|
657
665
|
}
|
|
@@ -699,9 +707,9 @@ async function main() {
|
|
|
699
707
|
logger.info('[binder] awaiting first binding (any first private message binds)');
|
|
700
708
|
}
|
|
701
709
|
else {
|
|
702
|
-
logger.info(`[binder] bound to owner
|
|
710
|
+
logger.info(`[binder] bound to owner userId=${binder.boundUserId()}`);
|
|
703
711
|
}
|
|
704
|
-
const sessionStore = new SessionStore(path.join(configDir, 'last-session.json')
|
|
712
|
+
const sessionStore = new SessionStore(path.join(configDir, 'last-session.json'));
|
|
705
713
|
const workspaceStore = new WorkspaceStore(path.join(configDir, 'workspace.json'));
|
|
706
714
|
const bridge = new Bridge({
|
|
707
715
|
connector,
|
package/dist/instance-lock.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
|
-
import {
|
|
20
|
+
import { bestEffortUnlink } from './common/fs.js';
|
|
21
21
|
import { binaryName, verifyPidIdentityVerdict } from './platform/identity.js';
|
|
22
22
|
export class InstanceAlreadyRunningError extends Error {
|
|
23
23
|
pid;
|
|
@@ -86,7 +86,7 @@ export class InstanceLock {
|
|
|
86
86
|
throw new InstanceAlreadyRunningError(pid, this.lockPath);
|
|
87
87
|
}
|
|
88
88
|
// Stale lock: remove and retry once
|
|
89
|
-
|
|
89
|
+
bestEffortUnlink(this.lockPath);
|
|
90
90
|
try {
|
|
91
91
|
const fd = fs.openSync(this.lockPath, 'wx');
|
|
92
92
|
fs.writeSync(fd, this.lockContents(process.pid));
|
|
@@ -114,7 +114,7 @@ export class InstanceLock {
|
|
|
114
114
|
release() {
|
|
115
115
|
const { pid } = this.readPidAndName();
|
|
116
116
|
if (pid === process.pid) {
|
|
117
|
-
|
|
117
|
+
bestEffortUnlink(this.lockPath);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
registerExitHandlers() {
|
|
@@ -160,7 +160,7 @@ export class InstanceLock {
|
|
|
160
160
|
if (Number.isInteger(pid) && pid > 0 && name)
|
|
161
161
|
return { pid, name };
|
|
162
162
|
// Malformed or missing name — treat as corrupt, unlink and return undefined
|
|
163
|
-
|
|
163
|
+
bestEffortUnlink(this.lockPath);
|
|
164
164
|
return { pid: undefined, name: undefined };
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { bestEffortUnlink, withBusyRetry } from '../common/fs.js';
|
|
4
4
|
import { getLogger } from '../logger/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* Atomically write content (string or binary Buffer) to a file via tmp + rename.
|
|
@@ -63,7 +63,7 @@ export function atomicWrite(filePath, content, encoding = 'utf-8') {
|
|
|
63
63
|
// errors we must not leak the tmp file — except when the scene is
|
|
64
64
|
// deliberately preserved after busy-retry exhaustion.
|
|
65
65
|
if (!preserveTmpScene) {
|
|
66
|
-
|
|
66
|
+
bestEffortUnlink(tmpPath);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -86,7 +86,7 @@ export function atomicMoveFile(srcPath, destPath) {
|
|
|
86
86
|
catch (err) {
|
|
87
87
|
if (err.code === 'EXDEV') {
|
|
88
88
|
fs.copyFileSync(srcPath, destPath);
|
|
89
|
-
|
|
89
|
+
bestEffortUnlink(srcPath);
|
|
90
90
|
}
|
|
91
91
|
else {
|
|
92
92
|
throw err;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 不会来的退出事件。
|
|
8
8
|
*
|
|
9
9
|
* **键 = agent + pid,不是 agent 单维**。两条理由:
|
|
10
|
-
* 1. 同一 agent 可以同时有多个存活子进程(每个
|
|
10
|
+
* 1. 同一 agent 可以同时有多个存活子进程(每个 cwd 一条长驻连接,外加
|
|
11
11
|
* claude 的进程代际更替)。按 agent 单键索引时后注册的连接会**覆盖**前一个
|
|
12
12
|
* 的通道,Terminator 便拿着 A 工作区的通道去停 B 工作区的进程;
|
|
13
13
|
* 2. Terminator 手上只有 `proc`,只有 pid 能把「这条通道」与「正要停的那个
|
|
@@ -41,17 +41,17 @@ async function stopPosix(proc, opts, log) {
|
|
|
41
41
|
}
|
|
42
42
|
// 负 PID = 整个进程组:shell 包装层与孙进程一并带走
|
|
43
43
|
const pgid = -pid;
|
|
44
|
-
log('debug', `[
|
|
44
|
+
log('debug', `[terminator] sending SIGTERM to pgid=${pgid} immediate=${opts.immediate}`);
|
|
45
45
|
const termErr = signalGroup(pgid, 'SIGTERM');
|
|
46
46
|
if (termErr) {
|
|
47
|
-
log('warn', `[
|
|
47
|
+
log('warn', `[terminator] ${termErr}`);
|
|
48
48
|
return { requested: false, via: 'cooperative', error: termErr };
|
|
49
49
|
}
|
|
50
50
|
if (opts.immediate) {
|
|
51
51
|
// 不等待:SIGKILL 紧随其后;进程已死时 kill 抛 ESRCH,忽略即可
|
|
52
52
|
const killErr = signalGroup(pgid, 'SIGKILL');
|
|
53
53
|
if (killErr) {
|
|
54
|
-
log('warn', `[
|
|
54
|
+
log('warn', `[terminator] ${killErr}`);
|
|
55
55
|
return { requested: false, via: 'taskkill', error: killErr };
|
|
56
56
|
}
|
|
57
57
|
return { requested: true, via: 'taskkill' };
|
|
@@ -71,10 +71,10 @@ async function stopPosix(proc, opts, log) {
|
|
|
71
71
|
if (graceTimer)
|
|
72
72
|
clearTimeout(graceTimer);
|
|
73
73
|
if (!exited) {
|
|
74
|
-
log('info', `[
|
|
74
|
+
log('info', `[terminator] process group ${pgid} did not exit within grace period, sending SIGKILL`);
|
|
75
75
|
const killErr = signalGroup(pgid, 'SIGKILL');
|
|
76
76
|
if (killErr) {
|
|
77
|
-
log('warn', `[
|
|
77
|
+
log('warn', `[terminator] ${killErr}`);
|
|
78
78
|
return { requested: false, via: 'taskkill', error: killErr };
|
|
79
79
|
}
|
|
80
80
|
return { requested: true, via: 'taskkill' };
|
|
@@ -74,7 +74,7 @@ export function createWin32Terminator(deps) {
|
|
|
74
74
|
return { requested: true, via: 'taskkill' };
|
|
75
75
|
}
|
|
76
76
|
const agent = deps.agent;
|
|
77
|
-
// 按 agent + pid 查:同一 agent 可能有多条长驻连接(每
|
|
77
|
+
// 按 agent + pid 查:同一 agent 可能有多条长驻连接(每 cwd 一条),
|
|
78
78
|
// 只有 pid 能把通道归属钉到手上这个 proc 上。
|
|
79
79
|
const stopper = agent === undefined ? undefined : stoppers.get(agent, proc.pid);
|
|
80
80
|
if (!stopper) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { markdownDiv } from '../card/collapsible.js';
|
|
10
10
|
import type { AgentSessionContentEvent } from '../runner/index.js';
|
|
11
|
-
import { formatUsageStats } from './
|
|
11
|
+
import { formatUsageStats } from './usage-format.js';
|
|
12
12
|
type SessionUsageLike = Parameters<typeof formatUsageStats>[0];
|
|
13
13
|
/**
|
|
14
14
|
* Build a collapsible panel for a single session content event.
|
|
@@ -28,8 +28,6 @@ type SessionUsageLike = Parameters<typeof formatUsageStats>[0];
|
|
|
28
28
|
*/
|
|
29
29
|
export declare function sessionEventPanel(ev: AgentSessionContentEvent, index: number, totalEvents: number, tailExpandedCount?: number, agentKind?: string): object;
|
|
30
30
|
export { markdownDiv };
|
|
31
|
-
/** 分页卡「跳转页码」输入框的组件 name(仅用于结构断言与可读性)。 */
|
|
32
|
-
export declare const PAGE_JUMP_INPUT_NAME = "pageInput";
|
|
33
31
|
/** 页码输入非法时的统一提示文案(/ls /ws /resume /active /order 共用)。 */
|
|
34
32
|
export declare const PAGE_JUMP_INVALID_HINT = "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u9875\u7801\uFF08\u6B63\u6574\u6570\uFF09";
|
|
35
33
|
/**
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { agentDisplayName } from '../card/card-shared.js';
|
|
10
10
|
import { collapsibleMarkdownPanel, markdownDiv } from '../card/collapsible.js';
|
|
11
11
|
import { formatTimestamp } from '../card/time.js';
|
|
12
|
-
import { formatUsageStats } from './
|
|
12
|
+
import { formatUsageStats } from './usage-format.js';
|
|
13
13
|
/**
|
|
14
14
|
* Build a collapsible panel for a single session content event.
|
|
15
15
|
*
|
|
@@ -64,7 +64,7 @@ function eventLabel(ev, agentKind) {
|
|
|
64
64
|
}
|
|
65
65
|
export { markdownDiv };
|
|
66
66
|
/** 分页卡「跳转页码」输入框的组件 name(仅用于结构断言与可读性)。 */
|
|
67
|
-
|
|
67
|
+
const PAGE_JUMP_INPUT_NAME = 'pageInput';
|
|
68
68
|
/** 页码输入非法时的统一提示文案(/ls /ws /resume /active /order 共用)。 */
|
|
69
69
|
export const PAGE_JUMP_INVALID_HINT = '请输入有效的页码(正整数)';
|
|
70
70
|
/**
|
|
@@ -81,7 +81,7 @@ export class CodexConfigBuilder {
|
|
|
81
81
|
if (key === 'agents.codex.modelProvider' && typeof value === 'string') {
|
|
82
82
|
const codexCfg = loadCodexConfig();
|
|
83
83
|
const currentModel = config.agents?.codex?.model;
|
|
84
|
-
const modelPatch = resetModelPatch(
|
|
84
|
+
const modelPatch = resetModelPatch('agents.codex.model', currentModel, codexCfg.modelOptions(value));
|
|
85
85
|
if (modelPatch) {
|
|
86
86
|
patches.push(modelPatch);
|
|
87
87
|
const replacedModel = modelPatch.value;
|
|
@@ -10,13 +10,12 @@
|
|
|
10
10
|
* model is not offered by the new provider (and the new provider has models),
|
|
11
11
|
* patch the model to the provider's first option.
|
|
12
12
|
*
|
|
13
|
-
* @param providerKey The field key that changed, e.g. 'agents.codex.modelProvider'.
|
|
14
13
|
* @param modelKey The model field to reset, e.g. 'agents.codex.model'.
|
|
15
14
|
* @param currentModel The currently-configured model value (may be undefined).
|
|
16
15
|
* @param newModelOptions Models offered by the newly-selected provider.
|
|
17
16
|
* @returns A `{ key, value }` patch, or null when no reset is needed.
|
|
18
17
|
*/
|
|
19
|
-
export declare function resetModelPatch(
|
|
18
|
+
export declare function resetModelPatch(modelKey: string, currentModel: string | undefined, newModelOptions: string[]): {
|
|
20
19
|
key: string;
|
|
21
20
|
value: unknown;
|
|
22
21
|
} | null;
|
|
@@ -10,13 +10,12 @@
|
|
|
10
10
|
* model is not offered by the new provider (and the new provider has models),
|
|
11
11
|
* patch the model to the provider's first option.
|
|
12
12
|
*
|
|
13
|
-
* @param providerKey The field key that changed, e.g. 'agents.codex.modelProvider'.
|
|
14
13
|
* @param modelKey The model field to reset, e.g. 'agents.codex.model'.
|
|
15
14
|
* @param currentModel The currently-configured model value (may be undefined).
|
|
16
15
|
* @param newModelOptions Models offered by the newly-selected provider.
|
|
17
16
|
* @returns A `{ key, value }` patch, or null when no reset is needed.
|
|
18
17
|
*/
|
|
19
|
-
export function resetModelPatch(
|
|
18
|
+
export function resetModelPatch(modelKey, currentModel, newModelOptions) {
|
|
20
19
|
const currentModelIsValid = newModelOptions.some((m) => m === currentModel);
|
|
21
20
|
if (!currentModelIsValid && newModelOptions.length > 0) {
|
|
22
21
|
return { key: modelKey, value: newModelOptions[0] };
|
|
@@ -58,7 +58,7 @@ export class OpencodeConfigBuilder {
|
|
|
58
58
|
if (key === 'agents.opencode.providerID' && typeof value === 'string') {
|
|
59
59
|
const opencodeCfg = loadOpencodeConfig();
|
|
60
60
|
const currentModel = config.agents?.opencode?.modelID;
|
|
61
|
-
const modelPatch = resetModelPatch(
|
|
61
|
+
const modelPatch = resetModelPatch('agents.opencode.modelID', currentModel, opencodeCfg.modelOptions(value));
|
|
62
62
|
if (modelPatch)
|
|
63
63
|
patches.push(modelPatch);
|
|
64
64
|
}
|
package/dist/router/config/pi.js
CHANGED
|
@@ -39,7 +39,7 @@ export class PiConfigBuilder {
|
|
|
39
39
|
// provider 变更时,重置 model 为新 provider 的首个模型
|
|
40
40
|
if (key === 'agents.pi.provider' && typeof value === 'string') {
|
|
41
41
|
const currentModel = config.agents?.pi?.model;
|
|
42
|
-
const modelPatch = resetModelPatch(
|
|
42
|
+
const modelPatch = resetModelPatch('agents.pi.model', currentModel, getPiModelOptions(value));
|
|
43
43
|
if (modelPatch)
|
|
44
44
|
patches.push(modelPatch);
|
|
45
45
|
}
|
package/dist/router/index.d.ts
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import type { SessionStore, SessionReaderRegistry } from '../session/index.js';
|
|
2
2
|
import type { AppConfig } from '../config/index.js';
|
|
3
|
-
import type { Bridge } from '../bridge/index.js';
|
|
3
|
+
import type { Bridge, CommandContext, CommandResult } from '../bridge/index.js';
|
|
4
4
|
import type { AgentBinding } from '../bridge/queue-manager.js';
|
|
5
5
|
import type { CloneSession } from '../clone.js';
|
|
6
6
|
import { type VersionCheckResult, type InstallResult } from '../update/index.js';
|
|
7
|
-
interface CommandContext {
|
|
8
|
-
userId: string;
|
|
9
|
-
chatId: string;
|
|
10
|
-
messageId: string;
|
|
11
|
-
/** 本轮(装配窗口)全部入站 messageId,`messageId` 是最后一条。见 BridgeContext 同名字段(§B8)。 */
|
|
12
|
-
turnMessageIds?: string[];
|
|
13
|
-
}
|
|
14
|
-
interface CommandResult {
|
|
15
|
-
text?: string;
|
|
16
|
-
markdown?: string;
|
|
17
|
-
card?: object;
|
|
18
|
-
}
|
|
19
7
|
/**
|
|
20
8
|
* Card-action commands that must run with enqueue({ immediate: true }).
|
|
21
9
|
* These bypass the serial queue and execute immediately.
|
|
@@ -31,7 +19,7 @@ interface CommandResult {
|
|
|
31
19
|
* stop — interrupt current run
|
|
32
20
|
* ls.file — send file only
|
|
33
21
|
* ws.page — paginate /ws list only
|
|
34
|
-
* ws.remove — delete
|
|
22
|
+
* ws.remove — delete cwd alias only
|
|
35
23
|
* resume.use — set sessionId + agent for correct reader routing
|
|
36
24
|
* help.* — read-only help commands
|
|
37
25
|
* order.delete — delete order only
|
|
@@ -46,8 +34,8 @@ export interface CardActionPayload {
|
|
|
46
34
|
name?: string;
|
|
47
35
|
runId?: string;
|
|
48
36
|
sessionId?: string;
|
|
37
|
+
/** 目标工作目录(queue.* 卡片动作定位队列;stop 等卡片回传执行目录)。 */
|
|
49
38
|
cwd?: string;
|
|
50
|
-
workspace?: string;
|
|
51
39
|
messageId?: string;
|
|
52
40
|
userId?: string;
|
|
53
41
|
chatId?: string;
|
|
@@ -56,7 +44,7 @@ export interface CardActionPayload {
|
|
|
56
44
|
offset?: number;
|
|
57
45
|
/**
|
|
58
46
|
* `/ls` 浏览起点(本次 `/ls <dir>` 指定的目录;省略时 = cwd)。
|
|
59
|
-
*
|
|
47
|
+
* 卡片上的「返回」按钮回到这里,而不是回到会话工作目录。
|
|
60
48
|
*/
|
|
61
49
|
root?: string;
|
|
62
50
|
/** /resume 列表的 agent 类型。 */
|
|
@@ -114,6 +102,18 @@ export declare const APPROVAL_ACTION_CMDS: readonly string[];
|
|
|
114
102
|
* index.ts 控制层直返清单的唯一来源(勿在别处复制名单)。
|
|
115
103
|
*/
|
|
116
104
|
export declare const DIRECT_RETURN_CMDS: ReadonlySet<string>;
|
|
105
|
+
/**
|
|
106
|
+
* 会话 / cwd / agent 变更类卡片动作(单源)。这些动作会改写 sessionStore 的
|
|
107
|
+
* sessionId / cwd / defaultAgent,必须与入站装配器的 700ms 静默窗口定序:
|
|
108
|
+
* 控制层(index.ts)在执行它们之前先 flush 该 user/chat 的装配窗口,保证窗口内
|
|
109
|
+
* 先到的文本消息在本动作之前 commit(绑定动作前的旧状态)。否则窗口内消息会
|
|
110
|
+
* 漂移到动作之后的新状态(2026-10-07 事故:输入「重新构建」后立即点「新会话」,
|
|
111
|
+
* 消息落到新会话)。
|
|
112
|
+
*
|
|
113
|
+
* 文本命令(/new、/cd、/resume、/config)已由 index.ts 命令分支先 flush,
|
|
114
|
+
* 不在此列——只有卡片动作绕过了该分支。
|
|
115
|
+
*/
|
|
116
|
+
export declare const SESSION_MUTATING_ACTION_CMDS: ReadonlySet<string>;
|
|
117
117
|
export declare class CommandRouter {
|
|
118
118
|
/** Valid agent kinds — single source of truth for resume.use / resume.page / cmdResume. */
|
|
119
119
|
static readonly VALID_AGENTS: readonly ["claude", "codex", "opencode", "pi", "kimi", "dsh"];
|
|
@@ -205,7 +205,7 @@ export declare class CommandRouter {
|
|
|
205
205
|
cachePath?: string;
|
|
206
206
|
bypassCache?: boolean;
|
|
207
207
|
}) => Promise<VersionCheckResult>;
|
|
208
|
-
|
|
208
|
+
compareVersions?: (current: string, latest: string) => -1 | 0 | 1 | null;
|
|
209
209
|
runInstallLatest?: () => Promise<InstallResult>;
|
|
210
210
|
};
|
|
211
211
|
});
|
|
@@ -222,7 +222,7 @@ export declare class CommandRouter {
|
|
|
222
222
|
/**
|
|
223
223
|
* Route a message: if it starts with /, handle as command; if it starts with !, execute as bash; otherwise forward to Claude.
|
|
224
224
|
*
|
|
225
|
-
* 命令识别前置条件(2026-09-15 P0
|
|
225
|
+
* 命令识别前置条件(2026-09-15 P0 修复,`inbound-unified-input-design.md` §5.6):
|
|
226
226
|
* 裸前缀判定会被结构占位符命中 —— SDK 把富文本里的图片渲染成 ``,
|
|
227
227
|
* 首字符恰好是 `!`,整段用户消息被当 shell 命令执行(`executeBash "[image](img_v3_…"`)。
|
|
228
228
|
* 因此命令前缀只在「调用方声明纯文本 + 剥离占位符后非空且无占位符 + 首字符是 / 或 !」
|
|
@@ -249,7 +249,7 @@ export declare class CommandRouter {
|
|
|
249
249
|
* Routes to the appropriate bridge method based on value.cmd.
|
|
250
250
|
* Returns a toast response for immediate user feedback.
|
|
251
251
|
*/
|
|
252
|
-
/** queue.* 卡片动作通用守卫:
|
|
252
|
+
/** queue.* 卡片动作通用守卫:cwd/messageId 缺失时回复错误并返回 null。 */
|
|
253
253
|
private queuePayloadOrReply;
|
|
254
254
|
/** 答案类操作的统一失败反馈:重复投递(同一 nonce 第二次点击)是已生效的
|
|
255
255
|
* 中性事件,报 error 会误导(首次点击实际已成功)。 */
|
|
@@ -410,7 +410,7 @@ export declare class CommandRouter {
|
|
|
410
410
|
*/
|
|
411
411
|
private handleOrderTextInput;
|
|
412
412
|
/**
|
|
413
|
-
* Handle ws.use card action: switch to the
|
|
413
|
+
* Handle ws.use card action: switch to the cwd, then refresh the /ws
|
|
414
414
|
* list card in place so "recent" sort order is immediately visible.
|
|
415
415
|
*
|
|
416
416
|
* Unlike the command-line `/ws use` (which returns text), the card action
|
|
@@ -419,7 +419,7 @@ export declare class CommandRouter {
|
|
|
419
419
|
*/
|
|
420
420
|
private handleWsUse;
|
|
421
421
|
/**
|
|
422
|
-
* Handle ws.remove: remove the
|
|
422
|
+
* Handle ws.remove: remove the cwd alias and refresh the /ws list card
|
|
423
423
|
* in place (mirrors handleOrderDelete). Without this the stale alias would
|
|
424
424
|
* remain visible on the card the user just clicked.
|
|
425
425
|
*/
|
|
@@ -556,7 +556,7 @@ export declare class CommandRouter {
|
|
|
556
556
|
* @param offset 分页起点(条目数)
|
|
557
557
|
* @param rootDir 浏览起点:卡片「返回」按钮回到这里。省略时默认 = 本次列出的
|
|
558
558
|
* 目录(`/ls <dir>` 的起点即该目录),因此「返回」不会把用户
|
|
559
|
-
* 丢回
|
|
559
|
+
* 丢回 工作目录(用户明确反馈过)。
|
|
560
560
|
* @param q 关键词筛选:只匹配**当前层**条目名(不递归),大小写不敏感。
|
|
561
561
|
* 省略/空 = 无筛选。
|
|
562
562
|
*/
|