lark-remote 0.1.13 → 0.2.1
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 +36 -3
- package/README.en.md +7 -11
- package/README.md +7 -11
- package/dist/bridge/final-usage.d.ts +24 -0
- package/dist/bridge/final-usage.js +55 -0
- package/dist/bridge/index.d.ts +0 -7
- package/dist/bridge/index.js +22 -64
- package/dist/bridge/queue-manager.js +4 -3
- package/dist/card/approval-render.js +39 -94
- package/dist/card/bash-renderer.js +12 -21
- package/dist/card/card-shared.d.ts +13 -1
- package/dist/card/card-shared.js +21 -30
- package/dist/card/run-renderer.d.ts +0 -22
- package/dist/card/run-renderer.js +206 -153
- package/dist/card/run-state.d.ts +29 -1
- package/dist/card/run-state.js +172 -84
- package/dist/card/text-truncate.d.ts +0 -9
- package/dist/card/text-truncate.js +0 -9
- package/dist/card/tool-render.d.ts +4 -1
- package/dist/card/tool-render.js +76 -32
- package/dist/cli.js +2 -2
- package/dist/common/fs.d.ts +14 -0
- package/dist/common/fs.js +57 -3
- package/dist/config/codex-config.js +2 -0
- package/dist/config/index.d.ts +0 -5
- package/dist/config/index.js +0 -11
- package/dist/config/kimi-config.js +1 -0
- package/dist/config/opencode-config.js +13 -2
- package/dist/config/qr.d.ts +36 -0
- package/dist/config/qr.js +124 -0
- package/dist/config/wizard.js +69 -27
- package/dist/connector/index.d.ts +3 -2
- package/dist/connector/index.js +5 -4
- package/dist/index.js +15 -42
- package/dist/instance-lock.js +7 -0
- package/dist/persistence/atomic-write.d.ts +2 -0
- package/dist/persistence/atomic-write.js +20 -5
- package/dist/persistence/load-json-file.d.ts +1 -2
- package/dist/persistence/load-json-file.js +2 -3
- package/dist/platform/agent-stopper.d.ts +22 -0
- package/dist/platform/agent-stopper.js +19 -0
- package/dist/platform/command.d.ts +11 -0
- package/dist/platform/command.js +107 -0
- package/dist/platform/identity.d.ts +33 -0
- package/dist/platform/identity.js +187 -0
- package/dist/platform/path.d.ts +24 -0
- package/dist/platform/path.js +110 -0
- package/dist/platform/select.d.ts +9 -0
- package/dist/platform/select.js +12 -0
- package/dist/platform/shell.d.ts +51 -0
- package/dist/platform/shell.js +75 -0
- package/dist/platform/spawn.d.ts +41 -0
- package/dist/platform/spawn.js +77 -0
- package/dist/platform/terminator-posix.d.ts +38 -0
- package/dist/platform/terminator-posix.js +103 -0
- package/dist/platform/terminator-win32.d.ts +21 -0
- package/dist/platform/terminator-win32.js +110 -0
- package/dist/platform/terminator.d.ts +44 -0
- package/dist/platform/terminator.js +24 -0
- package/dist/platform/types.d.ts +23 -0
- package/dist/platform/types.js +5 -0
- package/dist/restart.js +4 -2
- package/dist/router/card-helpers.d.ts +33 -6
- package/dist/router/card-helpers.js +67 -11
- package/dist/router/config/common/render.d.ts +1 -1
- package/dist/router/config/common/render.js +1 -1
- package/dist/router/config-diff.d.ts +4 -0
- package/dist/router/config-diff.js +70 -0
- package/dist/router/index.d.ts +66 -6
- package/dist/router/index.js +451 -316
- package/dist/router/utils.d.ts +6 -0
- package/dist/router/utils.js +12 -0
- package/dist/runner/bash/runner.d.ts +9 -2
- package/dist/runner/bash/runner.js +45 -20
- package/dist/runner/claude/session.d.ts +4 -1
- package/dist/runner/claude/session.js +18 -4
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
- package/dist/runner/codex/app-server/translator.d.ts +18 -0
- package/dist/runner/codex/app-server/translator.js +146 -0
- package/dist/runner/common/acp/base-acp-runner.d.ts +116 -0
- package/dist/runner/common/acp/base-acp-runner.js +248 -0
- package/dist/runner/common/acp/base-translator.d.ts +141 -0
- package/dist/runner/common/acp/base-translator.js +271 -0
- package/dist/runner/common/acp/protocol-helpers.d.ts +39 -0
- package/dist/runner/common/acp/protocol-helpers.js +34 -0
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -0
- package/dist/runner/common/jsonrpc/transport.js +25 -5
- package/dist/runner/common/process-stopper.d.ts +4 -26
- package/dist/runner/common/process-stopper.js +4 -76
- package/dist/runner/common/runner-utils.d.ts +0 -5
- package/dist/runner/common/runner-utils.js +0 -7
- package/dist/runner/common/spawning-runner.d.ts +42 -132
- package/dist/runner/common/spawning-runner.js +52 -271
- package/dist/runner/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +45 -42
- package/dist/runner/kimi/acp/runner.js +140 -293
- package/dist/runner/kimi/acp/translator.d.ts +18 -100
- package/dist/runner/kimi/acp/translator.js +56 -242
- package/dist/runner/opencode/acp/runner.d.ts +22 -46
- package/dist/runner/opencode/acp/runner.js +73 -256
- package/dist/runner/opencode/acp/translator.d.ts +28 -103
- package/dist/runner/opencode/acp/translator.js +51 -236
- package/dist/runner/pi/rpc/translator.d.ts +3 -0
- package/dist/runner/pi/rpc/translator.js +47 -9
- package/dist/runner/probe.d.ts +8 -11
- package/dist/runner/probe.js +17 -21
- package/dist/runner/registry.d.ts +4 -4
- package/dist/runner/registry.js +4 -4
- package/dist/runner/types.d.ts +38 -1
- package/dist/session/claude/sessions.js +35 -58
- package/dist/session/codex/rollout-reader.d.ts +10 -0
- package/dist/session/codex/rollout-reader.js +123 -41
- package/dist/session/common/constants.d.ts +7 -0
- package/dist/session/common/constants.js +9 -0
- package/dist/session/common/two-pass.d.ts +22 -0
- package/dist/session/common/two-pass.js +70 -0
- package/dist/session/dsh/sessions.js +7 -6
- package/dist/session/kimi/sessions.d.ts +6 -0
- package/dist/session/kimi/sessions.js +50 -73
- package/dist/session/opencode/sessions.js +26 -8
- package/dist/session/pi/sessions.js +54 -73
- package/dist/update/index.d.ts +1 -2
- package/dist/update/index.js +1 -2
- package/dist/update/install.d.ts +2 -1
- package/dist/update/install.js +58 -9
- package/dist/update/version-check.d.ts +5 -0
- package/dist/update/version-check.js +9 -0
- package/package.json +9 -6
- package/dist/update/startup-hint.d.ts +0 -5
- package/dist/update/startup-hint.js +0 -11
package/dist/common/fs.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { getLogger } from '../logger/index.js';
|
|
3
|
+
import { currentPlatform, isWin32 } from '../platform/select.js';
|
|
2
4
|
/**
|
|
3
5
|
* Unlink a file, silently ignoring ENOENT and other errors.
|
|
4
6
|
*
|
|
@@ -8,10 +10,62 @@ import fs from 'node:fs';
|
|
|
8
10
|
*/
|
|
9
11
|
export function silentlyUnlink(path) {
|
|
10
12
|
try {
|
|
11
|
-
fs.unlinkSync(path);
|
|
13
|
+
withBusyRetry(() => fs.unlinkSync(path));
|
|
12
14
|
}
|
|
13
|
-
catch {
|
|
14
|
-
|
|
15
|
+
catch (err) {
|
|
16
|
+
// 占用放弃(win32 重试耗尽 / posix 首次占用即失败):按设计「告警,
|
|
17
|
+
// 不静默吞」,但清理路径不向调用方抛错(保持本函数契约)
|
|
18
|
+
getLogger().warn(`[fs] silentlyUnlink gave up on busy file ${path}: ${String(err)}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* win32 句柄占用重试:rename/unlink 目标被
|
|
23
|
+
* 其他句柄持有时报 EPERM/EBUSY(共享冲突),短暂退避后重试通常即可成功。
|
|
24
|
+
* 仅 win32 启用重试:posix 上这两个错误码不是「占用瞬态」语义(如 NFS 权限
|
|
25
|
+
* 问题),立即失败——避免忙等阻塞单线程 bridge 的事件循环。
|
|
26
|
+
*/
|
|
27
|
+
const BUSY_CODES = new Set(['EPERM', 'EBUSY']);
|
|
28
|
+
/**
|
|
29
|
+
* 退避参数(取参照项目上线验证值):
|
|
30
|
+
* 最多 5 次,第 n 次失败后睡 BASE_RETRY_DELAY_MS × n——总阻塞上界 ~250ms。
|
|
31
|
+
* Windows 上杀毒/索引器持锁更久,窗口比短退避更稳。
|
|
32
|
+
*/
|
|
33
|
+
const BASE_RETRY_DELAY_MS = 25;
|
|
34
|
+
const MAX_RETRY_ATTEMPTS = 5;
|
|
35
|
+
export function isBusyError(err) {
|
|
36
|
+
return BUSY_CODES.has(err?.code ?? '');
|
|
37
|
+
}
|
|
38
|
+
function busySleep(ms) {
|
|
39
|
+
if (ms <= 0)
|
|
40
|
+
return;
|
|
41
|
+
const end = Date.now() + ms;
|
|
42
|
+
// 同步退避只能忙等(调用方均为 sync API);总上界 ~250ms
|
|
43
|
+
while (Date.now() < end) {
|
|
44
|
+
/* spin */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 对 sync 文件操作套占用退避重试:win32 上遇 EPERM/EBUSY 按退避序列重试,
|
|
49
|
+
* 其他错误或重试耗尽后抛最后一个错误;posix 上占用错误不重试、立即失败。
|
|
50
|
+
* `onGiveUp` 仅在「占用导致的放弃」时回调(告警/保留现场);非占用错误
|
|
51
|
+
* (如 EXDEV)不走回调、直接透传,避免误触发调用方的占用善后逻辑。
|
|
52
|
+
*/
|
|
53
|
+
export function withBusyRetry(op, opts) {
|
|
54
|
+
const win32 = isWin32(opts?.platform ?? currentPlatform);
|
|
55
|
+
for (let attempt = 1;; attempt++) {
|
|
56
|
+
try {
|
|
57
|
+
return op();
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (!isBusyError(err))
|
|
61
|
+
throw err;
|
|
62
|
+
if (!win32 || attempt >= MAX_RETRY_ATTEMPTS) {
|
|
63
|
+
// 真正放弃才回调:中间重试失败不能触发「保留现场」类善后
|
|
64
|
+
opts?.onGiveUp?.(err);
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
busySleep(BASE_RETRY_DELAY_MS * attempt);
|
|
68
|
+
}
|
|
15
69
|
}
|
|
16
70
|
}
|
|
17
71
|
//# sourceMappingURL=fs.js.map
|
|
@@ -176,6 +176,7 @@ export function getCodexCatalogModels(codexHome) {
|
|
|
176
176
|
maxBuffer: 4 * 1024 * 1024,
|
|
177
177
|
env: process.env,
|
|
178
178
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
179
|
+
windowsHide: true, // codex 是 npm .cmd 垫片:不隐藏会闪 cmd.exe 控制台
|
|
179
180
|
});
|
|
180
181
|
const models = parseCodexModelsOutput(stdout);
|
|
181
182
|
catalogCache = { key, models, ts: now, failed: models.length === 0 };
|
|
@@ -284,6 +285,7 @@ function execFileAsync(file, args) {
|
|
|
284
285
|
timeout: 8_000,
|
|
285
286
|
maxBuffer: 4 * 1024 * 1024,
|
|
286
287
|
env: process.env,
|
|
288
|
+
windowsHide: true,
|
|
287
289
|
}, (err, stdout) => {
|
|
288
290
|
if (err)
|
|
289
291
|
reject(err);
|
package/dist/config/index.d.ts
CHANGED
|
@@ -241,11 +241,6 @@ export declare const AppConfigSchema: z.ZodObject<{
|
|
|
241
241
|
idle: z.ZodDefault<z.ZodObject<{
|
|
242
242
|
watchdogMinutes: z.ZodDefault<z.ZodNumber>;
|
|
243
243
|
}, z.core.$strip>>;
|
|
244
|
-
output: z.ZodDefault<z.ZodObject<{
|
|
245
|
-
showThinking: z.ZodDefault<z.ZodBoolean>;
|
|
246
|
-
showToolUse: z.ZodDefault<z.ZodBoolean>;
|
|
247
|
-
showToolResult: z.ZodDefault<z.ZodBoolean>;
|
|
248
|
-
}, z.core.$strip>>;
|
|
249
244
|
logging: z.ZodDefault<z.ZodObject<{
|
|
250
245
|
level: z.ZodDefault<z.ZodEnum<{
|
|
251
246
|
debug: "debug";
|
package/dist/config/index.js
CHANGED
|
@@ -202,11 +202,6 @@ const IdleConfigSchema = z.object({
|
|
|
202
202
|
*/
|
|
203
203
|
watchdogMinutes: z.number().int().min(0).default(DEFAULTS.IDLE_WATCHDOG_MINUTES),
|
|
204
204
|
});
|
|
205
|
-
const OutputConfigSchema = z.object({
|
|
206
|
-
showThinking: z.boolean().default(true),
|
|
207
|
-
showToolUse: z.boolean().default(true),
|
|
208
|
-
showToolResult: z.boolean().default(true),
|
|
209
|
-
});
|
|
210
205
|
const LoggingConfigSchema = z.object({
|
|
211
206
|
level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
|
212
207
|
});
|
|
@@ -256,7 +251,6 @@ export const AppConfigSchema = z.object({
|
|
|
256
251
|
/** Agent-specific saved choices for quick switching. */
|
|
257
252
|
agentChoices: AgentChoicesSchema.optional(),
|
|
258
253
|
idle: IdleConfigSchema.default(IdleConfigSchema.parse({})),
|
|
259
|
-
output: OutputConfigSchema.default(OutputConfigSchema.parse({})),
|
|
260
254
|
logging: LoggingConfigSchema.default(LoggingConfigSchema.parse({})),
|
|
261
255
|
/** 入站媒体(图片/文件)落盘配置。 */
|
|
262
256
|
inboundMedia: InboundMediaConfigSchema.default(InboundMediaConfigSchema.parse({})),
|
|
@@ -280,11 +274,6 @@ claude:
|
|
|
280
274
|
idle:
|
|
281
275
|
watchdogMinutes: ${DEFAULTS.IDLE_WATCHDOG_MINUTES}
|
|
282
276
|
|
|
283
|
-
output:
|
|
284
|
-
showThinking: true
|
|
285
|
-
showToolUse: true
|
|
286
|
-
showToolResult: true
|
|
287
|
-
|
|
288
277
|
logging:
|
|
289
278
|
level: info
|
|
290
279
|
|
|
@@ -34,6 +34,7 @@ export function loadKimiConfig() {
|
|
|
34
34
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
35
35
|
timeout: 10000,
|
|
36
36
|
maxBuffer: 2 * 1024 * 1024,
|
|
37
|
+
windowsHide: true, // kimi 是 npm .cmd 垫片:不隐藏会闪 cmd.exe 控制台
|
|
37
38
|
});
|
|
38
39
|
const data = JSON.parse(output.toString());
|
|
39
40
|
if (!data.models || Object.keys(data.models).length === 0) {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 从 opencode models --verbose 命令获取 provider 和 model 信息
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { resolveExecutable } from '../platform/command.js';
|
|
7
|
+
import { spawnProcessSync } from '../platform/spawn.js';
|
|
7
8
|
import { getLogger } from '../logger/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* 内置的 fallback 列表
|
|
@@ -66,13 +67,23 @@ export function loadOpencodeConfig() {
|
|
|
66
67
|
return buildFallbackResult();
|
|
67
68
|
}
|
|
68
69
|
try {
|
|
70
|
+
// 经 seam 解析真实可执行文件(win32 PATHEXT),cross-spawn 同步执行
|
|
71
|
+
const resolved = resolveExecutable('opencode');
|
|
72
|
+
if (!resolved)
|
|
73
|
+
throw new Error('opencode not found');
|
|
69
74
|
// 执行 opencode models --verbose 获取所有模型信息
|
|
70
|
-
const
|
|
75
|
+
const res = spawnProcessSync(resolved.file, ['models', '--verbose'], {
|
|
71
76
|
encoding: 'utf-8',
|
|
72
77
|
// P1-8:30s → 10s,与 codex/kimi 对齐;maxBuffer 防模型清单 ENOBUFS 隐性截断
|
|
73
78
|
timeout: 10000,
|
|
74
79
|
maxBuffer: 64 * 1024 * 1024,
|
|
75
80
|
});
|
|
81
|
+
if (res.error)
|
|
82
|
+
throw res.error;
|
|
83
|
+
if (res.status !== 0) {
|
|
84
|
+
throw new Error(`opencode models exited code=${res.status}: ${String(res.stderr).slice(-200)}`);
|
|
85
|
+
}
|
|
86
|
+
const output = String(res.stdout);
|
|
76
87
|
// 按 provider 分组收集模型(只存 model ID,不存 provider/model 完整格式)
|
|
77
88
|
const providerModels = new Map();
|
|
78
89
|
// 解析输出:每个模型以 provider/model 格式开头,后面跟着 JSON 块
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type TerminalQrMode = 'full-block' | 'half-block';
|
|
2
|
+
export interface TerminalQr {
|
|
3
|
+
mode: TerminalQrMode;
|
|
4
|
+
/** Columns the rendered symbol occupies — compare against the terminal width. */
|
|
5
|
+
columns: number;
|
|
6
|
+
/** Symbol rows, one text row per line, no trailing newline. */
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Render a scannable symbol for the current terminal.
|
|
11
|
+
*
|
|
12
|
+
* Prefers the Windows-safe full-block rendering; when it is too wide for the
|
|
13
|
+
* terminal, POSIX terminals fall back to the compact half-block rendering
|
|
14
|
+
* (macOS/Linux terminals draw its glyphs correctly). Windows deliberately does
|
|
15
|
+
* not: half blocks are exactly what does not scan there, so the caller falls
|
|
16
|
+
* back to the image file instead. Returns `null` when nothing fits.
|
|
17
|
+
*/
|
|
18
|
+
export declare function renderTerminalQr(text: string, options?: {
|
|
19
|
+
columns?: number;
|
|
20
|
+
platform?: NodeJS.Platform;
|
|
21
|
+
}): TerminalQr | null;
|
|
22
|
+
/**
|
|
23
|
+
* Render the symbol as a GIF.
|
|
24
|
+
*
|
|
25
|
+
* The escape hatch for terminals where block rendering still does not scan
|
|
26
|
+
* (narrow window, missing glyphs): a real image file always scans.
|
|
27
|
+
*/
|
|
28
|
+
export declare function renderQrImage(text: string, cellSize?: number): Buffer;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the wizard should also write the QR image file.
|
|
31
|
+
*
|
|
32
|
+
* Windows always gets the file: its consoles are exactly where block glyphs
|
|
33
|
+
* render unreliably, so the image is the dependable path there. Elsewhere the
|
|
34
|
+
* file is only written when the terminal could not show a symbol at all.
|
|
35
|
+
*/
|
|
36
|
+
export declare function needsQrImage(terminalQr: TerminalQr | null, platform?: NodeJS.Platform): boolean;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import qrcode from 'qrcode-generator';
|
|
2
|
+
import { currentPlatform, isWin32 } from '../platform/select.js';
|
|
3
|
+
/**
|
|
4
|
+
* Terminal rendering for the first-run wizard's QR symbol.
|
|
5
|
+
*
|
|
6
|
+
* Everything is drawn from one module matrix. Two renderers exist because
|
|
7
|
+
* terminals disagree about block glyphs:
|
|
8
|
+
*
|
|
9
|
+
* - `full-block` draws each module as two `█` columns. It only relies on the
|
|
10
|
+
* full block glyph, which every console font has, and stays square in a
|
|
11
|
+
* typical 1:2 terminal cell. This is the Windows-safe rendering.
|
|
12
|
+
* - `half-block` packs two QR rows into one text row with `▀`/`▄`. It is half
|
|
13
|
+
* as tall, but needs a font that draws those glyphs as exact half-height
|
|
14
|
+
* blocks — Windows consoles (raster/CJK fonts, extra line spacing) often
|
|
15
|
+
* don't, which is why the symbol printed there often cannot be scanned.
|
|
16
|
+
*
|
|
17
|
+
* Both renderers emit the 4-module light quiet zone the QR spec requires;
|
|
18
|
+
* `qrcode-terminal`'s half-block output had none, and a missing quiet zone
|
|
19
|
+
* alone can make scanners fail.
|
|
20
|
+
*
|
|
21
|
+
* Polarity follows `qrcode-terminal`: glyphs are painted in the foreground
|
|
22
|
+
* colour and stand for the *light* modules, so the terminal's dark background
|
|
23
|
+
* supplies the dark modules (the common default theme).
|
|
24
|
+
*/
|
|
25
|
+
/** Light modules that must surround a symbol for scanners to lock on (QR spec). */
|
|
26
|
+
const QUIET_ZONE = 4;
|
|
27
|
+
const LIGHT_FULL = '██';
|
|
28
|
+
const DARK_FULL = ' ';
|
|
29
|
+
const LIGHT_HALF_BOTH = '█';
|
|
30
|
+
const LIGHT_HALF_TOP = '▀';
|
|
31
|
+
const LIGHT_HALF_BOTTOM = '▄';
|
|
32
|
+
const DARK_HALF_BOTH = ' ';
|
|
33
|
+
/** Used when stdout reports no width — non-TTY pipes, and consoles that report 0. */
|
|
34
|
+
const DEFAULT_COLUMNS = 80;
|
|
35
|
+
function buildSymbol(text) {
|
|
36
|
+
const symbol = qrcode(0, 'L');
|
|
37
|
+
symbol.addData(text);
|
|
38
|
+
symbol.make();
|
|
39
|
+
return symbol;
|
|
40
|
+
}
|
|
41
|
+
function renderFullBlock(symbol) {
|
|
42
|
+
const count = symbol.getModuleCount();
|
|
43
|
+
const lightRow = LIGHT_FULL.repeat(count + QUIET_ZONE * 2);
|
|
44
|
+
const pad = LIGHT_FULL.repeat(QUIET_ZONE);
|
|
45
|
+
const rows = new Array(count + QUIET_ZONE * 2).fill(lightRow);
|
|
46
|
+
for (let row = 0; row < count; row++) {
|
|
47
|
+
let line = pad;
|
|
48
|
+
for (let col = 0; col < count; col++) {
|
|
49
|
+
line += symbol.isDark(row, col) ? DARK_FULL : LIGHT_FULL;
|
|
50
|
+
}
|
|
51
|
+
rows[QUIET_ZONE + row] = line + pad;
|
|
52
|
+
}
|
|
53
|
+
return rows.join('\n');
|
|
54
|
+
}
|
|
55
|
+
function renderHalfBlock(symbol) {
|
|
56
|
+
const count = symbol.getModuleCount();
|
|
57
|
+
// Module counts are always odd (4 * version + 17), so the final row pairs
|
|
58
|
+
// with an implicit light row.
|
|
59
|
+
const textRows = Math.ceil(count / 2);
|
|
60
|
+
const quietRows = QUIET_ZONE / 2;
|
|
61
|
+
const lightRow = LIGHT_HALF_BOTH.repeat(count + QUIET_ZONE * 2);
|
|
62
|
+
const rows = new Array(textRows + quietRows * 2).fill(lightRow);
|
|
63
|
+
for (let pair = 0; pair < count; pair += 2) {
|
|
64
|
+
let line = LIGHT_HALF_BOTH.repeat(QUIET_ZONE);
|
|
65
|
+
for (let col = 0; col < count; col++) {
|
|
66
|
+
const top = symbol.isDark(pair, col);
|
|
67
|
+
const bottom = pair + 1 < count && symbol.isDark(pair + 1, col);
|
|
68
|
+
if (!top && !bottom)
|
|
69
|
+
line += LIGHT_HALF_BOTH;
|
|
70
|
+
else if (!top)
|
|
71
|
+
line += LIGHT_HALF_TOP;
|
|
72
|
+
else if (!bottom)
|
|
73
|
+
line += LIGHT_HALF_BOTTOM;
|
|
74
|
+
else
|
|
75
|
+
line += DARK_HALF_BOTH;
|
|
76
|
+
}
|
|
77
|
+
rows[quietRows + pair / 2] = line + LIGHT_HALF_BOTH.repeat(QUIET_ZONE);
|
|
78
|
+
}
|
|
79
|
+
return rows.join('\n');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Render a scannable symbol for the current terminal.
|
|
83
|
+
*
|
|
84
|
+
* Prefers the Windows-safe full-block rendering; when it is too wide for the
|
|
85
|
+
* terminal, POSIX terminals fall back to the compact half-block rendering
|
|
86
|
+
* (macOS/Linux terminals draw its glyphs correctly). Windows deliberately does
|
|
87
|
+
* not: half blocks are exactly what does not scan there, so the caller falls
|
|
88
|
+
* back to the image file instead. Returns `null` when nothing fits.
|
|
89
|
+
*/
|
|
90
|
+
export function renderTerminalQr(text, options = {}) {
|
|
91
|
+
const columns = options.columns && options.columns > 0 ? options.columns : DEFAULT_COLUMNS;
|
|
92
|
+
const platform = options.platform ?? currentPlatform;
|
|
93
|
+
const symbol = buildSymbol(text);
|
|
94
|
+
const width = symbol.getModuleCount() + QUIET_ZONE * 2;
|
|
95
|
+
if (width * 2 <= columns) {
|
|
96
|
+
return { mode: 'full-block', columns: width * 2, text: renderFullBlock(symbol) };
|
|
97
|
+
}
|
|
98
|
+
if (!isWin32(platform) && width <= columns) {
|
|
99
|
+
return { mode: 'half-block', columns: width, text: renderHalfBlock(symbol) };
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Render the symbol as a GIF.
|
|
105
|
+
*
|
|
106
|
+
* The escape hatch for terminals where block rendering still does not scan
|
|
107
|
+
* (narrow window, missing glyphs): a real image file always scans.
|
|
108
|
+
*/
|
|
109
|
+
export function renderQrImage(text, cellSize = 8) {
|
|
110
|
+
const symbol = buildSymbol(text);
|
|
111
|
+
const dataUrl = symbol.createDataURL(cellSize);
|
|
112
|
+
return Buffer.from(dataUrl.slice(dataUrl.indexOf(',') + 1), 'base64');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Whether the wizard should also write the QR image file.
|
|
116
|
+
*
|
|
117
|
+
* Windows always gets the file: its consoles are exactly where block glyphs
|
|
118
|
+
* render unreliably, so the image is the dependable path there. Elsewhere the
|
|
119
|
+
* file is only written when the terminal could not show a symbol at all.
|
|
120
|
+
*/
|
|
121
|
+
export function needsQrImage(terminalQr, platform = currentPlatform) {
|
|
122
|
+
return terminalQr === null || isWin32(platform);
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=qr.js.map
|
package/dist/config/wizard.js
CHANGED
|
@@ -1,41 +1,83 @@
|
|
|
1
1
|
import { registerApp } from '@larksuite/channel';
|
|
2
|
-
import qrcode from 'qrcode-terminal';
|
|
3
2
|
import fs from 'node:fs';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import YAML from 'yaml';
|
|
6
5
|
import { AppConfigSchema } from './index.js';
|
|
6
|
+
import { needsQrImage, renderQrImage, renderTerminalQr } from './qr.js';
|
|
7
|
+
import { silentlyUnlink } from '../common/fs.js';
|
|
8
|
+
/** Where the fallback QR image is written (next to the config it will seed). */
|
|
9
|
+
const QR_IMAGE_NAME = 'qr-code.gif';
|
|
10
|
+
/**
|
|
11
|
+
* Print the scan-to-create QR code.
|
|
12
|
+
*
|
|
13
|
+
* The terminal symbol uses full blocks on Windows (half blocks there often
|
|
14
|
+
* render wrong and cannot be scanned); the image file is offered whenever the
|
|
15
|
+
* terminal cannot show a trustworthy symbol — Windows, or a window too narrow
|
|
16
|
+
* for the symbol to fit without wrapping.
|
|
17
|
+
*
|
|
18
|
+
* Returns true when the fallback image was written, so the caller only cleans
|
|
19
|
+
* up a file that actually exists.
|
|
20
|
+
*/
|
|
21
|
+
function printRegistrationQr(info, imagePath) {
|
|
22
|
+
console.log('请用飞书 App 扫描以下二维码完成应用创建:\n');
|
|
23
|
+
const qr = renderTerminalQr(info.url, { columns: process.stdout.columns });
|
|
24
|
+
if (qr)
|
|
25
|
+
console.log(`${qr.text}\n`);
|
|
26
|
+
else
|
|
27
|
+
console.log('(当前终端太窄,二维码显示不下:可拉宽窗口重试,或扫下面的图片)\n');
|
|
28
|
+
let wroteImage = false;
|
|
29
|
+
if (needsQrImage(qr)) {
|
|
30
|
+
try {
|
|
31
|
+
fs.mkdirSync(path.dirname(imagePath), { recursive: true });
|
|
32
|
+
fs.writeFileSync(imagePath, renderQrImage(info.url));
|
|
33
|
+
wroteImage = true;
|
|
34
|
+
console.log(`二维码图片:${imagePath}(或用图片查看器打开后扫码)`);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.log(`二维码图片写入失败:${String(err)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const mins = Math.max(1, Math.round(info.expireIn / 60));
|
|
41
|
+
console.log(`\n二维码有效期:约 ${mins} 分钟`);
|
|
42
|
+
console.log(`也可以直接在浏览器打开:${info.url}\n`);
|
|
43
|
+
return wroteImage;
|
|
44
|
+
}
|
|
7
45
|
/**
|
|
8
46
|
* Run the QR-code registration wizard. The user scans the QR with their
|
|
9
47
|
* Feishu/Lark app to create a new application; the SDK polls until the
|
|
10
48
|
* creation completes and returns the credentials.
|
|
11
49
|
*/
|
|
12
|
-
async function runRegistrationWizard() {
|
|
50
|
+
async function runRegistrationWizard(configPath) {
|
|
13
51
|
console.log('\n[lark-remote] 未检测到飞书应用配置,进入扫码创建向导。\n');
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
const imagePath = path.join(path.dirname(configPath), QR_IMAGE_NAME);
|
|
53
|
+
let wroteImage = false;
|
|
54
|
+
try {
|
|
55
|
+
const result = await registerApp({
|
|
56
|
+
source: 'lark-remote',
|
|
57
|
+
onQRCodeReady: (info) => {
|
|
58
|
+
wroteImage = printRegistrationQr(info, imagePath);
|
|
59
|
+
},
|
|
60
|
+
onStatusChange: (info) => {
|
|
61
|
+
if (info.status === 'domain_switched') {
|
|
62
|
+
console.log('识别到国际版租户,已切换到 larksuite.com 域名。');
|
|
63
|
+
}
|
|
64
|
+
else if (info.status === 'slow_down') {
|
|
65
|
+
console.log('轮询速度过快,已自动降速。');
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
console.log('\n✓ 应用创建成功');
|
|
70
|
+
console.log(` App ID: ${result.client_id}`);
|
|
71
|
+
if (result.user_info?.tenant_brand) {
|
|
72
|
+
console.log(` Tenant: ${result.user_info.tenant_brand}`);
|
|
73
|
+
}
|
|
74
|
+
console.log('');
|
|
75
|
+
return { appId: result.client_id, appSecret: result.client_secret };
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
if (wroteImage)
|
|
79
|
+
silentlyUnlink(imagePath);
|
|
36
80
|
}
|
|
37
|
-
console.log('');
|
|
38
|
-
return { appId: result.client_id, appSecret: result.client_secret };
|
|
39
81
|
}
|
|
40
82
|
function isInteractiveTerminal() {
|
|
41
83
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -77,7 +119,7 @@ export async function ensureConfig(configPath) {
|
|
|
77
119
|
return;
|
|
78
120
|
if (!isInteractiveTerminal())
|
|
79
121
|
return;
|
|
80
|
-
const { appId, appSecret } = await runRegistrationWizard();
|
|
122
|
+
const { appId, appSecret } = await runRegistrationWizard(configPath);
|
|
81
123
|
writeConfigWithCredentials(configPath, appId, appSecret);
|
|
82
124
|
console.log(`[lark-remote] 凭证已写入 ${configPath},继续启动...\n`);
|
|
83
125
|
}
|
|
@@ -6,9 +6,10 @@ import type { AppConfig } from '../config/index.js';
|
|
|
6
6
|
* 必须远小于用户连击间隔,否则同一按钮的连续点击会被当成"重复事件"丢弃:
|
|
7
7
|
* - SDK 的 cardAction dedup eventId = `card:{messageId}:{operator.openId}:{actionId}`
|
|
8
8
|
* - actionId = `tag|name|option|JSON.stringify(value)`,不含时间戳/事件序号
|
|
9
|
-
* - config 卡片原地更新(updateCardInPlace
|
|
9
|
+
* - config 卡片原地更新(updateCardInPlace),用户在同一张卡上重复触发
|
|
10
|
+
* 固定 value 的按钮(如 config.toggle):
|
|
10
11
|
* messageId / operator / value 三段都相同 → eventId 完全相同 → 第二次点击被
|
|
11
|
-
* seenCache drop,toggle
|
|
12
|
+
* seenCache drop,toggle 不可逆(第二次点击静默失效)。
|
|
12
13
|
*
|
|
13
14
|
* SDK 默认 12h 太长;60s 仍会误伤连击。300ms 挡飞书瞬时重投递(<100ms 级),
|
|
14
15
|
* 放过用户连击(慢点两次通常 >500ms)。代价:削弱 message 秒级重投递防护——
|
package/dist/connector/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { sleep } from '../common/sleep.js';
|
|
|
6
6
|
import axios from 'axios';
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import { silentlyUnlink } from '../common/fs.js';
|
|
9
|
+
import { displayName } from '../platform/path.js';
|
|
9
10
|
import https from 'node:https';
|
|
10
11
|
import os from 'node:os';
|
|
11
12
|
import path from 'node:path';
|
|
@@ -29,9 +30,10 @@ const noKeepAliveAgent = new https.Agent({ keepAlive: false });
|
|
|
29
30
|
* 必须远小于用户连击间隔,否则同一按钮的连续点击会被当成"重复事件"丢弃:
|
|
30
31
|
* - SDK 的 cardAction dedup eventId = `card:{messageId}:{operator.openId}:{actionId}`
|
|
31
32
|
* - actionId = `tag|name|option|JSON.stringify(value)`,不含时间戳/事件序号
|
|
32
|
-
* - config 卡片原地更新(updateCardInPlace
|
|
33
|
+
* - config 卡片原地更新(updateCardInPlace),用户在同一张卡上重复触发
|
|
34
|
+
* 固定 value 的按钮(如 config.toggle):
|
|
33
35
|
* messageId / operator / value 三段都相同 → eventId 完全相同 → 第二次点击被
|
|
34
|
-
* seenCache drop,toggle
|
|
36
|
+
* seenCache drop,toggle 不可逆(第二次点击静默失效)。
|
|
35
37
|
*
|
|
36
38
|
* SDK 默认 12h 太长;60s 仍会误伤连击。300ms 挡飞书瞬时重投递(<100ms 级),
|
|
37
39
|
* 放过用户连击(慢点两次通常 >500ms)。代价:削弱 message 秒级重投递防护——
|
|
@@ -128,7 +130,6 @@ export class FeishuConnector {
|
|
|
128
130
|
// 返回时,lark SDK 正常 resolve、@larksuite/channel 的 patchCard 丢弃返回值,
|
|
129
131
|
// 导致终态卡 patch 被业务层拒绝时全链路无日志无兜底。这里只观测不改行为——
|
|
130
132
|
// 不 throw、不重试,返回值原样透传。
|
|
131
|
-
// 详见 .adversarial-tdd/prompt-patchcard-business-code-observability.md
|
|
132
133
|
// Guard: unit-test mocks may omit rawClient; skip probe installation in that case.
|
|
133
134
|
const messageService = tryGetPatchService(this.channel);
|
|
134
135
|
if (messageService?.patch) {
|
|
@@ -403,7 +404,7 @@ export class FeishuConnector {
|
|
|
403
404
|
if (stat.size > MAX_FILE_UPLOAD_SIZE) {
|
|
404
405
|
throw new Error(`File too large (${(stat.size / 1_000_000).toFixed(1)}MB), exceeds ${MAX_FILE_UPLOAD_SIZE / (1024 * 1024)}MB limit`);
|
|
405
406
|
}
|
|
406
|
-
const fileName = filePath
|
|
407
|
+
const fileName = displayName(filePath);
|
|
407
408
|
// P2-18: capture the read stream so it can be destroyed on any failure
|
|
408
409
|
// path. Without this, a token/upload/send failure leaks the fd (axios
|
|
409
410
|
// only closes the stream when it fully consumes it on success).
|
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 } from './router/index.js';
|
|
12
|
+
import { CommandRouter, isImmediateAction, DIRECT_RETURN_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 { Bridge } from './bridge/index.js';
|
|
@@ -117,13 +117,14 @@ function setupInstanceLockAndHandlers(instanceLock, logger, configDir) {
|
|
|
117
117
|
/** Initialize per-workspace runner factory + agent registry. */
|
|
118
118
|
function initializeRunner(config, configDir, cliArgs) {
|
|
119
119
|
const agentRegistry = new AgentRegistry();
|
|
120
|
+
// W2.10 单点化:runner factory 取最新 config 的唯一入口(P1-15 语义保持,
|
|
121
|
+
// container 未接线时回退启动快照)。
|
|
122
|
+
const latest = () => agentRegistry.getConfigContainer()?.current ?? config;
|
|
120
123
|
// P1-15: Claude factory reads from configContainer (not closure) so runtime
|
|
121
124
|
// config changes (model, effort, stopGraceMs) take effect after
|
|
122
125
|
// bridge.setConfig() + clearRunners(). Same pattern as codex/pi/kimi.
|
|
123
126
|
agentRegistry.register('claude', (ws) => {
|
|
124
|
-
const
|
|
125
|
-
const latestConfig = container?.current ?? config;
|
|
126
|
-
const claudeConfig = latestConfig.claude;
|
|
127
|
+
const claudeConfig = latest().claude;
|
|
127
128
|
return new ClaudeRunner({
|
|
128
129
|
model: claudeConfig.model,
|
|
129
130
|
effort: claudeConfig.effort,
|
|
@@ -141,9 +142,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
141
142
|
// + clearRunners().
|
|
142
143
|
const codexSessionReader = new CodexSessionReader({ codexHome: process.env.CODEX_HOME });
|
|
143
144
|
agentRegistry.register('codex', (_ws) => {
|
|
144
|
-
const
|
|
145
|
-
const latestConfig = container?.current ?? config;
|
|
146
|
-
const codexConfig = getAgentConfig(latestConfig, 'codex');
|
|
145
|
+
const codexConfig = getAgentConfig(latest(), 'codex');
|
|
147
146
|
return new CodexAppServerRunner({
|
|
148
147
|
kind: 'codex',
|
|
149
148
|
model: codexConfig?.model,
|
|
@@ -169,10 +168,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
169
168
|
// Using `opencode session list` and `opencode export` instead of HTTP API
|
|
170
169
|
const opencodeSessionReader = new OpencodeSessionReader();
|
|
171
170
|
agentRegistry.register('opencode', (_ws) => {
|
|
172
|
-
|
|
173
|
-
const container = agentRegistry.getConfigContainer();
|
|
174
|
-
const latestConfig = container?.current ?? config;
|
|
175
|
-
const ocConfig = getAgentConfig(latestConfig, 'opencode');
|
|
171
|
+
const ocConfig = getAgentConfig(latest(), 'opencode');
|
|
176
172
|
// Build model string with validation: provider/model format
|
|
177
173
|
let model;
|
|
178
174
|
if (ocConfig?.modelID) {
|
|
@@ -201,10 +197,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
201
197
|
agentRegistry.setConfigContainer(configContainer);
|
|
202
198
|
const piSessionReader = new PiSessionReader();
|
|
203
199
|
agentRegistry.register('pi', (ws) => {
|
|
204
|
-
|
|
205
|
-
const container = agentRegistry.getConfigContainer();
|
|
206
|
-
const latestConfig = container?.current ?? config;
|
|
207
|
-
const piConf = getAgentConfig(latestConfig, 'pi');
|
|
200
|
+
const piConf = getAgentConfig(latest(), 'pi');
|
|
208
201
|
return new PiRpcRunner({
|
|
209
202
|
provider: piConf?.provider ?? 'Volcano',
|
|
210
203
|
model: piConf?.model ?? 'glm-5.2',
|
|
@@ -221,10 +214,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
221
214
|
// Register KimiAcpRunner (pure ACP mode) and KimiSessionReader
|
|
222
215
|
const kimiSessionReader = new KimiSessionReader();
|
|
223
216
|
agentRegistry.register('kimi', (_ws) => {
|
|
224
|
-
|
|
225
|
-
const container = agentRegistry.getConfigContainer();
|
|
226
|
-
const latestConfig = container?.current ?? config;
|
|
227
|
-
const kimiConf = getAgentConfig(latestConfig, 'kimi');
|
|
217
|
+
const kimiConf = getAgentConfig(latest(), 'kimi');
|
|
228
218
|
const acpConf = kimiConf?.acp;
|
|
229
219
|
return new KimiAcpRunner({
|
|
230
220
|
kind: 'kimi',
|
|
@@ -244,18 +234,10 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
244
234
|
const dshSessionReader = new DshSessionReader({
|
|
245
235
|
// hostProvider 每次调用读最新 config:/config 修改 host 后 runner 重建为
|
|
246
236
|
// 新 host,reader(/resume、用量、完成卡)也跟随新 host(CC-02)。
|
|
247
|
-
hostProvider: () =>
|
|
248
|
-
const container = agentRegistry.getConfigContainer();
|
|
249
|
-
const latestConfig = container?.current ?? config;
|
|
250
|
-
return getAgentConfig(latestConfig, 'dsh')?.host;
|
|
251
|
-
},
|
|
237
|
+
hostProvider: () => getAgentConfig(latest(), 'dsh')?.host,
|
|
252
238
|
});
|
|
253
239
|
agentRegistry.register('dsh', (_ws) => {
|
|
254
|
-
|
|
255
|
-
// after bridge.setConfig() + clearRunners().
|
|
256
|
-
const container = agentRegistry.getConfigContainer();
|
|
257
|
-
const latestConfig = container?.current ?? config;
|
|
258
|
-
const dshConf = getAgentConfig(latestConfig, 'dsh');
|
|
240
|
+
const dshConf = getAgentConfig(latest(), 'dsh');
|
|
259
241
|
return new DshRunner({
|
|
260
242
|
kind: 'dsh',
|
|
261
243
|
sessionReader: dshSessionReader,
|
|
@@ -456,7 +438,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
456
438
|
const workspaces = workspaceStore.list();
|
|
457
439
|
if (workspaces.length > 0) {
|
|
458
440
|
// NOTE: fallback uses insertion order, not sort preference — by design
|
|
459
|
-
// (
|
|
441
|
+
// (cwd fallback stays insertion-order for now)
|
|
460
442
|
workspace = workspaces[0].path;
|
|
461
443
|
}
|
|
462
444
|
}
|
|
@@ -541,18 +523,9 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
541
523
|
// order.aliasInput / order.aliasRemove / order.textInput 也返回 toast+card,
|
|
542
524
|
// 需直返避免被吞(order.textInput 曾漏在列表外,编辑卡停留在编辑界面)。
|
|
543
525
|
// approval.planFeedback 同 answer 系列:附意见的 toast 需直返 + 不落串行队列。
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
actionValue.cmd === 'order.textInput' ||
|
|
548
|
-
actionValue.cmd === 'config.save' ||
|
|
549
|
-
actionValue.cmd === 'approval.respond' ||
|
|
550
|
-
actionValue.cmd === 'approval.toggle' ||
|
|
551
|
-
actionValue.cmd === 'approval.answer' ||
|
|
552
|
-
actionValue.cmd === 'approval.answerSubmit' ||
|
|
553
|
-
actionValue.cmd === 'approval.answerCustom' ||
|
|
554
|
-
actionValue.cmd === 'approval.answerNote' ||
|
|
555
|
-
actionValue.cmd === 'approval.planFeedback') {
|
|
526
|
+
// W2.1:直返名单以 router 的 DIRECT_RETURN_CMDS 为单一来源(历史注释:
|
|
527
|
+
// order.textInput 曾漏在列表外、answer 家族曾漏在直返列表外——两份拷贝漂移)。
|
|
528
|
+
if (DIRECT_RETURN_CMDS.has(actionValue.cmd)) {
|
|
556
529
|
return router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
557
530
|
}
|
|
558
531
|
// order.exec → equivalent queued message (Plan A): resolve the order text
|
package/dist/instance-lock.js
CHANGED
|
@@ -116,6 +116,13 @@ export class InstanceLock {
|
|
|
116
116
|
process.exit(129);
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
+
else {
|
|
120
|
+
// win32 无 SIGHUP;CTRL_BREAK 退出前也要释放锁(§3.5,对照 cli.ts 信号转发)
|
|
121
|
+
process.on('SIGBREAK', () => {
|
|
122
|
+
this.release();
|
|
123
|
+
process.exit(149);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
119
126
|
}
|
|
120
127
|
/**
|
|
121
128
|
* Lock file stores `pid\ncomm` where comm is the owning process's command
|