lark-remote 0.1.12 → 0.2.0
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 +37 -0
- package/README.en.md +2 -6
- package/README.md +3 -7
- package/dist/bridge/final-usage.d.ts +24 -0
- package/dist/bridge/final-usage.js +55 -0
- package/dist/bridge/inbound-media.d.ts +0 -3
- package/dist/bridge/inbound-media.js +0 -1
- package/dist/bridge/index.d.ts +7 -7
- package/dist/bridge/index.js +100 -133
- 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 +14 -5
- package/dist/card/card-shared.js +23 -34
- 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/common/truncate.d.ts +0 -6
- package/dist/common/truncate.js +1 -1
- package/dist/common/ttl-cache.d.ts +24 -0
- package/dist/common/ttl-cache.js +53 -0
- package/dist/config/codex-config.js +14 -11
- package/dist/config/index.d.ts +0 -8
- package/dist/config/index.js +0 -16
- package/dist/config/kimi-config.d.ts +1 -1
- package/dist/config/kimi-config.js +2 -1
- package/dist/config/opencode-config.d.ts +1 -1
- package/dist/config/opencode-config.js +16 -10
- 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 -3
- package/dist/index.js +14 -41
- 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 +73 -6
- package/dist/router/index.js +663 -590
- 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 -4
- package/dist/runner/claude/session.js +18 -9
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -1
- package/dist/runner/codex/app-server/runner.d.ts +1 -16
- package/dist/runner/codex/app-server/runner.js +2 -2
- 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 +70 -0
- package/dist/runner/common/acp/protocol-helpers.js +82 -0
- package/dist/runner/common/connection-based-runner.d.ts +0 -2
- package/dist/runner/common/connection-based-runner.js +1 -1
- 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/dsh/index.d.ts +0 -2
- package/dist/runner/dsh/index.js +0 -2
- package/dist/runner/dsh/runner.d.ts +0 -2
- package/dist/runner/dsh/runner.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +63 -53
- package/dist/runner/kimi/acp/runner.js +189 -334
- package/dist/runner/kimi/acp/translator.d.ts +18 -101
- package/dist/runner/kimi/acp/translator.js +61 -255
- package/dist/runner/opencode/acp/runner.d.ts +22 -46
- package/dist/runner/opencode/acp/runner.js +74 -297
- package/dist/runner/opencode/acp/translator.d.ts +28 -104
- package/dist/runner/opencode/acp/translator.js +54 -249
- package/dist/runner/pi/rpc/index.d.ts +0 -2
- package/dist/runner/pi/rpc/index.js +0 -2
- 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/resolve-agent-choices.js +6 -2
- package/dist/runner/sync-agent-choices.js +1 -1
- package/dist/runner/types.d.ts +38 -1
- package/dist/session/claude/session-index.js +3 -1
- package/dist/session/claude/sessions.js +37 -64
- package/dist/session/codex/rollout-reader.d.ts +10 -14
- package/dist/session/codex/rollout-reader.js +126 -48
- package/dist/session/common/constants.d.ts +7 -0
- package/dist/session/common/constants.js +9 -0
- package/dist/session/common/pagination.d.ts +0 -2
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/common/recency.d.ts +7 -0
- package/dist/session/common/recency.js +10 -0
- package/dist/session/common/two-pass.d.ts +22 -0
- package/dist/session/common/two-pass.js +70 -0
- package/dist/session/common/usage-accumulator.d.ts +14 -0
- package/dist/session/common/usage-accumulator.js +15 -0
- package/dist/session/dsh/sessions.js +15 -11
- package/dist/session/kimi/sessions.d.ts +24 -4
- package/dist/session/kimi/sessions.js +99 -103
- package/dist/session/opencode/sessions.d.ts +2 -14
- package/dist/session/opencode/sessions.js +35 -15
- package/dist/session/pi/sessions.js +64 -95
- package/dist/update/index.d.ts +2 -3
- 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 -7
- 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
|
|
@@ -16,12 +16,6 @@ export declare const DEFAULT_TRUNCATE_SUFFIX = "\u2026\uFF08\u5DF2\u622A\u65AD\u
|
|
|
16
16
|
* pathological tool_result never inflates the replay payload to megabytes.
|
|
17
17
|
*/
|
|
18
18
|
export declare const TOOL_RESULT_MAX_BYTES = 4000;
|
|
19
|
-
/**
|
|
20
|
-
* Fit a UTF-8 string into `maxBytes` by iterating Unicode codepoints, never
|
|
21
|
-
* splitting a multi-byte sequence. When `fromEnd` is true, keeps the TAIL
|
|
22
|
-
* (useful for logs/tails); otherwise keeps the HEAD.
|
|
23
|
-
*/
|
|
24
|
-
export declare function fitUtf8(value: string, maxBytes: number, fromEnd?: boolean): string;
|
|
25
19
|
/**
|
|
26
20
|
* Truncate a UTF-8 string to fit within `maxBytes`, appending `suffix`
|
|
27
21
|
* (default `…(已截断)`) when truncation occurs. When `fromEnd` is true, keeps
|
package/dist/common/truncate.js
CHANGED
|
@@ -21,7 +21,7 @@ export const TOOL_RESULT_MAX_BYTES = 4000;
|
|
|
21
21
|
* splitting a multi-byte sequence. When `fromEnd` is true, keeps the TAIL
|
|
22
22
|
* (useful for logs/tails); otherwise keeps the HEAD.
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
function fitUtf8(value, maxBytes, fromEnd = false) {
|
|
25
25
|
if (fromEnd) {
|
|
26
26
|
const codepoints = Array.from(value);
|
|
27
27
|
let bytes = 0;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic TTL + bounded LRU cache.
|
|
3
|
+
*
|
|
4
|
+
* Consolidates the per-reader memory caches that previously hand-rolled the
|
|
5
|
+
* same "mtime/TTL + entry cap" Map pattern (kimi/pi/opencode session readers).
|
|
6
|
+
* Semantics follow the kimi implementation:
|
|
7
|
+
* - TTL is measured from the cache-write time (P1-17 defect 2), not key mtime.
|
|
8
|
+
* Note: ttlMs = 0 effectively disables caching — `Date.now() - at >= 0` is
|
|
9
|
+
* always true, so every entry expires immediately.
|
|
10
|
+
* - A hit refreshes recency (LRU); eviction removes the least recently
|
|
11
|
+
* inserted/used entry past `maxEntries` (P1-17 defect 1: unbounded growth).
|
|
12
|
+
* The old pi implementation did NOT re-insert on hit (FIFO eviction); the
|
|
13
|
+
* eviction order for pi therefore changes from FIFO to LRU here.
|
|
14
|
+
*/
|
|
15
|
+
export declare class TtlCache<K, V> {
|
|
16
|
+
private readonly ttlMs;
|
|
17
|
+
private readonly maxEntries?;
|
|
18
|
+
private map;
|
|
19
|
+
constructor(ttlMs: number, maxEntries?: number | undefined);
|
|
20
|
+
/** Return the fresh value for `key` (refreshing LRU order), or undefined. */
|
|
21
|
+
get(key: K): V | undefined;
|
|
22
|
+
set(key: K, value: V): void;
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic TTL + bounded LRU cache.
|
|
3
|
+
*
|
|
4
|
+
* Consolidates the per-reader memory caches that previously hand-rolled the
|
|
5
|
+
* same "mtime/TTL + entry cap" Map pattern (kimi/pi/opencode session readers).
|
|
6
|
+
* Semantics follow the kimi implementation:
|
|
7
|
+
* - TTL is measured from the cache-write time (P1-17 defect 2), not key mtime.
|
|
8
|
+
* Note: ttlMs = 0 effectively disables caching — `Date.now() - at >= 0` is
|
|
9
|
+
* always true, so every entry expires immediately.
|
|
10
|
+
* - A hit refreshes recency (LRU); eviction removes the least recently
|
|
11
|
+
* inserted/used entry past `maxEntries` (P1-17 defect 1: unbounded growth).
|
|
12
|
+
* The old pi implementation did NOT re-insert on hit (FIFO eviction); the
|
|
13
|
+
* eviction order for pi therefore changes from FIFO to LRU here.
|
|
14
|
+
*/
|
|
15
|
+
export class TtlCache {
|
|
16
|
+
ttlMs;
|
|
17
|
+
maxEntries;
|
|
18
|
+
map = new Map();
|
|
19
|
+
constructor(ttlMs, maxEntries) {
|
|
20
|
+
this.ttlMs = ttlMs;
|
|
21
|
+
this.maxEntries = maxEntries;
|
|
22
|
+
}
|
|
23
|
+
/** Return the fresh value for `key` (refreshing LRU order), or undefined. */
|
|
24
|
+
get(key) {
|
|
25
|
+
const entry = this.map.get(key);
|
|
26
|
+
if (!entry)
|
|
27
|
+
return undefined;
|
|
28
|
+
if (Date.now() - entry.at >= this.ttlMs) {
|
|
29
|
+
this.map.delete(key);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
// LRU refresh: re-insert to move to the tail of iteration order.
|
|
33
|
+
this.map.delete(key);
|
|
34
|
+
this.map.set(key, entry);
|
|
35
|
+
return entry.value;
|
|
36
|
+
}
|
|
37
|
+
set(key, value) {
|
|
38
|
+
this.map.delete(key);
|
|
39
|
+
this.map.set(key, { at: Date.now(), value });
|
|
40
|
+
if (this.maxEntries !== undefined) {
|
|
41
|
+
while (this.map.size > this.maxEntries) {
|
|
42
|
+
const oldest = this.map.keys().next().value;
|
|
43
|
+
if (oldest === undefined)
|
|
44
|
+
break;
|
|
45
|
+
this.map.delete(oldest);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
clear() {
|
|
50
|
+
this.map.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=ttl-cache.js.map
|
|
@@ -31,16 +31,13 @@ export function resolveCodexHome(codexHome) {
|
|
|
31
31
|
* 配置 [model_providers.anthropic] 才存在,fallback 阶段不得虚构。
|
|
32
32
|
*/
|
|
33
33
|
const FALLBACK_PROVIDERS = ['openai'];
|
|
34
|
-
const FALLBACK_MODELS = [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'claude-sonnet-4-20250514',
|
|
42
|
-
'claude-opus-4-20250115',
|
|
43
|
-
];
|
|
34
|
+
const FALLBACK_MODELS = ['o3', 'o4-mini', 'gpt-4o', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano'];
|
|
35
|
+
/**
|
|
36
|
+
* anthropic 的独立兜底模型表:anthropic 非内置 provider,仅在用户显式配置后使用。
|
|
37
|
+
* 不得混入 FALLBACK_MODELS——无 config.toml 时 openai 的模型列表会原样暴露这些
|
|
38
|
+
* claude 条目(claude 模型列在 openai provider 下是错的)。
|
|
39
|
+
*/
|
|
40
|
+
const ANTHROPIC_FALLBACK_MODELS = ['claude-sonnet-4-20250514', 'claude-opus-4-20250115'];
|
|
44
41
|
/** Bundled models from codex binary, 1h TTL is enough (only change on binary upgrade) */
|
|
45
42
|
const BUNDLED_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
46
43
|
/** 失败/空结果负缓存 TTL:避免每次卡片构建同步阻塞最长 8s(P3-5) */
|
|
@@ -179,6 +176,7 @@ export function getCodexCatalogModels(codexHome) {
|
|
|
179
176
|
maxBuffer: 4 * 1024 * 1024,
|
|
180
177
|
env: process.env,
|
|
181
178
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
179
|
+
windowsHide: true, // codex 是 npm .cmd 垫片:不隐藏会闪 cmd.exe 控制台
|
|
182
180
|
});
|
|
183
181
|
const models = parseCodexModelsOutput(stdout);
|
|
184
182
|
catalogCache = { key, models, ts: now, failed: models.length === 0 };
|
|
@@ -287,6 +285,7 @@ function execFileAsync(file, args) {
|
|
|
287
285
|
timeout: 8_000,
|
|
288
286
|
maxBuffer: 4 * 1024 * 1024,
|
|
289
287
|
env: process.env,
|
|
288
|
+
windowsHide: true,
|
|
290
289
|
}, (err, stdout) => {
|
|
291
290
|
if (err)
|
|
292
291
|
reject(err);
|
|
@@ -351,7 +350,11 @@ export function loadCodexConfig(opts = {}) {
|
|
|
351
350
|
// 否则 = bundled(与原行为一致)
|
|
352
351
|
const catalogSlugs = getCodexCatalogModels(codexHome).map((m) => m.slug);
|
|
353
352
|
const allModels = catalogSlugs.length > 0 ? catalogSlugs : FALLBACK_MODELS;
|
|
354
|
-
|
|
353
|
+
// anthropic 模型列表仅在用户显式配置 anthropic 时使用:catalog 可用取 catalog 中的
|
|
354
|
+
// claude-*,否则用 ANTHROPIC_FALLBACK_MODELS(与 openai 兜底表隔离)
|
|
355
|
+
const anthropicModels = catalogSlugs.length > 0
|
|
356
|
+
? catalogSlugs.filter((m) => m.startsWith('claude-'))
|
|
357
|
+
: ANTHROPIC_FALLBACK_MODELS;
|
|
355
358
|
if (!fs.existsSync(configFile)) {
|
|
356
359
|
// 无 config.toml:默认模型取目录首个可用模型(codex default_model_from_available),
|
|
357
360
|
// 目录不可用时回退 'o3'(FALLBACK_MODELS 首项,非 catalog 默认)
|
package/dist/config/index.d.ts
CHANGED
|
@@ -237,18 +237,10 @@ export declare const AppConfigSchema: z.ZodObject<{
|
|
|
237
237
|
model: z.ZodOptional<z.ZodString>;
|
|
238
238
|
thinkingEffort: z.ZodOptional<z.ZodString>;
|
|
239
239
|
}, z.core.$strip>>;
|
|
240
|
-
dsh: z.ZodOptional<z.ZodObject<{
|
|
241
|
-
host: z.ZodOptional<z.ZodString>;
|
|
242
|
-
}, z.core.$strip>>;
|
|
243
240
|
}, z.core.$strip>>;
|
|
244
241
|
idle: z.ZodDefault<z.ZodObject<{
|
|
245
242
|
watchdogMinutes: z.ZodDefault<z.ZodNumber>;
|
|
246
243
|
}, z.core.$strip>>;
|
|
247
|
-
output: z.ZodDefault<z.ZodObject<{
|
|
248
|
-
showThinking: z.ZodDefault<z.ZodBoolean>;
|
|
249
|
-
showToolUse: z.ZodDefault<z.ZodBoolean>;
|
|
250
|
-
showToolResult: z.ZodDefault<z.ZodBoolean>;
|
|
251
|
-
}, z.core.$strip>>;
|
|
252
244
|
logging: z.ZodDefault<z.ZodObject<{
|
|
253
245
|
level: z.ZodDefault<z.ZodEnum<{
|
|
254
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
|
});
|
|
@@ -246,11 +241,6 @@ const AgentChoicesSchema = z.object({
|
|
|
246
241
|
thinkingEffort: z.string().optional(),
|
|
247
242
|
})
|
|
248
243
|
.optional(),
|
|
249
|
-
dsh: z
|
|
250
|
-
.object({
|
|
251
|
-
host: z.string().optional(),
|
|
252
|
-
})
|
|
253
|
-
.optional(),
|
|
254
244
|
});
|
|
255
245
|
export const AppConfigSchema = z.object({
|
|
256
246
|
feishu: FeishuConfigSchema,
|
|
@@ -261,7 +251,6 @@ export const AppConfigSchema = z.object({
|
|
|
261
251
|
/** Agent-specific saved choices for quick switching. */
|
|
262
252
|
agentChoices: AgentChoicesSchema.optional(),
|
|
263
253
|
idle: IdleConfigSchema.default(IdleConfigSchema.parse({})),
|
|
264
|
-
output: OutputConfigSchema.default(OutputConfigSchema.parse({})),
|
|
265
254
|
logging: LoggingConfigSchema.default(LoggingConfigSchema.parse({})),
|
|
266
255
|
/** 入站媒体(图片/文件)落盘配置。 */
|
|
267
256
|
inboundMedia: InboundMediaConfigSchema.default(InboundMediaConfigSchema.parse({})),
|
|
@@ -285,11 +274,6 @@ claude:
|
|
|
285
274
|
idle:
|
|
286
275
|
watchdogMinutes: ${DEFAULTS.IDLE_WATCHDOG_MINUTES}
|
|
287
276
|
|
|
288
|
-
output:
|
|
289
|
-
showThinking: true
|
|
290
|
-
showToolUse: true
|
|
291
|
-
showToolResult: true
|
|
292
|
-
|
|
293
277
|
logging:
|
|
294
278
|
level: info
|
|
295
279
|
|
|
@@ -22,7 +22,7 @@ interface KimiConfigResult {
|
|
|
22
22
|
/** Model -> default effort mapping */
|
|
23
23
|
modelDefaultEfforts: Record<string, string>;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
25
|
+
/** 清空缓存(仅供测试使用,生产代码无调用点) */
|
|
26
26
|
export declare function invalidateKimiConfigCache(): void;
|
|
27
27
|
/**
|
|
28
28
|
* Load Kimi models from `kimi provider list --json`.
|
|
@@ -10,7 +10,7 @@ import { getLogger } from '../logger/index.js';
|
|
|
10
10
|
export const KIMI_THINKING_EFFORTS = ['low', 'high', 'max'];
|
|
11
11
|
/** Fallback efforts when all values are filtered out */
|
|
12
12
|
export const FALLBACK_EFFORTS = ['low', 'high', 'max'];
|
|
13
|
-
/**
|
|
13
|
+
/** 清空缓存(仅供测试使用,生产代码无调用点) */
|
|
14
14
|
export function invalidateKimiConfigCache() {
|
|
15
15
|
kimiConfigCache = null;
|
|
16
16
|
}
|
|
@@ -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) {
|
|
@@ -9,7 +9,7 @@ interface OpencodeConfigResult {
|
|
|
9
9
|
/** 根据 provider 获取可用模型列表(返回纯 model ID,不带 provider 前缀) */
|
|
10
10
|
modelOptions: (provider?: string) => string[];
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
12
|
+
/** 清空缓存(仅供测试使用,生产代码无调用点) */
|
|
13
13
|
export declare function invalidateOpencodeConfigCache(): void;
|
|
14
14
|
/**
|
|
15
15
|
* 加载 opencode 配置,执行 opencode models --verbose 并解析
|
|
@@ -3,25 +3,21 @@
|
|
|
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 列表
|
|
10
11
|
* 当 opencode 命令失败时使用
|
|
11
12
|
* 格式:provider/model(内部用,modelOptions 返回时会去掉 provider 前缀)
|
|
12
13
|
*/
|
|
13
|
-
const FALLBACK_PROVIDERS = [
|
|
14
|
-
'opencode',
|
|
15
|
-
'deepseek',
|
|
16
|
-
'minimax-cn-coding-plan',
|
|
17
|
-
'myprovider',
|
|
18
|
-
'volcengine-plan',
|
|
19
|
-
];
|
|
20
14
|
const FALLBACK_MODELS = {
|
|
21
15
|
opencode: ['big-pickle'],
|
|
22
16
|
deepseek: ['deepseek-chat'],
|
|
23
17
|
'minimax-cn-coding-plan': ['MiniMax-M2.5'],
|
|
24
18
|
};
|
|
19
|
+
/** fallback provider 列表从模型表派生,保证不会出现空模型列表的 provider(历史曾混入无模型的 myprovider/volcengine-plan) */
|
|
20
|
+
const FALLBACK_PROVIDERS = Object.keys(FALLBACK_MODELS);
|
|
25
21
|
/** 正缓存 TTL:opencode models 列表短时间不变,1 分钟(与 kimi-config 对齐) */
|
|
26
22
|
const OPENCODE_CONFIG_CACHE_TTL_MS = 60_000;
|
|
27
23
|
/**
|
|
@@ -31,7 +27,7 @@ const OPENCODE_CONFIG_CACHE_TTL_MS = 60_000;
|
|
|
31
27
|
const OPENCODE_NEGATIVE_CACHE_TTL_MS = 30_000;
|
|
32
28
|
let opencodeConfigCache = null;
|
|
33
29
|
let opencodeConfigFailedAt = null;
|
|
34
|
-
/**
|
|
30
|
+
/** 清空缓存(仅供测试使用,生产代码无调用点) */
|
|
35
31
|
export function invalidateOpencodeConfigCache() {
|
|
36
32
|
opencodeConfigCache = null;
|
|
37
33
|
opencodeConfigFailedAt = null;
|
|
@@ -71,13 +67,23 @@ export function loadOpencodeConfig() {
|
|
|
71
67
|
return buildFallbackResult();
|
|
72
68
|
}
|
|
73
69
|
try {
|
|
70
|
+
// 经 seam 解析真实可执行文件(win32 PATHEXT),cross-spawn 同步执行
|
|
71
|
+
const resolved = resolveExecutable('opencode');
|
|
72
|
+
if (!resolved)
|
|
73
|
+
throw new Error('opencode not found');
|
|
74
74
|
// 执行 opencode models --verbose 获取所有模型信息
|
|
75
|
-
const
|
|
75
|
+
const res = spawnProcessSync(resolved.file, ['models', '--verbose'], {
|
|
76
76
|
encoding: 'utf-8',
|
|
77
77
|
// P1-8:30s → 10s,与 codex/kimi 对齐;maxBuffer 防模型清单 ENOBUFS 隐性截断
|
|
78
78
|
timeout: 10000,
|
|
79
79
|
maxBuffer: 64 * 1024 * 1024,
|
|
80
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);
|
|
81
87
|
// 按 provider 分组收集模型(只存 model ID,不存 provider/model 完整格式)
|
|
82
88
|
const providerModels = new Map();
|
|
83
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
|