larkin 0.2.74 → 0.2.75
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/assets/larkin-mark-template.svg +3 -0
- package/assets/larkin-mark.svg +7 -0
- package/dist/app/official-lark-cli.cjs +88 -20
- package/dist/app/official-lark-cli.mjs +87 -20
- package/dist/runtime/claude-model-catalog.cjs +23 -0
- package/dist/runtime/claude-model-catalog.mjs +23 -0
- package/dist/runtime/codex-model-catalog.cjs +23 -0
- package/dist/runtime/codex-model-catalog.mjs +23 -0
- package/dist/runtime/pi-model-catalog.cjs +24 -0
- package/dist/runtime/pi-model-catalog.mjs +24 -0
- package/dist/runtime/runtime-host.cjs +7 -3
- package/dist/runtime/runtime-host.mjs +7 -3
- package/package.json +2 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">LARKIN</title>
|
|
3
|
+
<desc id="desc">A swift lark built from three folded ribbons.</desc>
|
|
4
|
+
<path fill="#00B8D9" d="M8 35.5C11.2 20.8 22.4 9.8 39.8 6l-9.4 18.2-13.2 15Z"/>
|
|
5
|
+
<path fill="#1456F0" d="M8 35.5c11.8 8.2 24.7 8.7 35.4-.6L56 23.8l-5.1 17.8C39.3 54.4 22.2 56.8 8 47.4Z"/>
|
|
6
|
+
<path fill="#A3402C" d="M13.8 38.2c3.7-10.4 12.1-18.3 25.4-21.7l-7.1 13.8-9.3 11.2c-3.1-.6-6.1-1.7-9-3.3Z"/>
|
|
7
|
+
</svg>
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.OFFICIAL_LARK_CLI_INSTALL = exports.OFFICIAL_LARK_CLI_VERSION = void 0;
|
|
7
|
+
exports.invalidateOfficialLarkCliProbeCache = invalidateOfficialLarkCliProbeCache;
|
|
7
8
|
exports.probeOfficialLarkCli = probeOfficialLarkCli;
|
|
8
9
|
exports.resolveOfficialLarkCli = resolveOfficialLarkCli;
|
|
9
10
|
exports.installOfficialLarkCli = installOfficialLarkCli;
|
|
@@ -65,29 +66,29 @@ function officialPackage(executable) {
|
|
|
65
66
|
}
|
|
66
67
|
return null;
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
/**
|
|
70
|
+
* 进程内缓存(仅生产路径,即未注入 spawn/shell 时生效):登录 shell 探测
|
|
71
|
+
* 是阻塞 spawnSync,且用户的 .zshrc/.zprofile(nvm 等)偶发需要数十秒才能
|
|
72
|
+
* 返回,daemon 热路径上反复探测会把启动拖到分钟级。同一进程内 PATH/SHELL
|
|
73
|
+
* 不变,解析结果也不会变,因此只探测一次。
|
|
74
|
+
*/
|
|
75
|
+
let productionProbeKey = null;
|
|
76
|
+
let productionProbe = null;
|
|
77
|
+
/** setup 安装/升级官方 CLI 后调用,使同一进程内的后续解析重新探测。 */
|
|
78
|
+
function invalidateOfficialLarkCliProbeCache() {
|
|
79
|
+
productionProbeKey = null;
|
|
80
|
+
productionProbe = null;
|
|
81
|
+
}
|
|
82
|
+
/** 对已确认是官方 @larksuite/cli 的入口做版本与 bind 能力验证(shell 与 PATH 两路共用)。 */
|
|
83
|
+
function validateOfficialCommand(command, env, spawn) {
|
|
83
84
|
if (!compatibleVersion(command.version))
|
|
84
85
|
return {
|
|
85
86
|
state: "outdated",
|
|
86
|
-
reason:
|
|
87
|
+
reason: `官方 lark-cli ${command.version} 低于最低兼容版本 ${exports.OFFICIAL_LARK_CLI_VERSION}: ${command.command}`,
|
|
87
88
|
nextAction: `升级:${exports.OFFICIAL_LARK_CLI_INSTALL}`,
|
|
88
89
|
};
|
|
89
|
-
const version =
|
|
90
|
-
encoding: "utf8", env
|
|
90
|
+
const version = spawn(command.command, ["--version"], {
|
|
91
|
+
encoding: "utf8", env,
|
|
91
92
|
});
|
|
92
93
|
if (version.status !== 0 || version.error || !String(version.stdout || "").includes(command.version))
|
|
93
94
|
return {
|
|
@@ -95,8 +96,8 @@ function probeOfficialLarkCli(dependencies = {}) {
|
|
|
95
96
|
reason: `官方 lark-cli 版本执行验证失败: ${command.command}`,
|
|
96
97
|
nextAction: `重新安装:${exports.OFFICIAL_LARK_CLI_INSTALL}`,
|
|
97
98
|
};
|
|
98
|
-
const bindHelp =
|
|
99
|
-
encoding: "utf8", env
|
|
99
|
+
const bindHelp = spawn(command.command, ["config", "bind", "--help"], {
|
|
100
|
+
encoding: "utf8", env,
|
|
100
101
|
});
|
|
101
102
|
const bindText = `${bindHelp.stdout || ""}\n${bindHelp.stderr || ""}`;
|
|
102
103
|
if (bindHelp.status !== 0 || bindHelp.error || !/--source/.test(bindText) || !/lark-channel/.test(bindText) || !/--identity/.test(bindText))
|
|
@@ -107,6 +108,72 @@ function probeOfficialLarkCli(dependencies = {}) {
|
|
|
107
108
|
};
|
|
108
109
|
return { state: "ready", command };
|
|
109
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* PATH 直接解析快路径:daemon(launchd)的 PATH 通常已包含官方 lark-cli,
|
|
113
|
+
* 此时完全不需要拉起登录 shell(这是启动变慢的主要来源之一)。
|
|
114
|
+
* 仅当 PATH 解析出可用的官方 CLI 时才返回结果;否则返回 null 回退到
|
|
115
|
+
* 登录 shell 探测(保留对 shell 自定义 PATH 环境的兼容)。
|
|
116
|
+
*/
|
|
117
|
+
function probePathResolution(env) {
|
|
118
|
+
const dirs = String(env.PATH || "").split(node_path_1.default.delimiter).filter(Boolean);
|
|
119
|
+
for (const dir of dirs) {
|
|
120
|
+
const candidate = node_path_1.default.resolve(dir, "lark-cli");
|
|
121
|
+
try {
|
|
122
|
+
if (!node_fs_1.default.statSync(candidate).isFile())
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const command = officialPackage(candidate);
|
|
129
|
+
if (!command)
|
|
130
|
+
return null;
|
|
131
|
+
return validateOfficialCommand(command, env, node_child_process_1.spawnSync);
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function probeOfficialLarkCliUncached(dependencies) {
|
|
136
|
+
const env = dependencies.env ?? process.env;
|
|
137
|
+
if (!dependencies.spawn && !dependencies.shell) {
|
|
138
|
+
const fromPath = probePathResolution(env);
|
|
139
|
+
if (fromPath)
|
|
140
|
+
return fromPath;
|
|
141
|
+
}
|
|
142
|
+
const executable = loginShellPath(dependencies);
|
|
143
|
+
if (!executable)
|
|
144
|
+
return {
|
|
145
|
+
state: "missing",
|
|
146
|
+
reason: "真实 login shell 找不到官方 lark-cli",
|
|
147
|
+
nextAction: `运行 larkin setup,并确认执行:${exports.OFFICIAL_LARK_CLI_INSTALL}`,
|
|
148
|
+
};
|
|
149
|
+
const command = officialPackage(executable);
|
|
150
|
+
if (!command)
|
|
151
|
+
return {
|
|
152
|
+
state: "conflict",
|
|
153
|
+
reason: `真实 login shell 的 lark-cli 不是兼容的官方 @larksuite/cli ${exports.OFFICIAL_LARK_CLI_VERSION}: ${executable}`,
|
|
154
|
+
nextAction: "移除或调整冲突入口后重新运行 larkin setup",
|
|
155
|
+
};
|
|
156
|
+
if (!compatibleVersion(command.version))
|
|
157
|
+
return {
|
|
158
|
+
state: "outdated",
|
|
159
|
+
reason: `真实 login shell 的官方 @larksuite/cli ${command.version} 低于最低兼容版本 ${exports.OFFICIAL_LARK_CLI_VERSION}`,
|
|
160
|
+
nextAction: `升级:${exports.OFFICIAL_LARK_CLI_INSTALL}`,
|
|
161
|
+
};
|
|
162
|
+
return validateOfficialCommand(command, env, dependencies.spawn ?? node_child_process_1.spawnSync);
|
|
163
|
+
}
|
|
164
|
+
function probeOfficialLarkCli(dependencies = {}) {
|
|
165
|
+
if (!dependencies.spawn && !dependencies.shell) {
|
|
166
|
+
const env = dependencies.env ?? process.env;
|
|
167
|
+
const key = `${env.SHELL || ""}|${env.PATH || ""}`;
|
|
168
|
+
if (productionProbe && productionProbeKey === key)
|
|
169
|
+
return productionProbe;
|
|
170
|
+
const probe = probeOfficialLarkCliUncached({ env });
|
|
171
|
+
productionProbeKey = key;
|
|
172
|
+
productionProbe = probe;
|
|
173
|
+
return probe;
|
|
174
|
+
}
|
|
175
|
+
return probeOfficialLarkCliUncached(dependencies);
|
|
176
|
+
}
|
|
110
177
|
function resolveOfficialLarkCli(dependencies = {}) {
|
|
111
178
|
const result = probeOfficialLarkCli(dependencies);
|
|
112
179
|
if (result.state !== "ready")
|
|
@@ -120,6 +187,7 @@ function installOfficialLarkCli(dependencies = {}) {
|
|
|
120
187
|
});
|
|
121
188
|
if (result.status !== 0 || result.error)
|
|
122
189
|
throw new Error(`官方 lark-cli 安装失败(exit=${result.status ?? "none"})`);
|
|
190
|
+
invalidateOfficialLarkCliProbeCache();
|
|
123
191
|
return resolveOfficialLarkCli(dependencies);
|
|
124
192
|
}
|
|
125
193
|
async function ensureOfficialLarkCliForSetup(input) {
|
|
@@ -55,29 +55,29 @@ function officialPackage(executable) {
|
|
|
55
55
|
}
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
58
|
+
/**
|
|
59
|
+
* 进程内缓存(仅生产路径,即未注入 spawn/shell 时生效):登录 shell 探测
|
|
60
|
+
* 是阻塞 spawnSync,且用户的 .zshrc/.zprofile(nvm 等)偶发需要数十秒才能
|
|
61
|
+
* 返回,daemon 热路径上反复探测会把启动拖到分钟级。同一进程内 PATH/SHELL
|
|
62
|
+
* 不变,解析结果也不会变,因此只探测一次。
|
|
63
|
+
*/
|
|
64
|
+
let productionProbeKey = null;
|
|
65
|
+
let productionProbe = null;
|
|
66
|
+
/** setup 安装/升级官方 CLI 后调用,使同一进程内的后续解析重新探测。 */
|
|
67
|
+
export function invalidateOfficialLarkCliProbeCache() {
|
|
68
|
+
productionProbeKey = null;
|
|
69
|
+
productionProbe = null;
|
|
70
|
+
}
|
|
71
|
+
/** 对已确认是官方 @larksuite/cli 的入口做版本与 bind 能力验证(shell 与 PATH 两路共用)。 */
|
|
72
|
+
function validateOfficialCommand(command, env, spawn) {
|
|
73
73
|
if (!compatibleVersion(command.version))
|
|
74
74
|
return {
|
|
75
75
|
state: "outdated",
|
|
76
|
-
reason:
|
|
76
|
+
reason: `官方 lark-cli ${command.version} 低于最低兼容版本 ${OFFICIAL_LARK_CLI_VERSION}: ${command.command}`,
|
|
77
77
|
nextAction: `升级:${OFFICIAL_LARK_CLI_INSTALL}`,
|
|
78
78
|
};
|
|
79
|
-
const version =
|
|
80
|
-
encoding: "utf8", env
|
|
79
|
+
const version = spawn(command.command, ["--version"], {
|
|
80
|
+
encoding: "utf8", env,
|
|
81
81
|
});
|
|
82
82
|
if (version.status !== 0 || version.error || !String(version.stdout || "").includes(command.version))
|
|
83
83
|
return {
|
|
@@ -85,8 +85,8 @@ export function probeOfficialLarkCli(dependencies = {}) {
|
|
|
85
85
|
reason: `官方 lark-cli 版本执行验证失败: ${command.command}`,
|
|
86
86
|
nextAction: `重新安装:${OFFICIAL_LARK_CLI_INSTALL}`,
|
|
87
87
|
};
|
|
88
|
-
const bindHelp =
|
|
89
|
-
encoding: "utf8", env
|
|
88
|
+
const bindHelp = spawn(command.command, ["config", "bind", "--help"], {
|
|
89
|
+
encoding: "utf8", env,
|
|
90
90
|
});
|
|
91
91
|
const bindText = `${bindHelp.stdout || ""}\n${bindHelp.stderr || ""}`;
|
|
92
92
|
if (bindHelp.status !== 0 || bindHelp.error || !/--source/.test(bindText) || !/lark-channel/.test(bindText) || !/--identity/.test(bindText))
|
|
@@ -97,6 +97,72 @@ export function probeOfficialLarkCli(dependencies = {}) {
|
|
|
97
97
|
};
|
|
98
98
|
return { state: "ready", command };
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* PATH 直接解析快路径:daemon(launchd)的 PATH 通常已包含官方 lark-cli,
|
|
102
|
+
* 此时完全不需要拉起登录 shell(这是启动变慢的主要来源之一)。
|
|
103
|
+
* 仅当 PATH 解析出可用的官方 CLI 时才返回结果;否则返回 null 回退到
|
|
104
|
+
* 登录 shell 探测(保留对 shell 自定义 PATH 环境的兼容)。
|
|
105
|
+
*/
|
|
106
|
+
function probePathResolution(env) {
|
|
107
|
+
const dirs = String(env.PATH || "").split(path.delimiter).filter(Boolean);
|
|
108
|
+
for (const dir of dirs) {
|
|
109
|
+
const candidate = path.resolve(dir, "lark-cli");
|
|
110
|
+
try {
|
|
111
|
+
if (!fs.statSync(candidate).isFile())
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const command = officialPackage(candidate);
|
|
118
|
+
if (!command)
|
|
119
|
+
return null;
|
|
120
|
+
return validateOfficialCommand(command, env, spawnSync);
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
function probeOfficialLarkCliUncached(dependencies) {
|
|
125
|
+
const env = dependencies.env ?? process.env;
|
|
126
|
+
if (!dependencies.spawn && !dependencies.shell) {
|
|
127
|
+
const fromPath = probePathResolution(env);
|
|
128
|
+
if (fromPath)
|
|
129
|
+
return fromPath;
|
|
130
|
+
}
|
|
131
|
+
const executable = loginShellPath(dependencies);
|
|
132
|
+
if (!executable)
|
|
133
|
+
return {
|
|
134
|
+
state: "missing",
|
|
135
|
+
reason: "真实 login shell 找不到官方 lark-cli",
|
|
136
|
+
nextAction: `运行 larkin setup,并确认执行:${OFFICIAL_LARK_CLI_INSTALL}`,
|
|
137
|
+
};
|
|
138
|
+
const command = officialPackage(executable);
|
|
139
|
+
if (!command)
|
|
140
|
+
return {
|
|
141
|
+
state: "conflict",
|
|
142
|
+
reason: `真实 login shell 的 lark-cli 不是兼容的官方 @larksuite/cli ${OFFICIAL_LARK_CLI_VERSION}: ${executable}`,
|
|
143
|
+
nextAction: "移除或调整冲突入口后重新运行 larkin setup",
|
|
144
|
+
};
|
|
145
|
+
if (!compatibleVersion(command.version))
|
|
146
|
+
return {
|
|
147
|
+
state: "outdated",
|
|
148
|
+
reason: `真实 login shell 的官方 @larksuite/cli ${command.version} 低于最低兼容版本 ${OFFICIAL_LARK_CLI_VERSION}`,
|
|
149
|
+
nextAction: `升级:${OFFICIAL_LARK_CLI_INSTALL}`,
|
|
150
|
+
};
|
|
151
|
+
return validateOfficialCommand(command, env, dependencies.spawn ?? spawnSync);
|
|
152
|
+
}
|
|
153
|
+
export function probeOfficialLarkCli(dependencies = {}) {
|
|
154
|
+
if (!dependencies.spawn && !dependencies.shell) {
|
|
155
|
+
const env = dependencies.env ?? process.env;
|
|
156
|
+
const key = `${env.SHELL || ""}|${env.PATH || ""}`;
|
|
157
|
+
if (productionProbe && productionProbeKey === key)
|
|
158
|
+
return productionProbe;
|
|
159
|
+
const probe = probeOfficialLarkCliUncached({ env });
|
|
160
|
+
productionProbeKey = key;
|
|
161
|
+
productionProbe = probe;
|
|
162
|
+
return probe;
|
|
163
|
+
}
|
|
164
|
+
return probeOfficialLarkCliUncached(dependencies);
|
|
165
|
+
}
|
|
100
166
|
export function resolveOfficialLarkCli(dependencies = {}) {
|
|
101
167
|
const result = probeOfficialLarkCli(dependencies);
|
|
102
168
|
if (result.state !== "ready")
|
|
@@ -110,6 +176,7 @@ export function installOfficialLarkCli(dependencies = {}) {
|
|
|
110
176
|
});
|
|
111
177
|
if (result.status !== 0 || result.error)
|
|
112
178
|
throw new Error(`官方 lark-cli 安装失败(exit=${result.status ?? "none"})`);
|
|
179
|
+
invalidateOfficialLarkCliProbeCache();
|
|
113
180
|
return resolveOfficialLarkCli(dependencies);
|
|
114
181
|
}
|
|
115
182
|
export async function ensureOfficialLarkCliForSetup(input) {
|
|
@@ -85,7 +85,30 @@ function parseCatalog(value) {
|
|
|
85
85
|
throw new Error("Claude model catalog has no visible local default");
|
|
86
86
|
return { models, effectiveModel, defaultSupportedReasoningEfforts: efforts(defaultRow || {}) };
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* 进程内缓存:同一 claude 可执行文件的模型目录在进程生命周期内不变,
|
|
90
|
+
* daemon 启动时多个 claude agent 只需探测一次(每次探测都要 spawn 一个
|
|
91
|
+
* claude CLI 进程做 list_models 控制请求)。失败不缓存以便重试;测试注入
|
|
92
|
+
* runClaudeControl 时绕过缓存,保持测试隔离。
|
|
93
|
+
*/
|
|
94
|
+
const discoveryCache = new Map();
|
|
88
95
|
async function discoverClaudeModelCatalog(options = {}) {
|
|
96
|
+
if (!options.runClaudeControl) {
|
|
97
|
+
const key = `${options.command || options.env?.LARKIN_CLAUDE_COMMAND || "claude"}`;
|
|
98
|
+
const cached = discoveryCache.get(key);
|
|
99
|
+
if (cached)
|
|
100
|
+
return cached;
|
|
101
|
+
const pending = discoverClaudeModelCatalogUncached(options);
|
|
102
|
+
discoveryCache.set(key, pending);
|
|
103
|
+
pending.catch(() => {
|
|
104
|
+
if (discoveryCache.get(key) === pending)
|
|
105
|
+
discoveryCache.delete(key);
|
|
106
|
+
});
|
|
107
|
+
return pending;
|
|
108
|
+
}
|
|
109
|
+
return discoverClaudeModelCatalogUncached(options);
|
|
110
|
+
}
|
|
111
|
+
async function discoverClaudeModelCatalogUncached(options) {
|
|
89
112
|
const execute = options.runClaudeControl ?? runClaudeControl;
|
|
90
113
|
const request = { type: "control_request", request_id: "larkin-model-list", request: { subtype: "list_models" } };
|
|
91
114
|
try {
|
|
@@ -82,7 +82,30 @@ function parseCatalog(value) {
|
|
|
82
82
|
throw new Error("Claude model catalog has no visible local default");
|
|
83
83
|
return { models, effectiveModel, defaultSupportedReasoningEfforts: efforts(defaultRow || {}) };
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* 进程内缓存:同一 claude 可执行文件的模型目录在进程生命周期内不变,
|
|
87
|
+
* daemon 启动时多个 claude agent 只需探测一次(每次探测都要 spawn 一个
|
|
88
|
+
* claude CLI 进程做 list_models 控制请求)。失败不缓存以便重试;测试注入
|
|
89
|
+
* runClaudeControl 时绕过缓存,保持测试隔离。
|
|
90
|
+
*/
|
|
91
|
+
const discoveryCache = new Map();
|
|
85
92
|
export async function discoverClaudeModelCatalog(options = {}) {
|
|
93
|
+
if (!options.runClaudeControl) {
|
|
94
|
+
const key = `${options.command || options.env?.LARKIN_CLAUDE_COMMAND || "claude"}`;
|
|
95
|
+
const cached = discoveryCache.get(key);
|
|
96
|
+
if (cached)
|
|
97
|
+
return cached;
|
|
98
|
+
const pending = discoverClaudeModelCatalogUncached(options);
|
|
99
|
+
discoveryCache.set(key, pending);
|
|
100
|
+
pending.catch(() => {
|
|
101
|
+
if (discoveryCache.get(key) === pending)
|
|
102
|
+
discoveryCache.delete(key);
|
|
103
|
+
});
|
|
104
|
+
return pending;
|
|
105
|
+
}
|
|
106
|
+
return discoverClaudeModelCatalogUncached(options);
|
|
107
|
+
}
|
|
108
|
+
async function discoverClaudeModelCatalogUncached(options) {
|
|
86
109
|
const execute = options.runClaudeControl ?? runClaudeControl;
|
|
87
110
|
const request = { type: "control_request", request_id: "larkin-model-list", request: { subtype: "list_models" } };
|
|
88
111
|
try {
|
|
@@ -93,7 +93,30 @@ function parseCatalog(value) {
|
|
|
93
93
|
throw new Error("Codex model catalog has no visible local default");
|
|
94
94
|
return { models, effectiveModel };
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* 进程内缓存:同一 codex 可执行文件的模型目录在进程生命周期内不变,
|
|
98
|
+
* daemon 启动时多个 codex agent 只需探测一次(每次探测都要 spawn 一个
|
|
99
|
+
* codex app-server 进程)。失败不缓存以便重试;测试注入 runCodexAppServer
|
|
100
|
+
* 时绕过缓存,保持测试隔离。
|
|
101
|
+
*/
|
|
102
|
+
const discoveryCache = new Map();
|
|
96
103
|
async function discoverCodexModelCatalog(options = {}) {
|
|
104
|
+
if (!options.runCodexAppServer) {
|
|
105
|
+
const key = `${options.command || options.env?.LARKIN_CODEX_COMMAND || "codex"}`;
|
|
106
|
+
const cached = discoveryCache.get(key);
|
|
107
|
+
if (cached)
|
|
108
|
+
return cached;
|
|
109
|
+
const pending = discoverCodexModelCatalogUncached(options);
|
|
110
|
+
discoveryCache.set(key, pending);
|
|
111
|
+
pending.catch(() => {
|
|
112
|
+
if (discoveryCache.get(key) === pending)
|
|
113
|
+
discoveryCache.delete(key);
|
|
114
|
+
});
|
|
115
|
+
return pending;
|
|
116
|
+
}
|
|
117
|
+
return discoverCodexModelCatalogUncached(options);
|
|
118
|
+
}
|
|
119
|
+
async function discoverCodexModelCatalogUncached(options) {
|
|
97
120
|
const execute = options.runCodexAppServer ?? runCodexAppServer;
|
|
98
121
|
const request = { id: "larkin-model-list", method: "model/list", params: { limit: 100, includeHidden: false } };
|
|
99
122
|
try {
|
|
@@ -90,7 +90,30 @@ function parseCatalog(value) {
|
|
|
90
90
|
throw new Error("Codex model catalog has no visible local default");
|
|
91
91
|
return { models, effectiveModel };
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* 进程内缓存:同一 codex 可执行文件的模型目录在进程生命周期内不变,
|
|
95
|
+
* daemon 启动时多个 codex agent 只需探测一次(每次探测都要 spawn 一个
|
|
96
|
+
* codex app-server 进程)。失败不缓存以便重试;测试注入 runCodexAppServer
|
|
97
|
+
* 时绕过缓存,保持测试隔离。
|
|
98
|
+
*/
|
|
99
|
+
const discoveryCache = new Map();
|
|
93
100
|
export async function discoverCodexModelCatalog(options = {}) {
|
|
101
|
+
if (!options.runCodexAppServer) {
|
|
102
|
+
const key = `${options.command || options.env?.LARKIN_CODEX_COMMAND || "codex"}`;
|
|
103
|
+
const cached = discoveryCache.get(key);
|
|
104
|
+
if (cached)
|
|
105
|
+
return cached;
|
|
106
|
+
const pending = discoverCodexModelCatalogUncached(options);
|
|
107
|
+
discoveryCache.set(key, pending);
|
|
108
|
+
pending.catch(() => {
|
|
109
|
+
if (discoveryCache.get(key) === pending)
|
|
110
|
+
discoveryCache.delete(key);
|
|
111
|
+
});
|
|
112
|
+
return pending;
|
|
113
|
+
}
|
|
114
|
+
return discoverCodexModelCatalogUncached(options);
|
|
115
|
+
}
|
|
116
|
+
async function discoverCodexModelCatalogUncached(options) {
|
|
94
117
|
const execute = options.runCodexAppServer ?? runCodexAppServer;
|
|
95
118
|
const request = { id: "larkin-model-list", method: "model/list", params: { limit: 100, includeHidden: false } };
|
|
96
119
|
try {
|
|
@@ -63,8 +63,32 @@ function findExactPiModel(reference, models) {
|
|
|
63
63
|
return undefined;
|
|
64
64
|
return models.find((model) => piModelId(model) === reference);
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* 进程内缓存:同一个 pi 可执行文件的模型目录在进程生命周期内不变。
|
|
68
|
+
* daemon 启动时 3 个 pi agent 会各发起一次发现探测(每个都 spawn 一个 pi 进程
|
|
69
|
+
* 并做 RPC 握手,约 5s),缓存后只 spawn 一次;失败不缓存以便重试。
|
|
70
|
+
* 测试注入 options.spawn 时绕过缓存,保持测试隔离。
|
|
71
|
+
*/
|
|
72
|
+
const discoveryCache = new Map();
|
|
66
73
|
/** Discover only through Pi's structured RPC protocol; no table parsing or static fallback. */
|
|
67
74
|
async function discoverPiModelCatalog(options) {
|
|
75
|
+
if (!options.spawn) {
|
|
76
|
+
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
77
|
+
const key = `${command}|${options.env?.PI_CODING_AGENT_DIR ?? ""}|${options.agentDir ?? ""}`;
|
|
78
|
+
const cached = discoveryCache.get(key);
|
|
79
|
+
if (cached)
|
|
80
|
+
return cached;
|
|
81
|
+
const pending = discoverPiModelCatalogUncached(options);
|
|
82
|
+
discoveryCache.set(key, pending);
|
|
83
|
+
pending.catch(() => {
|
|
84
|
+
if (discoveryCache.get(key) === pending)
|
|
85
|
+
discoveryCache.delete(key);
|
|
86
|
+
});
|
|
87
|
+
return pending;
|
|
88
|
+
}
|
|
89
|
+
return discoverPiModelCatalogUncached(options);
|
|
90
|
+
}
|
|
91
|
+
async function discoverPiModelCatalogUncached(options) {
|
|
68
92
|
const spawn = options.spawn ?? ((command, args, spawnOptions) => (0, node_child_process_1.spawn)(command, [...args], spawnOptions));
|
|
69
93
|
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
70
94
|
const child = spawn(command, ["--mode", "rpc", "--no-session"], {
|
|
@@ -22,8 +22,32 @@ export function findExactPiModel(reference, models) {
|
|
|
22
22
|
return undefined;
|
|
23
23
|
return models.find((model) => piModelId(model) === reference);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* 进程内缓存:同一个 pi 可执行文件的模型目录在进程生命周期内不变。
|
|
27
|
+
* daemon 启动时 3 个 pi agent 会各发起一次发现探测(每个都 spawn 一个 pi 进程
|
|
28
|
+
* 并做 RPC 握手,约 5s),缓存后只 spawn 一次;失败不缓存以便重试。
|
|
29
|
+
* 测试注入 options.spawn 时绕过缓存,保持测试隔离。
|
|
30
|
+
*/
|
|
31
|
+
const discoveryCache = new Map();
|
|
25
32
|
/** Discover only through Pi's structured RPC protocol; no table parsing or static fallback. */
|
|
26
33
|
export async function discoverPiModelCatalog(options) {
|
|
34
|
+
if (!options.spawn) {
|
|
35
|
+
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
36
|
+
const key = `${command}|${options.env?.PI_CODING_AGENT_DIR ?? ""}|${options.agentDir ?? ""}`;
|
|
37
|
+
const cached = discoveryCache.get(key);
|
|
38
|
+
if (cached)
|
|
39
|
+
return cached;
|
|
40
|
+
const pending = discoverPiModelCatalogUncached(options);
|
|
41
|
+
discoveryCache.set(key, pending);
|
|
42
|
+
pending.catch(() => {
|
|
43
|
+
if (discoveryCache.get(key) === pending)
|
|
44
|
+
discoveryCache.delete(key);
|
|
45
|
+
});
|
|
46
|
+
return pending;
|
|
47
|
+
}
|
|
48
|
+
return discoverPiModelCatalogUncached(options);
|
|
49
|
+
}
|
|
50
|
+
async function discoverPiModelCatalogUncached(options) {
|
|
27
51
|
const spawn = options.spawn ?? ((command, args, spawnOptions) => nodeSpawn(command, [...args], spawnOptions));
|
|
28
52
|
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
29
53
|
const child = spawn(command, ["--mode", "rpc", "--no-session"], {
|
|
@@ -793,10 +793,13 @@ function createRuntimeHost(options) {
|
|
|
793
793
|
let activeCount = 0;
|
|
794
794
|
let recoveringCount = 0;
|
|
795
795
|
const startupFailures = [];
|
|
796
|
-
|
|
796
|
+
// Agents start concurrently: each Agent's runtime session is independent,
|
|
797
|
+
// and serial startup multiplied every per-Agent handshake (login-shell
|
|
798
|
+
// probe + runtime RPC discovery) by the Agent count.
|
|
799
|
+
const startups = configs.map(async (config) => {
|
|
797
800
|
if (managed.has(config.agentId)) {
|
|
798
801
|
activeCount += 1;
|
|
799
|
-
|
|
802
|
+
return;
|
|
800
803
|
}
|
|
801
804
|
const stateStore = options.stateStoreFor?.(config.agentId);
|
|
802
805
|
const persisted = stateStore
|
|
@@ -851,7 +854,8 @@ function createRuntimeHost(options) {
|
|
|
851
854
|
}
|
|
852
855
|
agent.poller = setInterval(() => reconcileExternalConsumption(agent), 250);
|
|
853
856
|
agent.poller.unref?.();
|
|
854
|
-
}
|
|
857
|
+
});
|
|
858
|
+
await Promise.all(startups);
|
|
855
859
|
if (configs.length > 0 && activeCount === 0 && recoveringCount === 0) {
|
|
856
860
|
throw new Error(`No runtime Agent started: ${startupFailures.join("; ")}`);
|
|
857
861
|
}
|
|
@@ -785,10 +785,13 @@ export function createRuntimeHost(options) {
|
|
|
785
785
|
let activeCount = 0;
|
|
786
786
|
let recoveringCount = 0;
|
|
787
787
|
const startupFailures = [];
|
|
788
|
-
|
|
788
|
+
// Agents start concurrently: each Agent's runtime session is independent,
|
|
789
|
+
// and serial startup multiplied every per-Agent handshake (login-shell
|
|
790
|
+
// probe + runtime RPC discovery) by the Agent count.
|
|
791
|
+
const startups = configs.map(async (config) => {
|
|
789
792
|
if (managed.has(config.agentId)) {
|
|
790
793
|
activeCount += 1;
|
|
791
|
-
|
|
794
|
+
return;
|
|
792
795
|
}
|
|
793
796
|
const stateStore = options.stateStoreFor?.(config.agentId);
|
|
794
797
|
const persisted = stateStore
|
|
@@ -843,7 +846,8 @@ export function createRuntimeHost(options) {
|
|
|
843
846
|
}
|
|
844
847
|
agent.poller = setInterval(() => reconcileExternalConsumption(agent), 250);
|
|
845
848
|
agent.poller.unref?.();
|
|
846
|
-
}
|
|
849
|
+
});
|
|
850
|
+
await Promise.all(startups);
|
|
847
851
|
if (configs.length > 0 && activeCount === 0 && recoveringCount === 0) {
|
|
848
852
|
throw new Error(`No runtime Agent started: ${startupFailures.join("; ")}`);
|
|
849
853
|
}
|