lark-remote 0.4.4 → 0.5.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 +28 -0
- package/dist/binder.d.ts +11 -10
- package/dist/binder.js +17 -16
- package/dist/bridge/approval-coordinator.d.ts +1 -1
- package/dist/bridge/approval-coordinator.js +3 -3
- package/dist/bridge/inbound-media.d.ts +1 -27
- package/dist/bridge/inbound-media.js +23 -21
- package/dist/bridge/index.d.ts +57 -57
- package/dist/bridge/index.js +136 -129
- package/dist/bridge/queue-manager.d.ts +45 -38
- package/dist/bridge/queue-manager.js +136 -136
- package/dist/card/approval-render.d.ts +1 -1
- package/dist/card/approval-render.js +6 -6
- package/dist/card/bash-card-session.d.ts +3 -3
- package/dist/card/bash-card-session.js +3 -3
- package/dist/card/bash-renderer.js +5 -5
- package/dist/card/card-session.d.ts +4 -4
- package/dist/card/card-session.js +6 -6
- package/dist/card/card-shared.d.ts +1 -1
- package/dist/card/card-shared.js +2 -2
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.d.ts +1 -1
- package/dist/card/run-renderer.js +22 -22
- package/dist/card/run-state.d.ts +2 -2
- package/dist/card/run-state.js +3 -3
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +6 -6
- package/dist/cli.js +5 -6
- package/dist/clone.d.ts +6 -9
- package/dist/clone.js +12 -8
- 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/common/truncate.d.ts +1 -1
- package/dist/common/truncate.js +2 -2
- package/dist/common/ttl-cache.d.ts +2 -2
- package/dist/common/ttl-cache.js +2 -2
- package/dist/config/codex-config.d.ts +9 -9
- package/dist/config/codex-config.js +18 -18
- package/dist/config/dir.d.ts +4 -5
- package/dist/config/dir.js +12 -10
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +6 -6
- package/dist/config/opencode-config.js +3 -3
- package/dist/config/wizard.js +2 -2
- package/dist/connector/index.d.ts +5 -7
- package/dist/connector/index.js +29 -16
- package/dist/inbound/event.d.ts +1 -1
- package/dist/inbound/event.js +1 -1
- package/dist/inbound/placeholder.d.ts +2 -2
- package/dist/inbound/placeholder.js +2 -2
- 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 +38 -30
- package/dist/instance-lock.js +8 -7
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.js +4 -4
- package/dist/persistence/atomic-write.d.ts +1 -1
- package/dist/persistence/atomic-write.js +9 -5
- package/dist/platform/agent-stopper.d.ts +2 -2
- package/dist/platform/identity.d.ts +7 -1
- package/dist/platform/identity.js +2 -2
- package/dist/platform/path.d.ts +6 -0
- package/dist/platform/path.js +14 -8
- package/dist/platform/select.d.ts +1 -1
- package/dist/platform/select.js +1 -1
- package/dist/platform/shell.d.ts +2 -3
- package/dist/platform/spawn.d.ts +2 -2
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/terminator-posix.js +6 -6
- package/dist/platform/terminator-win32.js +3 -3
- package/dist/platform/terminator.d.ts +3 -3
- package/dist/platform/terminator.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/restart.d.ts +1 -1
- package/dist/restart.js +5 -3
- package/dist/router/card-helpers.d.ts +1 -3
- package/dist/router/card-helpers.js +2 -2
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +3 -3
- 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 +2 -2
- package/dist/router/config/pi.js +1 -1
- package/dist/router/index.d.ts +30 -30
- package/dist/router/index.js +103 -84
- package/dist/router/order-exec-dispatch.d.ts +3 -10
- package/dist/router/order-exec-dispatch.js +5 -5
- 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/bash/runner.d.ts +2 -2
- package/dist/runner/bash/runner.js +6 -6
- package/dist/runner/claude/runner.d.ts +1 -1
- package/dist/runner/claude/runner.js +2 -3
- package/dist/runner/claude/session.d.ts +4 -6
- package/dist/runner/claude/session.js +21 -23
- package/dist/runner/codex/app-server/runner.js +3 -3
- package/dist/runner/codex/app-server/translator.d.ts +2 -2
- package/dist/runner/codex/app-server/translator.js +5 -5
- package/dist/runner/common/acp/{protocol-helpers.d.ts → acp-approval.d.ts} +4 -10
- package/dist/runner/common/acp/{protocol-helpers.js → acp-approval.js} +7 -6
- package/dist/runner/common/acp/base-acp-runner.d.ts +1 -1
- package/dist/runner/common/acp/base-acp-runner.js +2 -2
- package/dist/runner/common/acp/base-translator.d.ts +3 -3
- package/dist/runner/common/acp/base-translator.js +4 -4
- package/dist/runner/common/acp/protocol-types.js +1 -1
- package/dist/runner/common/connection-based-runner.d.ts +5 -5
- package/dist/runner/common/connection-based-runner.js +5 -5
- package/dist/runner/common/jsonl-stream.d.ts +2 -2
- package/dist/runner/common/jsonl-stream.js +12 -12
- package/dist/runner/common/jsonrpc/client.d.ts +1 -1
- package/dist/runner/common/jsonrpc/client.js +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +13 -13
- package/dist/runner/common/jsonrpc/connection-manager.js +37 -37
- package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
- package/dist/runner/common/jsonrpc/transport.js +2 -2
- 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 +23 -10
- package/dist/runner/common/spawning-runner.js +48 -22
- 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 +3 -3
- package/dist/runner/kimi/acp/runner.d.ts +4 -4
- package/dist/runner/kimi/acp/runner.js +10 -10
- package/dist/runner/kimi/acp/translator.d.ts +2 -2
- package/dist/runner/kimi/acp/translator.js +5 -5
- package/dist/runner/opencode/acp/runner.d.ts +4 -4
- package/dist/runner/opencode/acp/runner.js +4 -4
- package/dist/runner/opencode/acp/translator.js +2 -2
- package/dist/runner/pi/rpc/runner.d.ts +1 -1
- package/dist/runner/pi/rpc/runner.js +2 -2
- package/dist/runner/pi/rpc/translator.d.ts +1 -2
- package/dist/runner/pi/rpc/translator.js +4 -6
- package/dist/runner/registry.d.ts +3 -3
- package/dist/runner/registry.js +3 -3
- package/dist/runner/types.d.ts +1 -1
- package/dist/session/claude/session-index.d.ts +13 -1
- package/dist/session/claude/session-index.js +21 -6
- package/dist/session/claude/session-reader.d.ts +1 -1
- package/dist/session/claude/session-reader.js +1 -1
- package/dist/session/claude/sessions.js +10 -10
- package/dist/session/codex/rollout-reader.d.ts +4 -4
- package/dist/session/codex/rollout-reader.js +14 -14
- package/dist/session/common/two-pass.js +2 -2
- package/dist/session/common/usage-accumulator.d.ts +2 -2
- package/dist/session/common/usage-accumulator.js +2 -2
- package/dist/session/dsh/sessions.d.ts +1 -1
- package/dist/session/dsh/sessions.js +3 -3
- package/dist/session/kimi/sessions.d.ts +2 -6
- package/dist/session/kimi/sessions.js +13 -19
- package/dist/session/opencode/sessions.d.ts +3 -3
- package/dist/session/opencode/sessions.js +15 -13
- package/dist/session/pi/sessions.js +13 -12
- package/dist/session/registry.d.ts +1 -1
- package/dist/session/registry.js +1 -1
- package/dist/session/session-store.d.ts +3 -4
- package/dist/session/session-store.js +3 -5
- package/dist/update/index.d.ts +1 -1
- package/dist/update/index.js +1 -1
- package/dist/update/version-check.d.ts +5 -3
- package/dist/update/version-check.js +10 -8
- package/dist/workspace/index.d.ts +4 -4
- package/dist/workspace/index.js +5 -5
- package/package.json +1 -1
package/dist/clone.d.ts
CHANGED
|
@@ -25,7 +25,8 @@ import { type QRCodeInfo, type RegisterAppResult } from '@larksuite/channel';
|
|
|
25
25
|
type CloneState = 'idle' | 'awaiting_scan' | 'finalizing';
|
|
26
26
|
/**
|
|
27
27
|
* 校验用户指定的分身名字:路径分隔符、以 . 开头、尾点/尾空格(win32 会静默
|
|
28
|
-
* 吞掉)、win32
|
|
28
|
+
* 吞掉)、win32 非法字符(`<>:"|?*`,NTFS/Win32 API 拒绝)、win32 保留设备名
|
|
29
|
+
* 一律拒绝。
|
|
29
30
|
*/
|
|
30
31
|
export declare function isValidCloneName(name: string): boolean;
|
|
31
32
|
/** 随机后缀:2 位数字 + 2 位小写字母/数字,如 `37ab`。 */
|
|
@@ -59,11 +60,6 @@ type RegisterAppFn = (options: {
|
|
|
59
60
|
status: string;
|
|
60
61
|
}) => void;
|
|
61
62
|
}) => 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
63
|
export interface CloneSessionOptions {
|
|
68
64
|
connector: CloneConnector;
|
|
69
65
|
/** 当前实例的 config.yaml 路径(配置复制源)。 */
|
|
@@ -111,7 +107,7 @@ export declare class CloneSession {
|
|
|
111
107
|
/**
|
|
112
108
|
* 扫码成功后的自动绑定落地:
|
|
113
109
|
* 1. 复制当前 config.yaml,仅替换 feishu.appId/appSecret;
|
|
114
|
-
* 2. 同步
|
|
110
|
+
* 2. 同步 cwd / 常用指令 / 会话状态(session 按 openId 重绑);
|
|
115
111
|
* 3. 用 registerApp 返回的新应用 openId 写 startup-contact.json(免认领绑定);
|
|
116
112
|
* 4. detached 拉起指向新目录的新实例(启动后自动向新应用发启动通知);
|
|
117
113
|
* 5. 在原应用回执「绑定完成 + 配置目录」,退出流程。
|
|
@@ -131,8 +127,9 @@ export declare class CloneSession {
|
|
|
131
127
|
private readOldOwnerId;
|
|
132
128
|
/**
|
|
133
129
|
* 写配置失败时清理半成品目录。守卫:只删内容全部是本次产物
|
|
134
|
-
* (config.yaml / startup-contact.json 及其
|
|
135
|
-
* 文件时保留现场不动。
|
|
130
|
+
* (config.yaml / startup-contact.json 及其 tmp)的目录,混入其他
|
|
131
|
+
* 文件时保留现场不动。tmp 形态两种都认:atomic-write 现行命名
|
|
132
|
+
* (.{name}.tmp-{pid}-{ts}-{rand})与旧版固定 `.tmp`(升级瞬间崩溃的残留)。
|
|
136
133
|
*/
|
|
137
134
|
private cleanupPartialDir;
|
|
138
135
|
}
|
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
|
/** 退出命令(不区分大小写)。 */
|
|
@@ -24,13 +24,16 @@ const WIN_RESERVED_NAMES = new Set([
|
|
|
24
24
|
]);
|
|
25
25
|
/**
|
|
26
26
|
* 校验用户指定的分身名字:路径分隔符、以 . 开头、尾点/尾空格(win32 会静默
|
|
27
|
-
* 吞掉)、win32
|
|
27
|
+
* 吞掉)、win32 非法字符(`<>:"|?*`,NTFS/Win32 API 拒绝)、win32 保留设备名
|
|
28
|
+
* 一律拒绝。
|
|
28
29
|
*/
|
|
29
30
|
export function isValidCloneName(name) {
|
|
30
31
|
if (!name || name.includes('/') || name.includes('\\') || name.startsWith('.'))
|
|
31
32
|
return false;
|
|
32
33
|
if (/[\s.]$/.test(name))
|
|
33
34
|
return false;
|
|
35
|
+
if (/[<>:"|?*]/.test(name))
|
|
36
|
+
return false;
|
|
34
37
|
if (WIN_RESERVED_NAMES.has(name.toLowerCase()))
|
|
35
38
|
return false;
|
|
36
39
|
return true;
|
|
@@ -48,7 +51,7 @@ export function generateCloneSuffix() {
|
|
|
48
51
|
* 默认新实例拉起:同入口 + 同参数,仅替换 --config-dir(detached,返回 pid)。
|
|
49
52
|
* spawn 骨架(log fd / windowsHide / error 兜底)单源于 restart.ts。
|
|
50
53
|
*/
|
|
51
|
-
|
|
54
|
+
function spawnDetachedCloneBridge(targetDir) {
|
|
52
55
|
const args = [];
|
|
53
56
|
const argv = process.argv.slice(2);
|
|
54
57
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -238,7 +241,7 @@ export class CloneSession {
|
|
|
238
241
|
return;
|
|
239
242
|
}
|
|
240
243
|
finally {
|
|
241
|
-
|
|
244
|
+
bestEffortUnlink(qrPath);
|
|
242
245
|
}
|
|
243
246
|
await this.connector
|
|
244
247
|
.sendWithRetry(ctx.chatId, {
|
|
@@ -264,7 +267,7 @@ export class CloneSession {
|
|
|
264
267
|
/**
|
|
265
268
|
* 扫码成功后的自动绑定落地:
|
|
266
269
|
* 1. 复制当前 config.yaml,仅替换 feishu.appId/appSecret;
|
|
267
|
-
* 2. 同步
|
|
270
|
+
* 2. 同步 cwd / 常用指令 / 会话状态(session 按 openId 重绑);
|
|
268
271
|
* 3. 用 registerApp 返回的新应用 openId 写 startup-contact.json(免认领绑定);
|
|
269
272
|
* 4. detached 拉起指向新目录的新实例(启动后自动向新应用发启动通知);
|
|
270
273
|
* 5. 在原应用回执「绑定完成 + 配置目录」,退出流程。
|
|
@@ -393,12 +396,13 @@ export class CloneSession {
|
|
|
393
396
|
}
|
|
394
397
|
/**
|
|
395
398
|
* 写配置失败时清理半成品目录。守卫:只删内容全部是本次产物
|
|
396
|
-
* (config.yaml / startup-contact.json 及其
|
|
397
|
-
* 文件时保留现场不动。
|
|
399
|
+
* (config.yaml / startup-contact.json 及其 tmp)的目录,混入其他
|
|
400
|
+
* 文件时保留现场不动。tmp 形态两种都认:atomic-write 现行命名
|
|
401
|
+
* (.{name}.tmp-{pid}-{ts}-{rand})与旧版固定 `.tmp`(升级瞬间崩溃的残留)。
|
|
398
402
|
*/
|
|
399
403
|
cleanupPartialDir(targetDir) {
|
|
400
404
|
try {
|
|
401
|
-
const allowed = /^(config\.yaml|startup-contact\.json)(\.tmp)
|
|
405
|
+
const allowed = /^(config\.yaml|startup-contact\.json)(\.tmp)?$|^\.(config\.yaml|startup-contact\.json)\.tmp-\d+-\d+-[a-z0-9]+$/;
|
|
402
406
|
const entries = fs.readdirSync(targetDir);
|
|
403
407
|
if (!entries.every((e) => allowed.test(e))) {
|
|
404
408
|
getLogger().warn(`[clone] target dir has foreign entries, kept: ${targetDir}`);
|
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
|
/**
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export declare const DEFAULT_TRUNCATE_SUFFIX = "\u2026\uFF08\u5DF2\u622A\u65AD\uFF09";
|
|
12
12
|
/**
|
|
13
13
|
* Max byte budget for a single session `tool_result` event's content when
|
|
14
|
-
* replaying an opencode session (
|
|
14
|
+
* replaying an opencode session (pre-fold). Pre-folding here bounds the
|
|
15
15
|
* in-memory `events[]` array and the intermediate card JSON so a single
|
|
16
16
|
* pathological tool_result never inflates the replay payload to megabytes.
|
|
17
17
|
*/
|
package/dist/common/truncate.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export const DEFAULT_TRUNCATE_SUFFIX = '…(已截断)';
|
|
12
12
|
/**
|
|
13
13
|
* Max byte budget for a single session `tool_result` event's content when
|
|
14
|
-
* replaying an opencode session (
|
|
14
|
+
* replaying an opencode session (pre-fold). Pre-folding here bounds the
|
|
15
15
|
* in-memory `events[]` array and the intermediate card JSON so a single
|
|
16
16
|
* pathological tool_result never inflates the replay payload to megabytes.
|
|
17
17
|
*/
|
|
@@ -76,7 +76,7 @@ export function truncate(str, max, options) {
|
|
|
76
76
|
if (budget <= 0)
|
|
77
77
|
return suffix;
|
|
78
78
|
// Iterate by Unicode codepoint (for...of) so the cut never lands inside a
|
|
79
|
-
// surrogate pair (
|
|
79
|
+
// surrogate pair (must not emit a lone surrogate).
|
|
80
80
|
let end = 0;
|
|
81
81
|
for (const ch of s) {
|
|
82
82
|
if (end + ch.length > budget)
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Consolidates the per-reader memory caches that previously hand-rolled the
|
|
5
5
|
* same "mtime/TTL + entry cap" Map pattern (kimi/pi/opencode session readers).
|
|
6
6
|
* Semantics follow the kimi implementation:
|
|
7
|
-
* - TTL is measured from the cache-write time (
|
|
7
|
+
* - TTL is measured from the cache-write time (defect 2), not key mtime.
|
|
8
8
|
* Note: ttlMs = 0 effectively disables caching — `Date.now() - at >= 0` is
|
|
9
9
|
* always true, so every entry expires immediately.
|
|
10
10
|
* - A hit refreshes recency (LRU); eviction removes the least recently
|
|
11
|
-
* inserted/used entry past `maxEntries` (
|
|
11
|
+
* inserted/used entry past `maxEntries` (defect 1: unbounded growth).
|
|
12
12
|
* The old pi implementation did NOT re-insert on hit (FIFO eviction); the
|
|
13
13
|
* eviction order for pi therefore changes from FIFO to LRU here.
|
|
14
14
|
*/
|
package/dist/common/ttl-cache.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Consolidates the per-reader memory caches that previously hand-rolled the
|
|
5
5
|
* same "mtime/TTL + entry cap" Map pattern (kimi/pi/opencode session readers).
|
|
6
6
|
* Semantics follow the kimi implementation:
|
|
7
|
-
* - TTL is measured from the cache-write time (
|
|
7
|
+
* - TTL is measured from the cache-write time (defect 2), not key mtime.
|
|
8
8
|
* Note: ttlMs = 0 effectively disables caching — `Date.now() - at >= 0` is
|
|
9
9
|
* always true, so every entry expires immediately.
|
|
10
10
|
* - A hit refreshes recency (LRU); eviction removes the least recently
|
|
11
|
-
* inserted/used entry past `maxEntries` (
|
|
11
|
+
* inserted/used entry past `maxEntries` (defect 1: unbounded growth).
|
|
12
12
|
* The old pi implementation did NOT re-insert on hit (FIFO eviction); the
|
|
13
13
|
* eviction order for pi therefore changes from FIFO to LRU here.
|
|
14
14
|
*/
|
|
@@ -52,32 +52,32 @@ export interface BundledModelInfo {
|
|
|
52
52
|
* 依据:codex 运行时对"已配置但文件缺失/不可解析"的目录是硬错误
|
|
53
53
|
* (core/src/config/mod.rs load_model_catalog ?),绝不会回退内置目录;因此键存在即
|
|
54
54
|
* catalog 模式,文件缺失自然落入 `codex debug models` 失败路径 → 模型列表回退
|
|
55
|
-
* [currentModel]
|
|
55
|
+
* [currentModel]。
|
|
56
56
|
*/
|
|
57
57
|
export declare function isCodexCatalogMode(codexHome?: string): boolean;
|
|
58
58
|
/**
|
|
59
59
|
* 单一解析器:把 `codex debug models`(活动目录)或 `--bundled`(内置目录)的原始 JSON
|
|
60
|
-
*
|
|
60
|
+
* 输出解析为模型列表(去重)。过滤/排序镜像 codex:
|
|
61
61
|
* - show_in_picker = visibility == List(openai_models.rs ModelPreset::from);
|
|
62
62
|
* - API 模式(lark 用 API key 认证)下 codex filter_by_auth 过滤 supported_in_api=false;
|
|
63
63
|
* - 按 priority 升序(build_available_models)。
|
|
64
|
-
* 档位按目录声明**原样透传**(含 custom/none
|
|
64
|
+
* 档位按目录声明**原样透传**(含 custom/none),不按标准枚举过滤、不虚构兜底:
|
|
65
65
|
* 未声明档位 → 空列表(codex with_model 语义:len=0 时用声明 default,都没有则不发
|
|
66
|
-
* effort
|
|
66
|
+
* effort;)。空串档位/空串 default 被过滤(codex ReasoningEffort::from_str
|
|
67
67
|
* 对 "" 硬错误)。supported_in_api===true 过滤等价于 codex 的 chatgpt_mode ||
|
|
68
|
-
* supported_in_api:本项目以 API-key 模式运行 codex,chatgpt_mode 恒 false
|
|
68
|
+
* supported_in_api:本项目以 API-key 模式运行 codex,chatgpt_mode 恒 false。
|
|
69
69
|
*/
|
|
70
70
|
export declare function parseCodexModelsOutput(stdout: string): BundledModelInfo[];
|
|
71
71
|
/**
|
|
72
72
|
* 统一目录入口:catalog 模式(model_catalog_json 已声明,含声明退化 '')跑
|
|
73
73
|
* `debug models`(无 --bundled),否则(未声明或 config.toml 解析失败)跑
|
|
74
|
-
* `debug models --bundled
|
|
74
|
+
* `debug models --bundled`。注:codex 无参 `debug models` 在
|
|
75
75
|
* uses_codex_backend()/has_command_auth() 时会请求远端 /models(models-manager
|
|
76
76
|
* manager.rs / models_endpoint.rs);本项目以 API-key 模式运行(无 ChatGPT/命令
|
|
77
77
|
* 认证),结果与 bundled 一致,故非 catalog 用 --bundled(已文档化)。
|
|
78
78
|
* 缓存:键含 binary/home/mode/models.json mtime:size + config.toml mtime:size
|
|
79
|
-
*
|
|
80
|
-
*
|
|
79
|
+
* 成功结果 TTL 1h,失败/空结果短 TTL 负缓存;stat 异常时
|
|
80
|
+
* 指纹为空。
|
|
81
81
|
*
|
|
82
82
|
* 同步路径(卡片构建用):缓存命中直接返回;若有一个后台异步加载
|
|
83
83
|
* (`loadCodexCatalogModelsAsync` / 启动 warm)在途,则不阻塞事件循环、不重复
|
|
@@ -117,7 +117,7 @@ export declare function getReasoningEffortOptions(model: string, codexHome?: str
|
|
|
117
117
|
/**
|
|
118
118
|
* Get default reasoning effort for a model.
|
|
119
119
|
* 未声明/未知模型 → undefined(codex 语义:ModelPreset.default_reasoning_effort
|
|
120
|
-
* unwrap_or(ReasoningEffort::None),即不传 effort
|
|
120
|
+
* unwrap_or(ReasoningEffort::None),即不传 effort;)。
|
|
121
121
|
*/
|
|
122
122
|
export declare function getDefaultReasoningEffort(model: string, codexHome?: string): string | undefined;
|
|
123
123
|
interface LoadCodexConfigOpts {
|
|
@@ -48,7 +48,7 @@ const FALLBACK_MODELS = ['o3', 'o4-mini', 'gpt-4o', 'gpt-4.1', 'gpt-4.1-mini', '
|
|
|
48
48
|
const ANTHROPIC_FALLBACK_MODELS = ['claude-sonnet-4-20250514', 'claude-opus-4-20250115'];
|
|
49
49
|
/** Bundled models from codex binary, 1h TTL is enough (only change on binary upgrade) */
|
|
50
50
|
const BUNDLED_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
51
|
-
/** 失败/空结果负缓存 TTL:避免每次卡片构建同步阻塞最长 8s
|
|
51
|
+
/** 失败/空结果负缓存 TTL:避免每次卡片构建同步阻塞最长 8s */
|
|
52
52
|
const NEGATIVE_CACHE_TTL_MS = 30 * 1000;
|
|
53
53
|
let catalogCache = null;
|
|
54
54
|
/** In-flight async catalog loads, keyed by cache key — dedup concurrent warm ups. */
|
|
@@ -58,7 +58,7 @@ const catalogInFlight = new Map();
|
|
|
58
58
|
* 返回语义:
|
|
59
59
|
* - undefined:config.toml 不存在或**未声明**该键 → 非 catalog 模式;
|
|
60
60
|
* - ''(空串哨兵):已声明但不可用(值为空串/非字符串)→ 按"catalog 声明退化"处理
|
|
61
|
-
*
|
|
61
|
+
* (绝不回退 bundled/FALLBACK);
|
|
62
62
|
* - TOML_PARSE_FAILED_SENTINEL:config.toml 存在但解析失败 → 无法得知是否声明,
|
|
63
63
|
* 按非 catalog 处理(用户未声明 model_catalog_json 时的默认行为);
|
|
64
64
|
* - 具体路径:声明有效(含文件缺失,文件缺失由调用方按 active 命令失败处理)。
|
|
@@ -87,7 +87,7 @@ function readModelCatalogJsonPath(codexHome) {
|
|
|
87
87
|
}
|
|
88
88
|
catch {
|
|
89
89
|
// config.toml 解析失败:codex 对非法配置是硬错误;此处无法得知是否声明了
|
|
90
|
-
// model_catalog_json,按非 catalog
|
|
90
|
+
// model_catalog_json,按非 catalog 处理,不冒充 catalog 声明退化
|
|
91
91
|
return TOML_PARSE_FAILED_SENTINEL;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -96,7 +96,7 @@ function readModelCatalogJsonPath(codexHome) {
|
|
|
96
96
|
* 依据:codex 运行时对"已配置但文件缺失/不可解析"的目录是硬错误
|
|
97
97
|
* (core/src/config/mod.rs load_model_catalog ?),绝不会回退内置目录;因此键存在即
|
|
98
98
|
* catalog 模式,文件缺失自然落入 `codex debug models` 失败路径 → 模型列表回退
|
|
99
|
-
* [currentModel]
|
|
99
|
+
* [currentModel]。
|
|
100
100
|
*/
|
|
101
101
|
export function isCodexCatalogMode(codexHome) {
|
|
102
102
|
const home = resolveCodexHome(codexHome);
|
|
@@ -105,15 +105,15 @@ export function isCodexCatalogMode(codexHome) {
|
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
107
|
* 单一解析器:把 `codex debug models`(活动目录)或 `--bundled`(内置目录)的原始 JSON
|
|
108
|
-
*
|
|
108
|
+
* 输出解析为模型列表(去重)。过滤/排序镜像 codex:
|
|
109
109
|
* - show_in_picker = visibility == List(openai_models.rs ModelPreset::from);
|
|
110
110
|
* - API 模式(lark 用 API key 认证)下 codex filter_by_auth 过滤 supported_in_api=false;
|
|
111
111
|
* - 按 priority 升序(build_available_models)。
|
|
112
|
-
* 档位按目录声明**原样透传**(含 custom/none
|
|
112
|
+
* 档位按目录声明**原样透传**(含 custom/none),不按标准枚举过滤、不虚构兜底:
|
|
113
113
|
* 未声明档位 → 空列表(codex with_model 语义:len=0 时用声明 default,都没有则不发
|
|
114
|
-
* effort
|
|
114
|
+
* effort;)。空串档位/空串 default 被过滤(codex ReasoningEffort::from_str
|
|
115
115
|
* 对 "" 硬错误)。supported_in_api===true 过滤等价于 codex 的 chatgpt_mode ||
|
|
116
|
-
* supported_in_api:本项目以 API-key 模式运行 codex,chatgpt_mode 恒 false
|
|
116
|
+
* supported_in_api:本项目以 API-key 模式运行 codex,chatgpt_mode 恒 false。
|
|
117
117
|
*/
|
|
118
118
|
export function parseCodexModelsOutput(stdout) {
|
|
119
119
|
const parsed = JSON.parse(stdout);
|
|
@@ -133,7 +133,7 @@ export function parseCodexModelsOutput(stdout) {
|
|
|
133
133
|
const declared = m.supported_reasoning_levels
|
|
134
134
|
.filter((r) => r !== null && typeof r === 'object' && 'effort' in r)
|
|
135
135
|
.map((r) => String(r.effort))
|
|
136
|
-
//
|
|
136
|
+
// codex 拒绝空串档位(openai_models.rs from_str "" → Err)
|
|
137
137
|
.filter((effort) => effort.length > 0);
|
|
138
138
|
supportedReasoningLevels.push(...declared);
|
|
139
139
|
}
|
|
@@ -153,13 +153,13 @@ export function parseCodexModelsOutput(stdout) {
|
|
|
153
153
|
/**
|
|
154
154
|
* 统一目录入口:catalog 模式(model_catalog_json 已声明,含声明退化 '')跑
|
|
155
155
|
* `debug models`(无 --bundled),否则(未声明或 config.toml 解析失败)跑
|
|
156
|
-
* `debug models --bundled
|
|
156
|
+
* `debug models --bundled`。注:codex 无参 `debug models` 在
|
|
157
157
|
* uses_codex_backend()/has_command_auth() 时会请求远端 /models(models-manager
|
|
158
158
|
* manager.rs / models_endpoint.rs);本项目以 API-key 模式运行(无 ChatGPT/命令
|
|
159
159
|
* 认证),结果与 bundled 一致,故非 catalog 用 --bundled(已文档化)。
|
|
160
160
|
* 缓存:键含 binary/home/mode/models.json mtime:size + config.toml mtime:size
|
|
161
|
-
*
|
|
162
|
-
*
|
|
161
|
+
* 成功结果 TTL 1h,失败/空结果短 TTL 负缓存;stat 异常时
|
|
162
|
+
* 指纹为空。
|
|
163
163
|
*
|
|
164
164
|
* 同步路径(卡片构建用):缓存命中直接返回;若有一个后台异步加载
|
|
165
165
|
* (`loadCodexCatalogModelsAsync` / 启动 warm)在途,则不阻塞事件循环、不重复
|
|
@@ -372,13 +372,13 @@ export function _clearCodexCatalogInFlightForTest() {
|
|
|
372
372
|
export function getReasoningEffortOptions(model, codexHome) {
|
|
373
373
|
const models = getCodexCatalogModels(codexHome);
|
|
374
374
|
const found = models.find((m) => m.slug === model);
|
|
375
|
-
// 未命中(含未知模型,codex fallback 元数据 supported 为空)→ []
|
|
375
|
+
// 未命中(含未知模型,codex fallback 元数据 supported 为空)→ [],不虚构档位
|
|
376
376
|
return found?.supportedReasoningLevels ?? [];
|
|
377
377
|
}
|
|
378
378
|
/**
|
|
379
379
|
* Get default reasoning effort for a model.
|
|
380
380
|
* 未声明/未知模型 → undefined(codex 语义:ModelPreset.default_reasoning_effort
|
|
381
|
-
* unwrap_or(ReasoningEffort::None),即不传 effort
|
|
381
|
+
* unwrap_or(ReasoningEffort::None),即不传 effort;)。
|
|
382
382
|
*/
|
|
383
383
|
export function getDefaultReasoningEffort(model, codexHome) {
|
|
384
384
|
const models = getCodexCatalogModels(codexHome);
|
|
@@ -459,7 +459,7 @@ export function loadCodexConfig(opts = {}) {
|
|
|
459
459
|
// 活动目录全局:codex 运行时 provider 与模型无绑定(StaticModelsManager),
|
|
460
460
|
// 每个 provider 下拉都展示目录全部 slug
|
|
461
461
|
// 活动目录不可用(命令失败/空)时回退 [currentModel]——无目录元数据时档位/能力
|
|
462
|
-
// 不可信;禁止泄漏 FALLBACK_MODELS/内置目录。model 键缺失时不得虚构 'o3'
|
|
462
|
+
// 不可信;禁止泄漏 FALLBACK_MODELS/内置目录。model 键缺失时不得虚构 'o3'。
|
|
463
463
|
const catalogCurrentModel = modelKeyRaw ?? catalogSlugs[0] ?? '';
|
|
464
464
|
const providerModelList = catalogSlugs.length > 0 ? catalogSlugs : modelKeyRaw ? [modelKeyRaw] : [];
|
|
465
465
|
providerModels = {};
|
|
@@ -492,7 +492,7 @@ export function loadCodexConfig(opts = {}) {
|
|
|
492
492
|
providerEnvKeys[name] = config.env_key;
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
|
-
// anthropic 显式配置但未写 env_key
|
|
495
|
+
// anthropic 显式配置但未写 env_key 时保留默认键(恢复旧版无条件映射的
|
|
496
496
|
// 配置场景行为;未配置时不再虚构)
|
|
497
497
|
if (rawProviderNames.includes('anthropic') &&
|
|
498
498
|
!modelProviders['anthropic']?.env_key &&
|
|
@@ -519,7 +519,7 @@ export function loadCodexConfig(opts = {}) {
|
|
|
519
519
|
}
|
|
520
520
|
catch {
|
|
521
521
|
// config.toml 解析失败(readModelCatalogJsonPath 已把此场景标记为 catalog 声明退化 ''):
|
|
522
|
-
// catalogMode 为 true 时绝不回退 bundled/FALLBACK
|
|
522
|
+
// catalogMode 为 true 时绝不回退 bundled/FALLBACK,返回空 provider/模型列表;
|
|
523
523
|
// 非 catalog 才走 FALLBACK 兜底。
|
|
524
524
|
if (catalogMode) {
|
|
525
525
|
return {
|
|
@@ -553,7 +553,7 @@ export function loadCodexConfig(opts = {}) {
|
|
|
553
553
|
* - If no provider: return all models from all providers (with currentModel prepended if not in any list)
|
|
554
554
|
*/
|
|
555
555
|
function buildModelOptions(currentModel, currentProvider, provider, providerModels) {
|
|
556
|
-
// 不为空 currentModel 虚构默认值(catalog
|
|
556
|
+
// 不为空 currentModel 虚构默认值(catalog 退化场景由上层显式传入);
|
|
557
557
|
// 空值时直接返回原始列表
|
|
558
558
|
const cm = currentModel;
|
|
559
559
|
if (provider && providerModels[provider]) {
|
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
|
@@ -11,6 +11,9 @@ export function resolveConfigDir(configDirArg) {
|
|
|
11
11
|
p = os.homedir();
|
|
12
12
|
else if (p.startsWith('~/'))
|
|
13
13
|
p = path.join(os.homedir(), p.slice(2));
|
|
14
|
+
// cmd/PowerShell 不展开 ~;`~\` 是 Windows 侧的常见键入形态,与 `~/` 同口径
|
|
15
|
+
else if (p.startsWith('~\\'))
|
|
16
|
+
p = path.join(os.homedir(), p.slice(2));
|
|
14
17
|
return path.resolve(p);
|
|
15
18
|
}
|
|
16
19
|
return path.join(os.homedir(), DEFAULT_DIR);
|
|
@@ -35,7 +38,7 @@ export function parseCliArgs(args = process.argv.slice(2)) {
|
|
|
35
38
|
result.version = true;
|
|
36
39
|
}
|
|
37
40
|
else if (arg === '--config-dir' && i + 1 < args.length) {
|
|
38
|
-
//
|
|
41
|
+
// peek, don't consume — when the next arg is itself a flag,
|
|
39
42
|
// roll back so it continues parsing (previously --settings/--help
|
|
40
43
|
// right after --config-dir were silently swallowed).
|
|
41
44
|
const nextArg = args[i + 1];
|
|
@@ -52,8 +55,8 @@ export function parseCliArgs(args = process.argv.slice(2)) {
|
|
|
52
55
|
else if (arg === '--dev') {
|
|
53
56
|
result.dev = true;
|
|
54
57
|
}
|
|
55
|
-
else if (arg === '--
|
|
56
|
-
result.
|
|
58
|
+
else if (arg === '--advanced-help') {
|
|
59
|
+
result.advancedHelp = true;
|
|
57
60
|
}
|
|
58
61
|
else if (arg === '--update' || arg === 'update') {
|
|
59
62
|
// 裸子命令等价形式:`lark-remote update` 与 `--update` 同义。
|
|
@@ -94,24 +97,23 @@ export function printHelp() {
|
|
|
94
97
|
' --settings <path> 指定 Claude 配置文件路径',
|
|
95
98
|
' --dev 开发模式:标记从源码 bun src/index.ts 启动(看门狗据此选择拉起方式)',
|
|
96
99
|
' --update 升级到最新版本后退出(用于 cron/脚本自动化升级)',
|
|
97
|
-
' --
|
|
100
|
+
' --advanced-help 高级配置参考:config.yaml 全部高级字段的语义与改法(面向 coding agent)',
|
|
98
101
|
' -h, --help 显示本帮助信息',
|
|
99
102
|
' -v, --version 显示版本号',
|
|
100
103
|
];
|
|
101
104
|
process.stdout.write(lines.join('\n') + '\n');
|
|
102
105
|
}
|
|
103
106
|
/**
|
|
104
|
-
* Print advanced-config reference to stdout (CLI `--
|
|
107
|
+
* Print advanced-config reference to stdout (CLI `--advanced-help`).
|
|
105
108
|
*
|
|
106
109
|
* 目标读者是 coding agent(用户让 agent 代改 config.yaml):披露所有
|
|
107
|
-
* /config 卡片未暴露的 YAML
|
|
108
|
-
* 等价命令为 /advancehelp(内容同源,卡片渲染)。
|
|
110
|
+
* /config 卡片未暴露的 YAML 字段——名称、默认值、语义、改法。
|
|
109
111
|
*/
|
|
110
|
-
export function
|
|
112
|
+
export function printAdvancedHelp() {
|
|
111
113
|
const configDir = resolveConfigDir(parseCliArgs().configDir);
|
|
112
|
-
const configPath = configDir
|
|
114
|
+
const configPath = path.join(configDir, 'config.yaml');
|
|
113
115
|
const lines = [
|
|
114
|
-
'lark-remote 高级配置参考(--
|
|
116
|
+
'lark-remote 高级配置参考(--advanced-help)',
|
|
115
117
|
'',
|
|
116
118
|
'目标读者:coding agent。用户把本输出交给 agent,agent 据此代改配置。',
|
|
117
119
|
'',
|
package/dist/config/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare const PI_THINKING_LEVELS: readonly ["off", "minimal", "low", "med
|
|
|
17
17
|
/**
|
|
18
18
|
* Default SIGTERM→SIGKILL grace period (ms). Exported so runners (SpawningRunner)
|
|
19
19
|
* and config-loading paths (src/index.ts) can reuse the same source of truth
|
|
20
|
-
* instead of re-literalizing `5000` (Clean Code
|
|
20
|
+
* instead of re-literalizing `5000` (Clean Code G25).
|
|
21
21
|
*/
|
|
22
22
|
export declare const DEFAULT_STOP_GRACE_MS: 5000;
|
|
23
23
|
/** Codex app-server turn idle timeout default (minutes). */
|
|
@@ -299,7 +299,7 @@ export declare function loadConfig(configPath: string): AppConfig;
|
|
|
299
299
|
*/
|
|
300
300
|
export declare function mapAgentKey(key: string): string;
|
|
301
301
|
/**
|
|
302
|
-
* 拒绝原型链危险 key
|
|
302
|
+
* 拒绝原型链危险 key 段。
|
|
303
303
|
*
|
|
304
304
|
* `/config` 直写路径的 key 是用户自由输入:`__proto__.polluted` 会通过
|
|
305
305
|
* `current['__proto__']` 拿到 Object.prototype 并写入,污染整个进程且不可恢复;
|
|
@@ -324,7 +324,7 @@ export declare function setConfigValue(configPath: string, config: AppConfig, ke
|
|
|
324
324
|
* Set multiple nested values in config, write back to YAML file once.
|
|
325
325
|
*
|
|
326
326
|
* 在 draft 副本上操作(structuredClone),验证通过后才写盘,避免校验失败时
|
|
327
|
-
* 内存中的 config 已被污染(
|
|
327
|
+
* 内存中的 config 已被污染(atomicity)。
|
|
328
328
|
*/
|
|
329
329
|
export declare function setConfigValues(configPath: string, config: AppConfig, updates: Record<string, string | undefined>): AppConfig;
|
|
330
330
|
/** Type helpers for agent config (used by getAgentConfig). */
|
package/dist/config/index.js
CHANGED
|
@@ -45,7 +45,7 @@ const DEFAULTS = {
|
|
|
45
45
|
/**
|
|
46
46
|
* Default SIGTERM→SIGKILL grace period (ms). Exported so runners (SpawningRunner)
|
|
47
47
|
* and config-loading paths (src/index.ts) can reuse the same source of truth
|
|
48
|
-
* instead of re-literalizing `5000` (Clean Code
|
|
48
|
+
* instead of re-literalizing `5000` (Clean Code G25).
|
|
49
49
|
*/
|
|
50
50
|
export const DEFAULT_STOP_GRACE_MS = DEFAULTS.STOP_GRACE_MS;
|
|
51
51
|
/** Codex app-server turn idle timeout default (minutes). */
|
|
@@ -115,9 +115,9 @@ export const CodexConfigSchema = z.object({
|
|
|
115
115
|
/**
|
|
116
116
|
* Reasoning effort level。codex 标准档位 minimal/low/medium/high/xhigh/max/ultra
|
|
117
117
|
* 之外还接受 none 与模型自定义值(ReasoningEffort::Custom),目录声明什么就存什么
|
|
118
|
-
*
|
|
118
|
+
* 故用字符串而非枚举。
|
|
119
119
|
*/
|
|
120
|
-
// codex 拒绝空串档位(ReasoningEffort::from_str "" → Err
|
|
120
|
+
// codex 拒绝空串档位(ReasoningEffort::from_str "" → Err)
|
|
121
121
|
reasoningEffort: z.string().min(1).optional(),
|
|
122
122
|
/** Approval policy (Codex 官方 AskForApproval 标准值). 默认 on-request。 */
|
|
123
123
|
approvalPolicy: z.enum(['untrusted', 'on-request', 'never']).default('on-request'),
|
|
@@ -363,7 +363,7 @@ function coerceValue(raw) {
|
|
|
363
363
|
return raw;
|
|
364
364
|
}
|
|
365
365
|
/**
|
|
366
|
-
* 拒绝原型链危险 key
|
|
366
|
+
* 拒绝原型链危险 key 段。
|
|
367
367
|
*
|
|
368
368
|
* `/config` 直写路径的 key 是用户自由输入:`__proto__.polluted` 会通过
|
|
369
369
|
* `current['__proto__']` 拿到 Object.prototype 并写入,污染整个进程且不可恢复;
|
|
@@ -423,7 +423,7 @@ export function setConfigValue(configPath, config, key, value) {
|
|
|
423
423
|
* Set multiple nested values in config, write back to YAML file once.
|
|
424
424
|
*
|
|
425
425
|
* 在 draft 副本上操作(structuredClone),验证通过后才写盘,避免校验失败时
|
|
426
|
-
* 内存中的 config 已被污染(
|
|
426
|
+
* 内存中的 config 已被污染(atomicity)。
|
|
427
427
|
*/
|
|
428
428
|
export function setConfigValues(configPath, config, updates) {
|
|
429
429
|
const draft = structuredClone(config);
|
|
@@ -472,7 +472,7 @@ function renderConfigPatch(configPath, updates, validated) {
|
|
|
472
472
|
doc.deleteIn(parts);
|
|
473
473
|
continue;
|
|
474
474
|
}
|
|
475
|
-
// 保存载荷里只写"校验结果中存活下来"的键:schema
|
|
475
|
+
// 保存载荷里只写"校验结果中存活下来"的键:schema 不认识的键(foo.bar)
|
|
476
476
|
// 会被 Zod 剥掉,跟着打在 Document 上就是把垃圾永久写进用户配置文件。
|
|
477
477
|
// 磁盘上原有的未知键不受影响 —— 那是用户手写的,本函数不碰。
|
|
478
478
|
const accepted = getConfigValue(validated, parts.join('.'));
|
|
@@ -22,7 +22,7 @@ const FALLBACK_PROVIDERS = Object.keys(FALLBACK_MODELS);
|
|
|
22
22
|
const OPENCODE_CONFIG_CACHE_TTL_MS = 60_000;
|
|
23
23
|
/**
|
|
24
24
|
* 失败负缓存 TTL:命令失败后 30s 内直接用 fallback,避免每次 /config 卡片构建
|
|
25
|
-
* 都同步阻塞事件循环最长 10s
|
|
25
|
+
* 都同步阻塞事件循环最长 10s(与 codex-config 负缓存对齐)。
|
|
26
26
|
*/
|
|
27
27
|
const OPENCODE_NEGATIVE_CACHE_TTL_MS = 30_000;
|
|
28
28
|
let opencodeConfigCache = null;
|
|
@@ -57,7 +57,7 @@ function buildFallbackResult() {
|
|
|
57
57
|
*/
|
|
58
58
|
export function loadOpencodeConfig() {
|
|
59
59
|
const now = Date.now();
|
|
60
|
-
// 正缓存命中:TTL 内不重复 execSync
|
|
60
|
+
// 正缓存命中:TTL 内不重复 execSync
|
|
61
61
|
if (opencodeConfigCache && now - opencodeConfigCache.timestamp < OPENCODE_CONFIG_CACHE_TTL_MS) {
|
|
62
62
|
return opencodeConfigCache.result;
|
|
63
63
|
}
|
|
@@ -74,7 +74,7 @@ export function loadOpencodeConfig() {
|
|
|
74
74
|
// 执行 opencode models --verbose 获取所有模型信息
|
|
75
75
|
const res = spawnProcessSync(resolved.file, ['models', '--verbose'], {
|
|
76
76
|
encoding: 'utf-8',
|
|
77
|
-
//
|
|
77
|
+
// 30s → 10s,与 codex/kimi 对齐;maxBuffer 防模型清单 ENOBUFS 隐性截断
|
|
78
78
|
timeout: 10000,
|
|
79
79
|
maxBuffer: 64 * 1024 * 1024,
|
|
80
80
|
});
|
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() {
|