lark-remote 0.1.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 +52 -0
- package/LICENSE +21 -0
- package/README.en.md +162 -0
- package/README.md +161 -0
- package/dist/binder.d.ts +57 -0
- package/dist/binder.js +105 -0
- package/dist/bridge/index.d.ts +338 -0
- package/dist/bridge/index.js +1281 -0
- package/dist/bridge/queue-manager.d.ts +277 -0
- package/dist/bridge/queue-manager.js +729 -0
- package/dist/card/bash-card-session.d.ts +78 -0
- package/dist/card/bash-card-session.js +174 -0
- package/dist/card/bash-renderer.d.ts +24 -0
- package/dist/card/bash-renderer.js +187 -0
- package/dist/card/card-budget.d.ts +31 -0
- package/dist/card/card-budget.js +280 -0
- package/dist/card/card-session.d.ts +83 -0
- package/dist/card/card-session.js +185 -0
- package/dist/card/card-shared.d.ts +28 -0
- package/dist/card/card-shared.js +99 -0
- package/dist/card/collapsible.d.ts +26 -0
- package/dist/card/collapsible.js +87 -0
- package/dist/card/run-card-session.d.ts +78 -0
- package/dist/card/run-card-session.js +165 -0
- package/dist/card/run-renderer.d.ts +66 -0
- package/dist/card/run-renderer.js +841 -0
- package/dist/card/run-state.d.ts +103 -0
- package/dist/card/run-state.js +313 -0
- package/dist/card/text-truncate.d.ts +56 -0
- package/dist/card/text-truncate.js +184 -0
- package/dist/card/time.d.ts +1 -0
- package/dist/card/time.js +14 -0
- package/dist/card/tool-render.d.ts +25 -0
- package/dist/card/tool-render.js +176 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.js +89 -0
- package/dist/common/guards.d.ts +37 -0
- package/dist/common/guards.js +65 -0
- package/dist/config/codex-config.d.ts +113 -0
- package/dist/config/codex-config.js +428 -0
- package/dist/config/dir.d.ts +17 -0
- package/dist/config/dir.js +81 -0
- package/dist/config/index.d.ts +219 -0
- package/dist/config/index.js +388 -0
- package/dist/config/kimi-config.d.ts +33 -0
- package/dist/config/kimi-config.js +111 -0
- package/dist/config/opencode-config.d.ts +20 -0
- package/dist/config/opencode-config.js +124 -0
- package/dist/config/pi-config.d.ts +49 -0
- package/dist/config/pi-config.js +151 -0
- package/dist/config/wizard.d.ts +12 -0
- package/dist/config/wizard.js +84 -0
- package/dist/connector/file-limits.d.ts +7 -0
- package/dist/connector/file-limits.js +8 -0
- package/dist/connector/index.d.ts +86 -0
- package/dist/connector/index.js +340 -0
- package/dist/error-classification.d.ts +15 -0
- package/dist/error-classification.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +580 -0
- package/dist/instance-lock.d.ts +30 -0
- package/dist/instance-lock.js +184 -0
- package/dist/logger/index.d.ts +57 -0
- package/dist/logger/index.js +178 -0
- package/dist/order/index.d.ts +27 -0
- package/dist/order/index.js +90 -0
- package/dist/persistence/atomic-write.d.ts +10 -0
- package/dist/persistence/atomic-write.js +65 -0
- package/dist/persistence/load-json-file.d.ts +9 -0
- package/dist/persistence/load-json-file.js +23 -0
- package/dist/restart.d.ts +24 -0
- package/dist/restart.js +73 -0
- package/dist/router/card-helpers.d.ts +28 -0
- package/dist/router/card-helpers.js +65 -0
- package/dist/router/config/claude.d.ts +14 -0
- package/dist/router/config/claude.js +72 -0
- package/dist/router/config/codex.d.ts +22 -0
- package/dist/router/config/codex.js +100 -0
- package/dist/router/config/common/render.d.ts +16 -0
- package/dist/router/config/common/render.js +158 -0
- package/dist/router/config/index.d.ts +18 -0
- package/dist/router/config/index.js +38 -0
- package/dist/router/config/kimi.d.ts +21 -0
- package/dist/router/config/kimi.js +63 -0
- package/dist/router/config/opencode.d.ts +12 -0
- package/dist/router/config/opencode.js +44 -0
- package/dist/router/config/pi.d.ts +12 -0
- package/dist/router/config/pi.js +50 -0
- package/dist/router/config/types.d.ts +43 -0
- package/dist/router/config/types.js +8 -0
- package/dist/router/index.d.ts +318 -0
- package/dist/router/index.js +2715 -0
- package/dist/router/order-exec-dispatch.d.ts +30 -0
- package/dist/router/order-exec-dispatch.js +48 -0
- package/dist/router/utils.d.ts +44 -0
- package/dist/router/utils.js +116 -0
- package/dist/runner/bash/index.d.ts +4 -0
- package/dist/runner/bash/index.js +5 -0
- package/dist/runner/bash/runner.d.ts +55 -0
- package/dist/runner/bash/runner.js +213 -0
- package/dist/runner/claude/index.d.ts +4 -0
- package/dist/runner/claude/index.js +5 -0
- package/dist/runner/claude/runner.d.ts +29 -0
- package/dist/runner/claude/runner.js +83 -0
- package/dist/runner/codex/argv.d.ts +33 -0
- package/dist/runner/codex/argv.js +40 -0
- package/dist/runner/codex/index.d.ts +1 -0
- package/dist/runner/codex/index.js +2 -0
- package/dist/runner/codex/jsonl.d.ts +37 -0
- package/dist/runner/codex/jsonl.js +221 -0
- package/dist/runner/codex/runner.d.ts +65 -0
- package/dist/runner/codex/runner.js +124 -0
- package/dist/runner/common/exec-translator.d.ts +73 -0
- package/dist/runner/common/exec-translator.js +105 -0
- package/dist/runner/common/jsonl-stream.d.ts +39 -0
- package/dist/runner/common/jsonl-stream.js +203 -0
- package/dist/runner/common/process-stopper.d.ts +28 -0
- package/dist/runner/common/process-stopper.js +79 -0
- package/dist/runner/common/runner-utils.d.ts +21 -0
- package/dist/runner/common/runner-utils.js +65 -0
- package/dist/runner/common/spawn-heartbeat.d.ts +33 -0
- package/dist/runner/common/spawn-heartbeat.js +51 -0
- package/dist/runner/common/spawning-runner.d.ts +321 -0
- package/dist/runner/common/spawning-runner.js +634 -0
- package/dist/runner/common/usage.d.ts +31 -0
- package/dist/runner/common/usage.js +29 -0
- package/dist/runner/index.d.ts +19 -0
- package/dist/runner/index.js +24 -0
- package/dist/runner/kimi/index.d.ts +1 -0
- package/dist/runner/kimi/index.js +2 -0
- package/dist/runner/kimi/jsonl.d.ts +28 -0
- package/dist/runner/kimi/jsonl.js +143 -0
- package/dist/runner/kimi/runner.d.ts +50 -0
- package/dist/runner/kimi/runner.js +105 -0
- package/dist/runner/opencode/argv.d.ts +32 -0
- package/dist/runner/opencode/argv.js +35 -0
- package/dist/runner/opencode/index.d.ts +1 -0
- package/dist/runner/opencode/index.js +2 -0
- package/dist/runner/opencode/jsonl.d.ts +68 -0
- package/dist/runner/opencode/jsonl.js +295 -0
- package/dist/runner/opencode/runner.d.ts +63 -0
- package/dist/runner/opencode/runner.js +93 -0
- package/dist/runner/pi/index.d.ts +1 -0
- package/dist/runner/pi/index.js +2 -0
- package/dist/runner/pi/jsonl.d.ts +128 -0
- package/dist/runner/pi/jsonl.js +249 -0
- package/dist/runner/pi/runner.d.ts +37 -0
- package/dist/runner/pi/runner.js +76 -0
- package/dist/runner/registry.d.ts +54 -0
- package/dist/runner/registry.js +75 -0
- package/dist/runner/resolve-agent-choices.d.ts +7 -0
- package/dist/runner/resolve-agent-choices.js +76 -0
- package/dist/runner/sync-agent-choices.d.ts +6 -0
- package/dist/runner/sync-agent-choices.js +73 -0
- package/dist/runner/types.d.ts +250 -0
- package/dist/runner/types.js +8 -0
- package/dist/session/claude/index.d.ts +1 -0
- package/dist/session/claude/index.js +2 -0
- package/dist/session/claude/session-reader.d.ts +34 -0
- package/dist/session/claude/session-reader.js +46 -0
- package/dist/session/claude/sessions.d.ts +53 -0
- package/dist/session/claude/sessions.js +528 -0
- package/dist/session/codex/index.d.ts +1 -0
- package/dist/session/codex/index.js +2 -0
- package/dist/session/codex/rollout-reader.d.ts +88 -0
- package/dist/session/codex/rollout-reader.js +473 -0
- package/dist/session/codex/sessions.d.ts +21 -0
- package/dist/session/codex/sessions.js +40 -0
- package/dist/session/common/catch-up.d.ts +12 -0
- package/dist/session/common/catch-up.js +31 -0
- package/dist/session/common/constants.d.ts +11 -0
- package/dist/session/common/constants.js +12 -0
- package/dist/session/common/content-blocks.d.ts +37 -0
- package/dist/session/common/content-blocks.js +68 -0
- package/dist/session/common/jsonl.d.ts +77 -0
- package/dist/session/common/jsonl.js +351 -0
- package/dist/session/common/usage-accumulator.d.ts +58 -0
- package/dist/session/common/usage-accumulator.js +72 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +11 -0
- package/dist/session/kimi/index.d.ts +1 -0
- package/dist/session/kimi/index.js +2 -0
- package/dist/session/kimi/sessions.d.ts +81 -0
- package/dist/session/kimi/sessions.js +527 -0
- package/dist/session/opencode/index.d.ts +1 -0
- package/dist/session/opencode/index.js +2 -0
- package/dist/session/opencode/sessions.d.ts +81 -0
- package/dist/session/opencode/sessions.js +388 -0
- package/dist/session/pi/index.d.ts +1 -0
- package/dist/session/pi/index.js +2 -0
- package/dist/session/pi/sessions.d.ts +27 -0
- package/dist/session/pi/sessions.js +399 -0
- package/dist/session/registry.d.ts +12 -0
- package/dist/session/registry.js +20 -0
- package/dist/session/session-store.d.ts +86 -0
- package/dist/session/session-store.js +450 -0
- package/dist/startup-contact.d.ts +18 -0
- package/dist/startup-contact.js +51 -0
- package/dist/workspace/index.d.ts +19 -0
- package/dist/workspace/index.js +53 -0
- package/package.json +93 -0
package/dist/restart.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getLogger } from './logger/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Self-restart without an external watchdog/cron: the only process that can
|
|
7
|
+
* reliably start the successor is the dying bridge itself. The old process
|
|
8
|
+
* spawns a detached replacement (same executable + argv, hence same
|
|
9
|
+
* --config-dir), then exits cleanly. The child learns the old pid via env
|
|
10
|
+
* and waits for it to die before acquiring the instance lock, so the lock
|
|
11
|
+
* file stays the single source of truth for the handoff.
|
|
12
|
+
*/
|
|
13
|
+
/** Env var telling a replacement child which parent pid to wait for before acquiring the instance lock. */
|
|
14
|
+
export const RESTART_WAIT_PID_ENV = 'LARK_REMOTE_RESTART_WAIT_PID';
|
|
15
|
+
const WAIT_TIMEOUT_MS = 20_000;
|
|
16
|
+
const POLL_MS = 100;
|
|
17
|
+
/**
|
|
18
|
+
* Spawn a detached replacement bridge with the same executable and argv
|
|
19
|
+
* (including --config-dir) and return its pid. The caller is expected to
|
|
20
|
+
* exit right after. stdio is redirected to restart-child.log so early
|
|
21
|
+
* startup failures (before the file logger initializes) are not lost.
|
|
22
|
+
*/
|
|
23
|
+
export function spawnReplacementBridge(logsDir) {
|
|
24
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
25
|
+
const out = fs.openSync(path.join(logsDir, 'restart-child.log'), 'a');
|
|
26
|
+
const child = spawn(process.execPath, process.argv.slice(1), {
|
|
27
|
+
cwd: process.cwd(),
|
|
28
|
+
env: { ...process.env, [RESTART_WAIT_PID_ENV]: String(process.pid) },
|
|
29
|
+
detached: true,
|
|
30
|
+
stdio: ['ignore', out, out],
|
|
31
|
+
});
|
|
32
|
+
// Late spawn errors (e.g. binary removed mid-run) must not crash the
|
|
33
|
+
// exiting parent via an unhandled 'error' event. Attach BEFORE the pid
|
|
34
|
+
// check — a failed spawn emits 'error' asynchronously (ENOENT/EACCES),
|
|
35
|
+
// and the pid check below throws synchronously first, so attaching after
|
|
36
|
+
// the throw would leave the 'error' event unhandled → uncaughtException
|
|
37
|
+
// → the old bridge exits despite having promised to stay alive.
|
|
38
|
+
child.on('error', () => { });
|
|
39
|
+
fs.closeSync(out);
|
|
40
|
+
if (child.pid === undefined) {
|
|
41
|
+
throw new Error('spawn replacement bridge failed: no pid');
|
|
42
|
+
}
|
|
43
|
+
child.unref();
|
|
44
|
+
return child.pid;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* On startup, if this process was spawned as a restart replacement, wait for
|
|
48
|
+
* the previous bridge process to exit (and release the instance lock) before
|
|
49
|
+
* continuing. No-op for normal starts. On timeout, proceed anyway — lock
|
|
50
|
+
* acquisition remains the authority.
|
|
51
|
+
*/
|
|
52
|
+
export async function waitForPreviousInstance() {
|
|
53
|
+
const raw = process.env[RESTART_WAIT_PID_ENV];
|
|
54
|
+
delete process.env[RESTART_WAIT_PID_ENV];
|
|
55
|
+
if (!raw)
|
|
56
|
+
return;
|
|
57
|
+
const pid = Number(raw);
|
|
58
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
59
|
+
return;
|
|
60
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
61
|
+
while (Date.now() < deadline) {
|
|
62
|
+
try {
|
|
63
|
+
process.kill(pid, 0);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
getLogger().info(`[restart] previous instance pid=${pid} exited, continuing startup`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
70
|
+
}
|
|
71
|
+
getLogger().warn(`[restart] previous instance pid=${pid} still alive after ${WAIT_TIMEOUT_MS}ms, proceeding anyway`);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=restart.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CardKit helpers for router cards (session history, dashboard,
|
|
3
|
+
* ls, ws, etc.).
|
|
4
|
+
*
|
|
5
|
+
* Re-exports `markdownDiv` from `card/collapsible.ts` and adds session-content
|
|
6
|
+
* specific builders (e.g. `sessionEventPanel`) so `router/index.ts` can fold
|
|
7
|
+
* long session histories into collapsible panels without duplicating logic.
|
|
8
|
+
*/
|
|
9
|
+
import { markdownDiv } from '../card/collapsible.js';
|
|
10
|
+
import type { AgentSessionContentEvent } from '../runner/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Build a collapsible panel for a single session content event.
|
|
13
|
+
*
|
|
14
|
+
* Header carries a type emoji (👤 user / 🤖 assistant·text / 💭 thinking /
|
|
15
|
+
* 🔧 tool_use / 🟢🔴 tool_result) with optional timestamp.
|
|
16
|
+
* Body: the event's content (markdown).
|
|
17
|
+
*
|
|
18
|
+
* The last `tailExpandedCount` events are expanded by default so the user
|
|
19
|
+
* sees the most recent context without clicking; older events are collapsed.
|
|
20
|
+
*
|
|
21
|
+
* @param ev - Session content event
|
|
22
|
+
* @param index - Index in the events array
|
|
23
|
+
* @param totalEvents - Total number of events
|
|
24
|
+
* @param tailExpandedCount - How many events from the end to keep expanded (default 2)
|
|
25
|
+
* @param agentKind - Agent type for display name (e.g. 'claude', 'pi'). Defaults to 'claude'.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sessionEventPanel(ev: AgentSessionContentEvent, index: number, totalEvents: number, tailExpandedCount?: number, agentKind?: string): object;
|
|
28
|
+
export { markdownDiv };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CardKit helpers for router cards (session history, dashboard,
|
|
3
|
+
* ls, ws, etc.).
|
|
4
|
+
*
|
|
5
|
+
* Re-exports `markdownDiv` from `card/collapsible.ts` and adds session-content
|
|
6
|
+
* specific builders (e.g. `sessionEventPanel`) so `router/index.ts` can fold
|
|
7
|
+
* long session histories into collapsible panels without duplicating logic.
|
|
8
|
+
*/
|
|
9
|
+
import { agentDisplayName } from '../card/card-shared.js';
|
|
10
|
+
import { collapsibleMarkdownPanel, markdownDiv } from '../card/collapsible.js';
|
|
11
|
+
import { formatTimestamp } from '../card/time.js';
|
|
12
|
+
/**
|
|
13
|
+
* Build a collapsible panel for a single session content event.
|
|
14
|
+
*
|
|
15
|
+
* Header carries a type emoji (👤 user / 🤖 assistant·text / 💭 thinking /
|
|
16
|
+
* 🔧 tool_use / 🟢🔴 tool_result) with optional timestamp.
|
|
17
|
+
* Body: the event's content (markdown).
|
|
18
|
+
*
|
|
19
|
+
* The last `tailExpandedCount` events are expanded by default so the user
|
|
20
|
+
* sees the most recent context without clicking; older events are collapsed.
|
|
21
|
+
*
|
|
22
|
+
* @param ev - Session content event
|
|
23
|
+
* @param index - Index in the events array
|
|
24
|
+
* @param totalEvents - Total number of events
|
|
25
|
+
* @param tailExpandedCount - How many events from the end to keep expanded (default 2)
|
|
26
|
+
* @param agentKind - Agent type for display name (e.g. 'claude', 'pi'). Defaults to 'claude'.
|
|
27
|
+
*/
|
|
28
|
+
export function sessionEventPanel(ev, index, totalEvents, tailExpandedCount = 2, agentKind = 'claude') {
|
|
29
|
+
const label = eventLabel(ev, agentKind);
|
|
30
|
+
const ts = formatTimestamp(ev.timestamp);
|
|
31
|
+
const title = ts ? `${label} (${ts})` : label;
|
|
32
|
+
const expanded = index >= totalEvents - tailExpandedCount;
|
|
33
|
+
return collapsibleMarkdownPanel({
|
|
34
|
+
title,
|
|
35
|
+
expanded,
|
|
36
|
+
border: 'grey',
|
|
37
|
+
content: ev.content,
|
|
38
|
+
textSize: 'notation',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 事件类型 → 面板标题标签。五种 reader 产出的事件类型是
|
|
43
|
+
* text/thinking/tool_use/tool_result(claude 另有 role-only 的 user/assistant),
|
|
44
|
+
* 裸英文 type 名对用户不可读,统一映射为 emoji 标签。
|
|
45
|
+
* tool_result 的成功/失败跟随正文前缀(content-blocks 打头 🟢/🔴),默认 🟢。
|
|
46
|
+
*/
|
|
47
|
+
function eventLabel(ev, agentKind) {
|
|
48
|
+
switch (ev.type) {
|
|
49
|
+
case 'user':
|
|
50
|
+
return '👤 你';
|
|
51
|
+
case 'assistant':
|
|
52
|
+
case 'text':
|
|
53
|
+
return `🤖 ${agentDisplayName(agentKind)}`;
|
|
54
|
+
case 'thinking':
|
|
55
|
+
return '💭 思考';
|
|
56
|
+
case 'tool_use':
|
|
57
|
+
return '🔧 工具调用';
|
|
58
|
+
case 'tool_result':
|
|
59
|
+
return ev.content.startsWith('🔴') ? '🔴 工具结果' : '🟢 工具结果';
|
|
60
|
+
default:
|
|
61
|
+
return ev.type;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export { markdownDiv };
|
|
65
|
+
//# sourceMappingURL=card-helpers.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude config builder — builds fields and handles field changes for Claude agent.
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentConfigCardBuilder, ConfigField } from './types.js';
|
|
5
|
+
import type { AppConfig } from '../../config/index.js';
|
|
6
|
+
export declare class ClaudeConfigBuilder implements AgentConfigCardBuilder {
|
|
7
|
+
/** Find Claude settings.json path (for dynamic model reading). */
|
|
8
|
+
private findSettingsPath;
|
|
9
|
+
buildFields(displayConfig: AppConfig): ConfigField[];
|
|
10
|
+
handleFieldChange(key: string, _value: unknown, _config: AppConfig): Array<{
|
|
11
|
+
key: string;
|
|
12
|
+
value: unknown;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude config builder — builds fields and handles field changes for Claude agent.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { MODEL_ID_TO_ALIAS, MODEL_ALIAS_TO_ID, getModelOptionsFromSettings, CLAUDE_EFFORTS, } from '../../config/index.js';
|
|
8
|
+
export class ClaudeConfigBuilder {
|
|
9
|
+
/** Find Claude settings.json path (for dynamic model reading). */
|
|
10
|
+
findSettingsPath() {
|
|
11
|
+
const envPath = process.env.CLAUDE_SETTINGS_PATH;
|
|
12
|
+
if (envPath && fs.existsSync(envPath)) {
|
|
13
|
+
return envPath;
|
|
14
|
+
}
|
|
15
|
+
const defaultPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
16
|
+
if (fs.existsSync(defaultPath)) {
|
|
17
|
+
return defaultPath;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
buildFields(displayConfig) {
|
|
22
|
+
const fields = [];
|
|
23
|
+
// 模型选项使用 alias,动态从 settings.json 读取
|
|
24
|
+
const modelAliasOptions = ['fable', 'opus', 'sonnet', 'haiku'];
|
|
25
|
+
const currentModel = displayConfig.claude?.model;
|
|
26
|
+
const currentEffort = displayConfig.claude?.effort ?? 'medium';
|
|
27
|
+
// 动态从 Claude settings.json 读取可选模型列表
|
|
28
|
+
const settingsPath = this.findSettingsPath();
|
|
29
|
+
const dynamicModelOptions = settingsPath ? getModelOptionsFromSettings(settingsPath) : [];
|
|
30
|
+
// 如果从 settings 读取到模型,使用它们;否则使用默认的 alias 列表
|
|
31
|
+
const modelOptions = dynamicModelOptions.length > 0 ? dynamicModelOptions : modelAliasOptions;
|
|
32
|
+
// 当前值处理:从 settings 读取时是 model ID,需要显示 alias
|
|
33
|
+
let currentModelValue = currentModel;
|
|
34
|
+
if (currentModel) {
|
|
35
|
+
currentModelValue = MODEL_ID_TO_ALIAS[currentModel] ?? currentModel;
|
|
36
|
+
}
|
|
37
|
+
const isCustomModel = currentModel &&
|
|
38
|
+
!modelOptions.includes(currentModel) &&
|
|
39
|
+
!modelOptions.includes(MODEL_ID_TO_ALIAS[currentModel] ?? '') &&
|
|
40
|
+
!Object.values(MODEL_ID_TO_ALIAS).includes(currentModel) &&
|
|
41
|
+
!(currentModel in MODEL_ID_TO_ALIAS) &&
|
|
42
|
+
!Object.values(MODEL_ALIAS_TO_ID).includes(currentModel);
|
|
43
|
+
fields.push({
|
|
44
|
+
key: 'claude.model',
|
|
45
|
+
label: '使用模型',
|
|
46
|
+
type: 'select',
|
|
47
|
+
options: modelOptions,
|
|
48
|
+
currentValue: isCustomModel ? undefined : currentModelValue,
|
|
49
|
+
});
|
|
50
|
+
fields.push({
|
|
51
|
+
key: 'claude.model',
|
|
52
|
+
label: '自定义模型名',
|
|
53
|
+
type: 'input',
|
|
54
|
+
currentValue: isCustomModel ? currentModel : '',
|
|
55
|
+
});
|
|
56
|
+
fields.push({
|
|
57
|
+
key: 'claude.effort',
|
|
58
|
+
label: '推理强度',
|
|
59
|
+
type: 'select',
|
|
60
|
+
// Use constant for effort options (SSOT)
|
|
61
|
+
options: CLAUDE_EFFORTS,
|
|
62
|
+
currentValue: currentEffort,
|
|
63
|
+
});
|
|
64
|
+
return fields;
|
|
65
|
+
}
|
|
66
|
+
handleFieldChange(key, _value, _config) {
|
|
67
|
+
// Claude has no dependent field changes (no auto-reset on provider change, etc.)
|
|
68
|
+
// Return the key as-is for the router to apply.
|
|
69
|
+
return [{ key, value: _value }];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex config builder — builds fields and handles field changes for Codex agent.
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentConfigCardBuilder, ConfigField } from './types.js';
|
|
5
|
+
import type { AppConfig } from '../../config/index.js';
|
|
6
|
+
export declare class CodexConfigBuilder implements AgentConfigCardBuilder {
|
|
7
|
+
buildFields(displayConfig: AppConfig): ConfigField[];
|
|
8
|
+
handleFieldChange(key: string, value: unknown, config: AppConfig): Array<{
|
|
9
|
+
key: string;
|
|
10
|
+
value: unknown;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* 按 codex with_model 语义(core/src/session/turn_context.rs:255-269)计算切到
|
|
14
|
+
* newModel 后的档位补丁:
|
|
15
|
+
* - 当前档位仍被新模型支持 → 不产出补丁;
|
|
16
|
+
* - 否则优先 supported_reasoning_levels 中位 (len-1)/2;
|
|
17
|
+
* - 列表为空才用声明 default_reasoning_level;
|
|
18
|
+
* - 两者皆无 → value=undefined(清空档位 = 不传 effort;router setNestedValue
|
|
19
|
+
* 对 undefined 做键删除,P1)。
|
|
20
|
+
*/
|
|
21
|
+
private effortPatchForModel;
|
|
22
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex config builder — builds fields and handles field changes for Codex agent.
|
|
3
|
+
*/
|
|
4
|
+
import { loadCodexConfig, getReasoningEffortOptions, getDefaultReasoningEffort, } from '../../config/codex-config.js';
|
|
5
|
+
export class CodexConfigBuilder {
|
|
6
|
+
buildFields(displayConfig) {
|
|
7
|
+
const fields = [];
|
|
8
|
+
// 从 codex config.toml 动态读取 provider 和 model
|
|
9
|
+
const codexBinary = displayConfig.agents?.codex?.binary ?? 'codex';
|
|
10
|
+
const codexCfg = loadCodexConfig({ binary: codexBinary });
|
|
11
|
+
const codexProviderNames = codexCfg.providerNames;
|
|
12
|
+
const currentModelProvider = displayConfig.agents?.codex?.modelProvider ?? codexCfg.currentProvider;
|
|
13
|
+
const currentCodexModel = displayConfig.agents?.codex?.model ?? codexCfg.currentModel;
|
|
14
|
+
const currentReasoningEffort = displayConfig.agents?.codex?.reasoningEffort ??
|
|
15
|
+
getDefaultReasoningEffort(currentCodexModel, codexBinary);
|
|
16
|
+
// 传入当前 provider 过滤模型列���
|
|
17
|
+
const codexModelOptions = codexCfg.modelOptions(currentModelProvider);
|
|
18
|
+
fields.push({
|
|
19
|
+
key: 'agents.codex.modelProvider',
|
|
20
|
+
label: 'Codex Provider',
|
|
21
|
+
type: 'select',
|
|
22
|
+
options: codexProviderNames,
|
|
23
|
+
currentValue: currentModelProvider,
|
|
24
|
+
});
|
|
25
|
+
fields.push({
|
|
26
|
+
key: 'agents.codex.model',
|
|
27
|
+
label: '使用模型',
|
|
28
|
+
type: 'select',
|
|
29
|
+
options: codexModelOptions,
|
|
30
|
+
currentValue: currentCodexModel,
|
|
31
|
+
});
|
|
32
|
+
const isCustomModel = currentCodexModel && !codexModelOptions.includes(currentCodexModel);
|
|
33
|
+
// 自定义模型名输入框(与 Claude 一致的行为)
|
|
34
|
+
fields.push({
|
|
35
|
+
key: 'agents.codex.model',
|
|
36
|
+
label: '自定义模型名',
|
|
37
|
+
type: 'input',
|
|
38
|
+
currentValue: isCustomModel ? currentCodexModel : '',
|
|
39
|
+
});
|
|
40
|
+
fields.push({
|
|
41
|
+
key: 'agents.codex.reasoningEffort',
|
|
42
|
+
label: '推理强度',
|
|
43
|
+
type: 'select',
|
|
44
|
+
// 档位按目录实际声明透传(P2-5):codex 支持 none/Custom,不再按标准枚举过滤
|
|
45
|
+
options: getReasoningEffortOptions(currentCodexModel, codexBinary),
|
|
46
|
+
currentValue: currentReasoningEffort,
|
|
47
|
+
});
|
|
48
|
+
return fields;
|
|
49
|
+
}
|
|
50
|
+
handleFieldChange(key, value, config) {
|
|
51
|
+
const patches = [];
|
|
52
|
+
patches.push({ key, value });
|
|
53
|
+
// provider 变更时,重置 model 为�� provider 的首个模型
|
|
54
|
+
if (key === 'agents.codex.modelProvider' && typeof value === 'string') {
|
|
55
|
+
const codexBinary = config.agents?.codex?.binary ?? 'codex';
|
|
56
|
+
const codexCfg = loadCodexConfig({ binary: codexBinary });
|
|
57
|
+
const newModelOptions = codexCfg.modelOptions(value);
|
|
58
|
+
const currentModel = config.agents?.codex?.model;
|
|
59
|
+
const currentModelIsValid = newModelOptions.some((m) => m === currentModel);
|
|
60
|
+
if (!currentModelIsValid && newModelOptions.length > 0) {
|
|
61
|
+
const replacedModel = newModelOptions[0];
|
|
62
|
+
patches.push({ key: 'agents.codex.model', value: replacedModel });
|
|
63
|
+
// provider 切换自动替换模型后,必须对替换后的模型执行
|
|
64
|
+
// 同样的档位校验——否则旧模型支持的档位会原样留在卡片/透传给不支持它的模型
|
|
65
|
+
// (codex 只在会话中 with_model 时钳制,session 创建路径不钳制)。
|
|
66
|
+
const effortPatch = this.effortPatchForModel(config, replacedModel);
|
|
67
|
+
if (effortPatch)
|
|
68
|
+
patches.push(effortPatch);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// model 变更时,若当前档位不被新模型支持,按 codex with_model 语义重置
|
|
72
|
+
if (key === 'agents.codex.model' && typeof value === 'string') {
|
|
73
|
+
const effortPatch = this.effortPatchForModel(config, value);
|
|
74
|
+
if (effortPatch)
|
|
75
|
+
patches.push(effortPatch);
|
|
76
|
+
}
|
|
77
|
+
return patches;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 按 codex with_model 语义(core/src/session/turn_context.rs:255-269)计算切到
|
|
81
|
+
* newModel 后的档位补丁:
|
|
82
|
+
* - 当前档位仍被新模型支持 → 不产出补丁;
|
|
83
|
+
* - 否则优先 supported_reasoning_levels 中位 (len-1)/2;
|
|
84
|
+
* - 列表为空才用声明 default_reasoning_level;
|
|
85
|
+
* - 两者皆无 → value=undefined(清空档位 = 不传 effort;router setNestedValue
|
|
86
|
+
* 对 undefined 做键删除,P1)。
|
|
87
|
+
*/
|
|
88
|
+
effortPatchForModel(config, newModel) {
|
|
89
|
+
const codexBinary = config.agents?.codex?.binary ?? 'codex';
|
|
90
|
+
const currentReasoningEffort = config.agents?.codex?.reasoningEffort;
|
|
91
|
+
const newModelSupportedEfforts = getReasoningEffortOptions(newModel, codexBinary);
|
|
92
|
+
const isCurrentEffortValid = currentReasoningEffort && newModelSupportedEfforts.includes(currentReasoningEffort);
|
|
93
|
+
if (isCurrentEffortValid)
|
|
94
|
+
return null;
|
|
95
|
+
const middle = newModelSupportedEfforts[Math.floor((newModelSupportedEfforts.length - 1) / 2)];
|
|
96
|
+
const newEffort = middle ?? getDefaultReasoningEffort(newModel, codexBinary);
|
|
97
|
+
return { key: 'agents.codex.reasoningEffort', value: newEffort };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared config card rendering logic.
|
|
3
|
+
*
|
|
4
|
+
* Builds CardKit 2.0 elements for config fields (boolean toggle, select dropdown, input).
|
|
5
|
+
* Used by all agent config builders and the shared tabs (idle, output, logging).
|
|
6
|
+
*/
|
|
7
|
+
import { type AppConfig } from '../../../config/index.js';
|
|
8
|
+
import type { ConfigField } from '../types.js';
|
|
9
|
+
/** A tab/section in the config card. */
|
|
10
|
+
export interface ConfigTab {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
fields: ConfigField[];
|
|
14
|
+
}
|
|
15
|
+
/** Build the full CardKit 2.0 card from tabs. */
|
|
16
|
+
export declare function buildConfigCardFromTabs(tabs: ConfigTab[], displayConfig: AppConfig): object;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared config card rendering logic.
|
|
3
|
+
*
|
|
4
|
+
* Builds CardKit 2.0 elements for config fields (boolean toggle, select dropdown, input).
|
|
5
|
+
* Used by all agent config builders and the shared tabs (idle, output, logging).
|
|
6
|
+
*/
|
|
7
|
+
import { getConfigValue } from '../../../config/index.js';
|
|
8
|
+
/** Build CardKit 2.0 elements for a list of config fields. */
|
|
9
|
+
function buildFieldElements(fields, displayConfig) {
|
|
10
|
+
const elements = [];
|
|
11
|
+
for (const field of fields) {
|
|
12
|
+
// Get current value from displayConfig for dynamic fields
|
|
13
|
+
const currentValue = getConfigValue(displayConfig, field.key);
|
|
14
|
+
if (field.type === 'boolean') {
|
|
15
|
+
// For boolean, use field.currentValue if set, otherwise use config value
|
|
16
|
+
const isOn = field.currentValue !== undefined ? field.currentValue === true : currentValue === true;
|
|
17
|
+
elements.push(buildBooleanElement(field, isOn));
|
|
18
|
+
}
|
|
19
|
+
else if (field.type === 'select' && field.options) {
|
|
20
|
+
elements.push(buildSelectElement(field, currentValue));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
elements.push(buildInputElement(field, currentValue));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return elements;
|
|
27
|
+
}
|
|
28
|
+
function buildBooleanElement(field, isOn) {
|
|
29
|
+
const displayVal = isOn ? '✅ 已开启' : '⚪ 已关闭';
|
|
30
|
+
return {
|
|
31
|
+
tag: 'column_set',
|
|
32
|
+
flex_mode: 'none',
|
|
33
|
+
columns: [
|
|
34
|
+
{
|
|
35
|
+
tag: 'column',
|
|
36
|
+
width: 'weighted',
|
|
37
|
+
weight: 2,
|
|
38
|
+
vertical_align: 'center',
|
|
39
|
+
elements: [{ tag: 'div', text: { tag: 'lark_md', content: field.label } }],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
tag: 'column',
|
|
43
|
+
width: 'weighted',
|
|
44
|
+
weight: 3,
|
|
45
|
+
vertical_align: 'center',
|
|
46
|
+
elements: [
|
|
47
|
+
{
|
|
48
|
+
tag: 'button',
|
|
49
|
+
text: { tag: 'plain_text', content: displayVal },
|
|
50
|
+
type: isOn ? 'primary' : 'default',
|
|
51
|
+
behaviors: [{ type: 'callback', value: { cmd: 'config.toggle', key: field.key } }],
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildSelectElement(field, currentValue) {
|
|
59
|
+
// 优先使用 field.currentValue(卡片显示用覆盖值),否则使用 config 中的值
|
|
60
|
+
const selectedValue = field.currentValue != null
|
|
61
|
+
? String(field.currentValue)
|
|
62
|
+
: currentValue == null
|
|
63
|
+
? undefined
|
|
64
|
+
: String(currentValue);
|
|
65
|
+
return {
|
|
66
|
+
tag: 'column_set',
|
|
67
|
+
flex_mode: 'none',
|
|
68
|
+
columns: [
|
|
69
|
+
{
|
|
70
|
+
tag: 'column',
|
|
71
|
+
width: 'weighted',
|
|
72
|
+
weight: 2,
|
|
73
|
+
vertical_align: 'center',
|
|
74
|
+
elements: [{ tag: 'div', text: { tag: 'lark_md', content: field.label } }],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
tag: 'column',
|
|
78
|
+
width: 'weighted',
|
|
79
|
+
weight: 3,
|
|
80
|
+
vertical_align: 'center',
|
|
81
|
+
elements: [
|
|
82
|
+
{
|
|
83
|
+
tag: 'select_static',
|
|
84
|
+
placeholder: { tag: 'plain_text', content: '请选择' },
|
|
85
|
+
options: field.options.map((opt) => ({
|
|
86
|
+
text: { tag: 'plain_text', content: opt },
|
|
87
|
+
value: opt,
|
|
88
|
+
})),
|
|
89
|
+
initial_option: selectedValue,
|
|
90
|
+
behaviors: [{ type: 'callback', value: { cmd: 'config.set', key: field.key } }],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function buildInputElement(field, currentValue) {
|
|
98
|
+
// 优先使用 field.currentValue(允许覆盖 displayConfig 中的值)
|
|
99
|
+
const currentStr = field.currentValue != null
|
|
100
|
+
? String(field.currentValue)
|
|
101
|
+
: currentValue == null
|
|
102
|
+
? ''
|
|
103
|
+
: String(currentValue);
|
|
104
|
+
return {
|
|
105
|
+
tag: 'column_set',
|
|
106
|
+
flex_mode: 'none',
|
|
107
|
+
columns: [
|
|
108
|
+
{
|
|
109
|
+
tag: 'column',
|
|
110
|
+
width: 'weighted',
|
|
111
|
+
weight: 2,
|
|
112
|
+
vertical_align: 'center',
|
|
113
|
+
elements: [{ tag: 'div', text: { tag: 'lark_md', content: field.label } }],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
tag: 'column',
|
|
117
|
+
width: 'weighted',
|
|
118
|
+
weight: 3,
|
|
119
|
+
vertical_align: 'center',
|
|
120
|
+
elements: [
|
|
121
|
+
{
|
|
122
|
+
tag: 'input',
|
|
123
|
+
name: field.key,
|
|
124
|
+
placeholder: { tag: 'plain_text', content: '请输入值' },
|
|
125
|
+
default_value: currentStr,
|
|
126
|
+
behaviors: [{ type: 'callback', value: { cmd: 'config.input', key: field.key } }],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Build the full CardKit 2.0 card from tabs. */
|
|
134
|
+
export function buildConfigCardFromTabs(tabs, displayConfig) {
|
|
135
|
+
const elements = [];
|
|
136
|
+
for (const tab of tabs) {
|
|
137
|
+
const fieldElements = buildFieldElements(tab.fields, displayConfig);
|
|
138
|
+
// Section header - bold markdown
|
|
139
|
+
elements.push({ tag: 'div', text: { tag: 'lark_md', content: `**${tab.label}**` } });
|
|
140
|
+
// Section fields
|
|
141
|
+
elements.push(...fieldElements);
|
|
142
|
+
elements.push({ tag: 'hr' });
|
|
143
|
+
}
|
|
144
|
+
// 底部保存按钮
|
|
145
|
+
elements.push({
|
|
146
|
+
tag: 'button',
|
|
147
|
+
text: { tag: 'plain_text', content: '💾 保存所有修改' },
|
|
148
|
+
type: 'primary',
|
|
149
|
+
behaviors: [{ type: 'callback', value: { cmd: 'config.save' } }],
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
schema: '2.0',
|
|
153
|
+
config: { wide_screen_mode: true, update_multi: true },
|
|
154
|
+
header: { title: { tag: 'plain_text', content: '⚙️ 系统配置' }, template: 'blue' },
|
|
155
|
+
body: { elements },
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config card builder registry — provides getConfigBuilder(agent) factory.
|
|
3
|
+
*
|
|
4
|
+
* Each agent implements AgentConfigCardBuilder in its own file.
|
|
5
|
+
* Adding a new agent = add a file + register here (no upstream changes needed).
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentKind } from '../../runner/types.js';
|
|
8
|
+
import type { AgentConfigCardBuilder } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Get the config builder for a specific agent.
|
|
11
|
+
*
|
|
12
|
+
* @param agent - The agent kind (claude, codex, pi, opencode, kimi)
|
|
13
|
+
* @returns The config builder instance for that agent
|
|
14
|
+
* @throws Error if the agent is not registered
|
|
15
|
+
*/
|
|
16
|
+
export declare function getConfigBuilder(agent: AgentKind): AgentConfigCardBuilder;
|
|
17
|
+
/** List all registered agent kinds. */
|
|
18
|
+
export declare function listRegisteredAgents(): AgentKind[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config card builder registry — provides getConfigBuilder(agent) factory.
|
|
3
|
+
*
|
|
4
|
+
* Each agent implements AgentConfigCardBuilder in its own file.
|
|
5
|
+
* Adding a new agent = add a file + register here (no upstream changes needed).
|
|
6
|
+
*/
|
|
7
|
+
import { ClaudeConfigBuilder } from './claude.js';
|
|
8
|
+
import { CodexConfigBuilder } from './codex.js';
|
|
9
|
+
import { PiConfigBuilder } from './pi.js';
|
|
10
|
+
import { OpencodeConfigBuilder } from './opencode.js';
|
|
11
|
+
import { KimiConfigBuilder } from './kimi.js';
|
|
12
|
+
/** Registry of config builders, keyed by agent kind. */
|
|
13
|
+
const builders = {
|
|
14
|
+
claude: new ClaudeConfigBuilder(),
|
|
15
|
+
codex: new CodexConfigBuilder(),
|
|
16
|
+
pi: new PiConfigBuilder(),
|
|
17
|
+
opencode: new OpencodeConfigBuilder(),
|
|
18
|
+
kimi: new KimiConfigBuilder(),
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Get the config builder for a specific agent.
|
|
22
|
+
*
|
|
23
|
+
* @param agent - The agent kind (claude, codex, pi, opencode, kimi)
|
|
24
|
+
* @returns The config builder instance for that agent
|
|
25
|
+
* @throws Error if the agent is not registered
|
|
26
|
+
*/
|
|
27
|
+
export function getConfigBuilder(agent) {
|
|
28
|
+
const builder = builders[agent];
|
|
29
|
+
if (!builder) {
|
|
30
|
+
throw new Error(`No config builder registered for agent: ${agent}`);
|
|
31
|
+
}
|
|
32
|
+
return builder;
|
|
33
|
+
}
|
|
34
|
+
/** List all registered agent kinds. */
|
|
35
|
+
export function listRegisteredAgents() {
|
|
36
|
+
return Object.keys(builders);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi config builder — builds fields and handles field changes for Kimi agent.
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentConfigCardBuilder, ConfigField } from './types.js';
|
|
5
|
+
import type { AppConfig } from '../../config/index.js';
|
|
6
|
+
export declare class KimiConfigBuilder implements AgentConfigCardBuilder {
|
|
7
|
+
buildFields(displayConfig: AppConfig): ConfigField[];
|
|
8
|
+
handleFieldChange(key: string, value: unknown, config: AppConfig): Array<{
|
|
9
|
+
key: string;
|
|
10
|
+
value: unknown;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* 切到 newModel 后的档位补丁(参照 codex effortPatchForModel 语义):
|
|
14
|
+
* - 当前档位仍被新模型支持 → 不产出补丁;
|
|
15
|
+
* - 否则优先 modelEfforts[newModel] 中位 (len-1)/2;
|
|
16
|
+
* - 列表为空才用 modelDefaultEfforts[newModel];
|
|
17
|
+
* - 两者皆无 → FALLBACK_EFFORTS 中位;仍无则 undefined(router setNestedValue
|
|
18
|
+
* 对 undefined 做键删除)。
|
|
19
|
+
*/
|
|
20
|
+
private thinkingEffortPatchForModel;
|
|
21
|
+
}
|