lark-remote 0.4.4 → 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 +10 -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/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/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.js +1 -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
|
@@ -34,7 +34,7 @@ export declare function newSessionButton(): object;
|
|
|
34
34
|
/**
|
|
35
35
|
* Create a CardKit 2.0 Compact button for any runCompact-capable runner
|
|
36
36
|
* (codex/kimi/opencode/pi/claude,鸭子类型 `'runCompact' in runner` 探测)。
|
|
37
|
-
* Triggers the compact flow via the
|
|
37
|
+
* Triggers the compact flow via the compact callback(handler 按
|
|
38
38
|
* runId 解析 agentKind,claude 走 stream-json 内建 /compact)。
|
|
39
39
|
*/
|
|
40
40
|
export declare function compactButton(runId: string): object;
|
package/dist/card/card-shared.js
CHANGED
|
@@ -84,11 +84,11 @@ export function newSessionButton() {
|
|
|
84
84
|
/**
|
|
85
85
|
* Create a CardKit 2.0 Compact button for any runCompact-capable runner
|
|
86
86
|
* (codex/kimi/opencode/pi/claude,鸭子类型 `'runCompact' in runner` 探测)。
|
|
87
|
-
* Triggers the compact flow via the
|
|
87
|
+
* Triggers the compact flow via the compact callback(handler 按
|
|
88
88
|
* runId 解析 agentKind,claude 走 stream-json 内建 /compact)。
|
|
89
89
|
*/
|
|
90
90
|
export function compactButton(runId) {
|
|
91
|
-
return cardButton('🗜 Compact', { cmd: '
|
|
91
|
+
return cardButton('🗜 Compact', { cmd: 'compact', runId }, { type: 'primary' });
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* Create a CardKit 2.0 Compact button for resume cards (auto-resume / `/resume <id>`).
|
|
@@ -3,7 +3,7 @@ import { collapsibleMarkdownPanel, markdownDiv } from './collapsible.js';
|
|
|
3
3
|
import { toolBodyMd, toolHeaderText } from './tool-render.js';
|
|
4
4
|
import { truncateUtf8, truncateMarkdownTables, CARD_BUDGET_BYTES } from './text-truncate.js';
|
|
5
5
|
import { formatTimestamp } from './time.js';
|
|
6
|
-
import { formatUsageStats } from '../router/
|
|
6
|
+
import { formatUsageStats } from '../router/usage-format.js';
|
|
7
7
|
import { renderApprovalArea } from './approval-render.js';
|
|
8
8
|
const REASONING_BYTES = 4_500;
|
|
9
9
|
const TEXT_BYTES = 10_000;
|
|
@@ -318,8 +318,8 @@ function assembleRunCard(state, options, elements) {
|
|
|
318
318
|
schema: '2.0',
|
|
319
319
|
config: { wide_screen_mode: true, update_multi: true },
|
|
320
320
|
header: {
|
|
321
|
-
template:
|
|
322
|
-
title: { content:
|
|
321
|
+
template: headerTemplate(state),
|
|
322
|
+
title: { content: headerTitle(state, options), tag: 'plain_text' },
|
|
323
323
|
},
|
|
324
324
|
body: { elements },
|
|
325
325
|
};
|
|
@@ -762,7 +762,7 @@ function buildSummaryContent(state, options = {}) {
|
|
|
762
762
|
// 压缩(operationKind='compaction')不会返回 agent 正文——引擎只压缩上下文
|
|
763
763
|
// 窗口,空内容属正常,不能沿用普通 run 的「(未返回内容)」异常信号
|
|
764
764
|
// (2026-09 用户反馈)。所有会压缩的 agent(codex/claude/kimi/opencode/pi)
|
|
765
|
-
// 都经
|
|
765
|
+
// 都经 streamCompact 走这条共享渲染路径,一处覆盖全部。
|
|
766
766
|
const empty = hasContent
|
|
767
767
|
? ''
|
|
768
768
|
: state.operationKind === 'compaction'
|
|
@@ -802,9 +802,9 @@ function buildDurationInfo(state) {
|
|
|
802
802
|
// 与标题一致:审批等待期间状态行显示「等待审批中」,而不是「工具调用中」。
|
|
803
803
|
if (hasPendingApproval(state))
|
|
804
804
|
return '✋ 等待审批中';
|
|
805
|
-
return
|
|
805
|
+
return footerText(state.footer);
|
|
806
806
|
}
|
|
807
|
-
function
|
|
807
|
+
function footerText(footer) {
|
|
808
808
|
if (footer === 'tool_running')
|
|
809
809
|
return '🧰 工具调用中';
|
|
810
810
|
if (footer === 'streaming')
|
|
@@ -816,7 +816,7 @@ function statusTagLabel(terminal) {
|
|
|
816
816
|
return terminalToLabel(terminal);
|
|
817
817
|
}
|
|
818
818
|
/** CardKit 2.0 header title — agent-aware via options.agentKind. */
|
|
819
|
-
function
|
|
819
|
+
function headerTitle(state, options = {}) {
|
|
820
820
|
const agent = agentDisplayName(options.agentKind ?? 'claude');
|
|
821
821
|
if (state.terminal === 'done')
|
|
822
822
|
return `✅ ${agent} · 已完成`;
|
|
@@ -845,7 +845,7 @@ function hasPendingApproval(state) {
|
|
|
845
845
|
return (state.approvals ?? []).some((a) => !a.expired);
|
|
846
846
|
}
|
|
847
847
|
/** CardKit 2.0 header template color */
|
|
848
|
-
function
|
|
848
|
+
function headerTemplate(state) {
|
|
849
849
|
return terminalToColor(state.terminal);
|
|
850
850
|
}
|
|
851
851
|
//# sourceMappingURL=run-renderer.js.map
|
package/dist/cli.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* CLI 引导层(bootstrap):在 import 应用模块图之前决定运行时(bun/node)并拉起它。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 1. 时序:preflight(Node 版本守卫、-v/-h/--advance-help)必须在 import 应用
|
|
5
|
+
*XX * 缺一不可:
|
|
6
|
+
* 1. 时序:preflight(Node 版本守卫、-v/-h/--advanced-help)必须在 import 应用
|
|
8
7
|
* 模块图之前跑完——dist/cli.js 刻意不使用 ES2020+ 语法以便在 node 12 上仍
|
|
9
8
|
* 可被解析,而 dist/index.js 是 ES2022;先 import platform/spawn 就等于先把
|
|
10
9
|
* 应用模块图拖进来,守卫当场失效(报错堆栈全落在 Node 内部)。
|
|
@@ -20,7 +19,7 @@ import fs from 'node:fs';
|
|
|
20
19
|
import path from 'node:path';
|
|
21
20
|
import { spawn, spawnSync } from 'node:child_process';
|
|
22
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
|
-
import {
|
|
22
|
+
import { printAdvancedHelp, printHelp, printVersion } from './config/dir.js';
|
|
24
23
|
const entryUrl = new URL('./index.js', import.meta.url);
|
|
25
24
|
const entry = fileURLToPath(entryUrl);
|
|
26
25
|
/**
|
|
@@ -49,8 +48,8 @@ export function handlePreflight(args, nodeVersion = process.versions.node) {
|
|
|
49
48
|
printHelp();
|
|
50
49
|
return 0;
|
|
51
50
|
}
|
|
52
|
-
if (arg === '--
|
|
53
|
-
|
|
51
|
+
if (arg === '--advanced-help') {
|
|
52
|
+
printAdvancedHelp();
|
|
54
53
|
return 0;
|
|
55
54
|
}
|
|
56
55
|
}
|
package/dist/clone.d.ts
CHANGED
|
@@ -59,11 +59,6 @@ type RegisterAppFn = (options: {
|
|
|
59
59
|
status: string;
|
|
60
60
|
}) => void;
|
|
61
61
|
}) => Promise<RegisterAppResult>;
|
|
62
|
-
/**
|
|
63
|
-
* 默认新实例拉起:同入口 + 同参数,仅替换 --config-dir(detached,返回 pid)。
|
|
64
|
-
* spawn 骨架(log fd / windowsHide / error 兜底)单源于 restart.ts。
|
|
65
|
-
*/
|
|
66
|
-
export declare function spawnDetachedCloneBridge(targetDir: string): number | undefined;
|
|
67
62
|
export interface CloneSessionOptions {
|
|
68
63
|
connector: CloneConnector;
|
|
69
64
|
/** 当前实例的 config.yaml 路径(配置复制源)。 */
|
|
@@ -111,7 +106,7 @@ export declare class CloneSession {
|
|
|
111
106
|
/**
|
|
112
107
|
* 扫码成功后的自动绑定落地:
|
|
113
108
|
* 1. 复制当前 config.yaml,仅替换 feishu.appId/appSecret;
|
|
114
|
-
* 2. 同步
|
|
109
|
+
* 2. 同步 cwd / 常用指令 / 会话状态(session 按 openId 重绑);
|
|
115
110
|
* 3. 用 registerApp 返回的新应用 openId 写 startup-contact.json(免认领绑定);
|
|
116
111
|
* 4. detached 拉起指向新目录的新实例(启动后自动向新应用发启动通知);
|
|
117
112
|
* 5. 在原应用回执「绑定完成 + 配置目录」,退出流程。
|
package/dist/clone.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from 'node:path';
|
|
|
6
6
|
import YAML from 'yaml';
|
|
7
7
|
import { getLogger } from './logger/index.js';
|
|
8
8
|
import { renderQrImage } from './config/qr.js';
|
|
9
|
-
import {
|
|
9
|
+
import { bestEffortUnlink } from './common/fs.js';
|
|
10
10
|
import { atomicWrite, atomicWriteJson } from './persistence/atomic-write.js';
|
|
11
11
|
import { spawnDetachedBridge } from './restart.js';
|
|
12
12
|
/** 退出命令(不区分大小写)。 */
|
|
@@ -48,7 +48,7 @@ export function generateCloneSuffix() {
|
|
|
48
48
|
* 默认新实例拉起:同入口 + 同参数,仅替换 --config-dir(detached,返回 pid)。
|
|
49
49
|
* spawn 骨架(log fd / windowsHide / error 兜底)单源于 restart.ts。
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
function spawnDetachedCloneBridge(targetDir) {
|
|
52
52
|
const args = [];
|
|
53
53
|
const argv = process.argv.slice(2);
|
|
54
54
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -238,7 +238,7 @@ export class CloneSession {
|
|
|
238
238
|
return;
|
|
239
239
|
}
|
|
240
240
|
finally {
|
|
241
|
-
|
|
241
|
+
bestEffortUnlink(qrPath);
|
|
242
242
|
}
|
|
243
243
|
await this.connector
|
|
244
244
|
.sendWithRetry(ctx.chatId, {
|
|
@@ -264,7 +264,7 @@ export class CloneSession {
|
|
|
264
264
|
/**
|
|
265
265
|
* 扫码成功后的自动绑定落地:
|
|
266
266
|
* 1. 复制当前 config.yaml,仅替换 feishu.appId/appSecret;
|
|
267
|
-
* 2. 同步
|
|
267
|
+
* 2. 同步 cwd / 常用指令 / 会话状态(session 按 openId 重绑);
|
|
268
268
|
* 3. 用 registerApp 返回的新应用 openId 写 startup-contact.json(免认领绑定);
|
|
269
269
|
* 4. detached 拉起指向新目录的新实例(启动后自动向新应用发启动通知);
|
|
270
270
|
* 5. 在原应用回执「绑定完成 + 配置目录」,退出流程。
|
package/dist/common/fs.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unlink a file,
|
|
2
|
+
* Unlink a file, swallowing errors instead of throwing to the caller.
|
|
3
3
|
*
|
|
4
4
|
* Use for cleanup paths (pid files, temp files, stale locks) where the
|
|
5
5
|
* file may already be gone or was never created — a missing file is not
|
|
6
|
-
* an error worth propagating.
|
|
6
|
+
* an error worth propagating. Not truly silent: gives up on busy files
|
|
7
|
+
* with a warning log (see below).
|
|
7
8
|
*/
|
|
8
|
-
export declare function
|
|
9
|
+
export declare function bestEffortUnlink(path: string): void;
|
|
9
10
|
export declare function isBusyError(err: unknown): boolean;
|
|
10
11
|
export interface BusyRetryOptions {
|
|
11
12
|
/** 平台注入(测试用);默认当前宿主。占用重试仅 win32 生效 */
|
package/dist/common/fs.js
CHANGED
|
@@ -2,20 +2,21 @@ import fs from 'node:fs';
|
|
|
2
2
|
import { getLogger } from '../logger/index.js';
|
|
3
3
|
import { currentPlatform, isWin32 } from '../platform/select.js';
|
|
4
4
|
/**
|
|
5
|
-
* Unlink a file,
|
|
5
|
+
* Unlink a file, swallowing errors instead of throwing to the caller.
|
|
6
6
|
*
|
|
7
7
|
* Use for cleanup paths (pid files, temp files, stale locks) where the
|
|
8
8
|
* file may already be gone or was never created — a missing file is not
|
|
9
|
-
* an error worth propagating.
|
|
9
|
+
* an error worth propagating. Not truly silent: gives up on busy files
|
|
10
|
+
* with a warning log (see below).
|
|
10
11
|
*/
|
|
11
|
-
export function
|
|
12
|
+
export function bestEffortUnlink(path) {
|
|
12
13
|
try {
|
|
13
14
|
withBusyRetry(() => fs.unlinkSync(path));
|
|
14
15
|
}
|
|
15
16
|
catch (err) {
|
|
16
17
|
// 占用放弃(win32 重试耗尽 / posix 首次占用即失败):按设计「告警,
|
|
17
18
|
// 不静默吞」,但清理路径不向调用方抛错(保持本函数契约)
|
|
18
|
-
getLogger().warn(`[fs]
|
|
19
|
+
getLogger().warn(`[fs] bestEffortUnlink gave up on busy file ${path}: ${String(err)}`);
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
/**
|
package/dist/config/dir.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ interface CliArgs {
|
|
|
5
5
|
help?: boolean;
|
|
6
6
|
version?: boolean;
|
|
7
7
|
update?: boolean;
|
|
8
|
-
|
|
8
|
+
advancedHelp?: boolean;
|
|
9
9
|
}
|
|
10
10
|
export declare function resolveConfigDir(configDirArg: string | undefined): string;
|
|
11
11
|
export declare function getConfigDir(): string;
|
|
@@ -18,11 +18,10 @@ export declare function printVersion(): void;
|
|
|
18
18
|
/** Print CLI help to stdout. */
|
|
19
19
|
export declare function printHelp(): void;
|
|
20
20
|
/**
|
|
21
|
-
* Print advanced-config reference to stdout (CLI `--
|
|
21
|
+
* Print advanced-config reference to stdout (CLI `--advanced-help`).
|
|
22
22
|
*
|
|
23
23
|
* 目标读者是 coding agent(用户让 agent 代改 config.yaml):披露所有
|
|
24
|
-
* /config 卡片未暴露的 YAML
|
|
25
|
-
* 等价命令为 /advancehelp(内容同源,卡片渲染)。
|
|
24
|
+
* /config 卡片未暴露的 YAML 字段——名称、默认值、语义、改法。
|
|
26
25
|
*/
|
|
27
|
-
export declare function
|
|
26
|
+
export declare function printAdvancedHelp(): void;
|
|
28
27
|
export {};
|
package/dist/config/dir.js
CHANGED
|
@@ -52,8 +52,8 @@ export function parseCliArgs(args = process.argv.slice(2)) {
|
|
|
52
52
|
else if (arg === '--dev') {
|
|
53
53
|
result.dev = true;
|
|
54
54
|
}
|
|
55
|
-
else if (arg === '--
|
|
56
|
-
result.
|
|
55
|
+
else if (arg === '--advanced-help') {
|
|
56
|
+
result.advancedHelp = true;
|
|
57
57
|
}
|
|
58
58
|
else if (arg === '--update' || arg === 'update') {
|
|
59
59
|
// 裸子命令等价形式:`lark-remote update` 与 `--update` 同义。
|
|
@@ -94,24 +94,23 @@ export function printHelp() {
|
|
|
94
94
|
' --settings <path> 指定 Claude 配置文件路径',
|
|
95
95
|
' --dev 开发模式:标记从源码 bun src/index.ts 启动(看门狗据此选择拉起方式)',
|
|
96
96
|
' --update 升级到最新版本后退出(用于 cron/脚本自动化升级)',
|
|
97
|
-
' --
|
|
97
|
+
' --advanced-help 高级配置参考:config.yaml 全部高级字段的语义与改法(面向 coding agent)',
|
|
98
98
|
' -h, --help 显示本帮助信息',
|
|
99
99
|
' -v, --version 显示版本号',
|
|
100
100
|
];
|
|
101
101
|
process.stdout.write(lines.join('\n') + '\n');
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
|
-
* Print advanced-config reference to stdout (CLI `--
|
|
104
|
+
* Print advanced-config reference to stdout (CLI `--advanced-help`).
|
|
105
105
|
*
|
|
106
106
|
* 目标读者是 coding agent(用户让 agent 代改 config.yaml):披露所有
|
|
107
|
-
* /config 卡片未暴露的 YAML
|
|
108
|
-
* 等价命令为 /advancehelp(内容同源,卡片渲染)。
|
|
107
|
+
* /config 卡片未暴露的 YAML 字段——名称、默认值、语义、改法。
|
|
109
108
|
*/
|
|
110
|
-
export function
|
|
109
|
+
export function printAdvancedHelp() {
|
|
111
110
|
const configDir = resolveConfigDir(parseCliArgs().configDir);
|
|
112
111
|
const configPath = configDir + '/config.yaml';
|
|
113
112
|
const lines = [
|
|
114
|
-
'lark-remote 高级配置参考(--
|
|
113
|
+
'lark-remote 高级配置参考(--advanced-help)',
|
|
115
114
|
'',
|
|
116
115
|
'目标读者:coding agent。用户把本输出交给 agent,agent 据此代改配置。',
|
|
117
116
|
'',
|
package/dist/config/wizard.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import YAML from 'yaml';
|
|
5
5
|
import { AppConfigSchema } from './index.js';
|
|
6
6
|
import { needsQrImage, renderQrImage, renderTerminalQr } from './qr.js';
|
|
7
|
-
import {
|
|
7
|
+
import { bestEffortUnlink } from '../common/fs.js';
|
|
8
8
|
import { atomicWrite } from '../persistence/atomic-write.js';
|
|
9
9
|
/** Where the fallback QR image is written (next to the config it will seed). */
|
|
10
10
|
const QR_IMAGE_NAME = 'qr-code.gif';
|
|
@@ -77,7 +77,7 @@ async function runRegistrationWizard(configPath) {
|
|
|
77
77
|
}
|
|
78
78
|
finally {
|
|
79
79
|
if (wroteImage)
|
|
80
|
-
|
|
80
|
+
bestEffortUnlink(imagePath);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
function isInteractiveTerminal() {
|
|
@@ -107,8 +107,6 @@ type CardActionHandler = (action: CardActionEvent) => void | CardActionResponse
|
|
|
107
107
|
* 卡片 patch 的重试上限。含义:1 次原始尝试 + 最多 PATCH_MAX_RETRIES 次重试。
|
|
108
108
|
*/
|
|
109
109
|
export declare const PATCH_MAX_RETRIES = 3;
|
|
110
|
-
/** 重试退避基数(ms);第 n 次重试前等待 n × 基数,给对端恢复的时间。 */
|
|
111
|
-
export declare const PATCH_RETRY_BASE_DELAY_MS = 150;
|
|
112
110
|
export declare class FeishuConnector {
|
|
113
111
|
/** 飞书 channel 实例(public:测试直接访问/注入 mock,替代 as unknown as)。 */
|
|
114
112
|
channel: LarkChannel;
|
package/dist/connector/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { sleep } from '../common/sleep.js';
|
|
|
7
7
|
import { isTransientTransportError } from '../error-classification.js';
|
|
8
8
|
import axios from 'axios';
|
|
9
9
|
import fs from 'node:fs';
|
|
10
|
-
import {
|
|
10
|
+
import { bestEffortUnlink } from '../common/fs.js';
|
|
11
11
|
import { displayName } from '../platform/path.js';
|
|
12
12
|
import https from 'node:https';
|
|
13
13
|
import os from 'node:os';
|
|
@@ -145,7 +145,7 @@ function shouldRetrySendError(err) {
|
|
|
145
145
|
*/
|
|
146
146
|
export const PATCH_MAX_RETRIES = 3;
|
|
147
147
|
/** 重试退避基数(ms);第 n 次重试前等待 n × 基数,给对端恢复的时间。 */
|
|
148
|
-
|
|
148
|
+
const PATCH_RETRY_BASE_DELAY_MS = 150;
|
|
149
149
|
/** 记日志用的错误摘要(不打印 axios 巨型循环对象)。 */
|
|
150
150
|
function formatErrorForLog(err) {
|
|
151
151
|
if (err instanceof Error) {
|
|
@@ -403,9 +403,9 @@ export class FeishuConnector {
|
|
|
403
403
|
for (const res of msg.resources) {
|
|
404
404
|
const tmpPath = path.join(os.tmpdir(), `lark-remote-inbound-${randomUUID()}`);
|
|
405
405
|
try {
|
|
406
|
-
const { contentType, bytesWritten } = await withTimeout(this.channel.downloadResourceToFile(msg.messageId, res.fileKey, res.type, tmpPath), timeoutMs, `downloadResource fileKey=${res.fileKey}`, () =>
|
|
406
|
+
const { contentType, bytesWritten } = await withTimeout(this.channel.downloadResourceToFile(msg.messageId, res.fileKey, res.type, tmpPath), timeoutMs, `downloadResource fileKey=${res.fileKey}`, () => bestEffortUnlink(tmpPath));
|
|
407
407
|
if (bytesWritten > maxBytes) {
|
|
408
|
-
|
|
408
|
+
bestEffortUnlink(tmpPath);
|
|
409
409
|
const limitMb = maxBytes / (1024 * 1024);
|
|
410
410
|
failures.push({
|
|
411
411
|
kind: res.kind,
|
|
@@ -429,7 +429,7 @@ export class FeishuConnector {
|
|
|
429
429
|
catch (err) {
|
|
430
430
|
// 超时不在这里删文件:上面已把清理挂到底层传输的 settle 上。
|
|
431
431
|
if (!(err instanceof ResourceTimeoutError))
|
|
432
|
-
|
|
432
|
+
bestEffortUnlink(tmpPath);
|
|
433
433
|
getLogger().warn(`[feishu] downloadResource failed fileKey=${res.fileKey} type=${res.type}:`, err.message);
|
|
434
434
|
failures.push({
|
|
435
435
|
kind: res.kind,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 入站回执文案(`inbound-unified-input-design.md` §5.7)。
|
|
3
3
|
*
|
|
4
4
|
* 两类回执合并成一条:
|
|
5
5
|
* 1. 无文本纯附件(决策 2):不自动起 turn,只回已保存路径 + 引导语;
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { PlaceholderKind } from './placeholder.js';
|
|
12
12
|
import type { RejectedItem } from './turn.js';
|
|
13
|
-
/** 单条回执最多列出的文件数;超出折叠为 "… 等 N 个文件"(沿用旧提示口径)。 */
|
|
14
|
-
export declare const MAX_PATHS_IN_RECEIPT = 10;
|
|
15
13
|
/** 「不支持」回执句式(§5.7 表:不支持类型统一「暂不支持 X」)。 */
|
|
16
14
|
export declare function unsupportedReason(kind: PlaceholderKind): string;
|
|
17
15
|
export interface ReceiptInput {
|
package/dist/inbound/receipt.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 入站回执文案(`inbound-unified-input-design.md` §5.7)。
|
|
3
3
|
*
|
|
4
4
|
* 两类回执合并成一条:
|
|
5
5
|
* 1. 无文本纯附件(决策 2):不自动起 turn,只回已保存路径 + 引导语;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
/** 单条回执最多列出的文件数;超出折叠为 "… 等 N 个文件"(沿用旧提示口径)。 */
|
|
13
|
-
|
|
13
|
+
const MAX_PATHS_IN_RECEIPT = 10;
|
|
14
14
|
/** 占位符种类 → 面向人的名称。 */
|
|
15
15
|
const PLACEHOLDER_LABELS = {
|
|
16
16
|
image: '图片',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* InboundTurnAssembler:统一静默期窗口 + 合并 + 下载等待 + commit。
|
|
3
3
|
*
|
|
4
|
-
* 设计依据:`docs/zh/architecture/inbound-unified-input-design.md` §4
|
|
4
|
+
* 设计依据:`docs/zh/architecture/inbound-unified-input-design.md` §4、§5.2。
|
|
5
5
|
*
|
|
6
6
|
* 规则:
|
|
7
7
|
* - 任何事件到达 → **重置** 700ms 定时器(按 `userId:chatId` 分桶),保证图/文任意顺序等价;
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { InboundEvent } from './event.js';
|
|
15
15
|
import { type CommitReason, type InboundTurn } from './turn.js';
|
|
16
|
-
/** 统一静默期窗口(ms)——决策 1:不做「纯文本零延迟」快路径。 */
|
|
17
|
-
export declare const DEFAULT_TURN_WINDOW_MS = 700;
|
|
18
16
|
export interface AssemblerContext {
|
|
19
17
|
userId: string;
|
|
20
18
|
chatId: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* InboundTurnAssembler:统一静默期窗口 + 合并 + 下载等待 + commit。
|
|
3
3
|
*
|
|
4
|
-
* 设计依据:`docs/zh/architecture/inbound-unified-input-design.md` §4
|
|
4
|
+
* 设计依据:`docs/zh/architecture/inbound-unified-input-design.md` §4、§5.2。
|
|
5
5
|
*
|
|
6
6
|
* 规则:
|
|
7
7
|
* - 任何事件到达 → **重置** 700ms 定时器(按 `userId:chatId` 分桶),保证图/文任意顺序等价;
|
|
@@ -17,7 +17,7 @@ import { buildPrompt } from './prompt.js';
|
|
|
17
17
|
import { buildReceipt, unsupportedReason } from './receipt.js';
|
|
18
18
|
import { bucketKey, } from './turn.js';
|
|
19
19
|
/** 统一静默期窗口(ms)——决策 1:不做「纯文本零延迟」快路径。 */
|
|
20
|
-
|
|
20
|
+
const DEFAULT_TURN_WINDOW_MS = 700;
|
|
21
21
|
export class InboundTurnAssembler {
|
|
22
22
|
deps;
|
|
23
23
|
buckets = new Map();
|
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
|
/**
|