larkin 0.4.29 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +5 -9
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +751 -4425
- package/dist/app/agent-cli.mjs +14 -2
- package/dist/app/agent-config.mjs +26 -86
- package/dist/app/binary-entry.mjs +0 -20
- package/dist/app/cli.mjs +6 -22
- package/dist/app/internal-command.cjs +0 -2
- package/dist/app/internal-command.mjs +0 -2
- package/dist/app/runtime-model-directory.mjs +2 -16
- package/dist/app/runtime-process.mjs +0 -1
- package/dist/app/setup.mjs +52 -57
- package/dist/dashboard/dashboard-config-controller.mjs +43 -109
- package/dist/dashboard/dashboard-view-model.mjs +4 -7
- package/dist/dashboard/web/assets/dashboard.css +1 -1
- package/dist/dashboard/web/assets/dashboard.js +15 -15
- package/dist/feishu/host-shell.cjs +1 -2
- package/dist/feishu/host-shell.mjs +1 -2
- package/dist/platform/config.cjs +168 -133
- package/dist/platform/config.mjs +168 -133
- package/dist/runtime/pi-artifact-provenance.mjs +3 -3
- package/dist/runtime/pi-bash-timeout-extension.mjs +4 -5
- package/dist/runtime/pi-compaction-recovery.cjs +45 -15
- package/dist/runtime/pi-compaction-recovery.mjs +43 -14
- package/dist/runtime/pi-extension-api.mjs +1 -0
- package/dist/runtime/pi-model-catalog.cjs +7 -8
- package/dist/runtime/pi-model-catalog.mjs +7 -8
- package/dist/runtime/pi-subagent-injection.mjs +17 -4
- package/dist/runtime/runtime-adapters.mjs +26 -43
- package/dist/runtime/runtime-host.cjs +21 -43
- package/dist/runtime/runtime-host.mjs +22 -44
- package/dist/runtime/runtime-model-catalog.cjs +1 -1
- package/dist/runtime/runtime-model-catalog.mjs +1 -1
- package/dist/runtime/runtime-readiness.cjs +62 -98
- package/dist/runtime/runtime-readiness.mjs +60 -97
- package/dist/runtime/user-runtime.cjs +7 -24
- package/dist/runtime/user-runtime.mjs +7 -23
- package/dist/setup/bot-register.mjs +15 -123
- package/dist/setup/setup-agent-choice.mjs +48 -74
- package/dist/setup/setup-bind.mjs +51 -151
- package/dist/setup/setup-binding.mjs +6 -9
- package/package.json +3 -2
- package/dist/app/pi-auth-cli.mjs +0 -277
- package/dist/platform/process-boundary-trace.cjs +0 -108
- package/dist/runtime/builtin-pi-assets.cjs +0 -117
- package/dist/runtime/builtin-pi-assets.mjs +0 -105
- package/dist/runtime/pi-artifact-provenance.cjs +0 -147
- package/dist/runtime/pi-distribution-label.cjs +0 -6
- package/dist/runtime/pi-distribution-label.mjs +0 -3
- package/dist/runtime/pi-inline-extensions.mjs +0 -78
- package/dist/runtime/pi-official-auth.cjs +0 -594
- package/dist/runtime/pi-official-auth.mjs +0 -544
- package/dist/runtime/pi-profile-migration.cjs +0 -764
- package/dist/runtime/pi-profile-migration.mjs +0 -719
- package/dist/runtime/pi-provider-config.cjs +0 -354
- package/dist/runtime/pi-provider-config.mjs +0 -334
- package/dist/runtime/pi-provider-login.mjs +0 -240
package/dist/platform/config.mjs
CHANGED
|
@@ -5,15 +5,14 @@ import * as path from "node:path";
|
|
|
5
5
|
import { TargetRootLayout, resolveConfigDir as resolveRootConfigDir } from "./root-layout.mjs";
|
|
6
6
|
import { exactMode, fsyncDirectoryOf } from "./secure-metadata.mjs";
|
|
7
7
|
import { CURRENT_RUNTIME_MODELS } from "../runtime/runtime-model-catalog.mjs";
|
|
8
|
-
import { assertBuiltinPiAgentDirectory, piAgentDirectory } from "../runtime/pi-provider-config.mjs";
|
|
9
8
|
import { fromUserRuntime, isAdapterRuntime, isUserRuntime, toUserRuntime } from "../runtime/user-runtime.mjs";
|
|
10
9
|
import processInspect from "./process-inspect.cjs";
|
|
11
|
-
import { applyPiProfileMigration, assertPiProfileMigrationAfterState, preparePiProfileMigration, releasePiProfileMigrationLock, rollbackPiProfileMigration, validatePiProfileMigrationState, } from "../runtime/pi-profile-migration.mjs";
|
|
12
10
|
export { RUNTIME_OPTIONS, fromUserRuntime, isAdapterRuntime, isUserRuntime, runtimeOptionOf, runtimeOptionTarget, toUserRuntime, } from "../runtime/user-runtime.mjs";
|
|
13
11
|
const TOP_FIELDS_V3 = new Set(["version", "serverId", "activeAgent", "agents"]);
|
|
14
12
|
const TOP_FIELDS_V4 = new Set(["version", "serverId", "mentionPolicy", "activeAgent", "agents"]);
|
|
15
13
|
const AGENT_FIELDS_V3 = new Set(["runtime", "model", "effort", "noMentionChats", "createdAt"]);
|
|
16
|
-
const AGENT_FIELDS_V4 = new Set(["runtime", "model", "
|
|
14
|
+
const AGENT_FIELDS_V4 = new Set(["runtime", "model", "effort", "mentionPolicy", "chatMentionPolicies", "createdAt"]);
|
|
15
|
+
const LEGACY_AGENT_FIELDS = new Set(["piDistribution", "runtimeOption"]);
|
|
17
16
|
const APP_ID = /^cli_[A-Za-z0-9]+$/;
|
|
18
17
|
const CHAT_ID = /^oc_[A-Za-z0-9_-]+$/;
|
|
19
18
|
const PI_EFFORTS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
@@ -91,31 +90,51 @@ function runtimeSupportsEffort(runtime, effort) {
|
|
|
91
90
|
: runtime === "codex" ? CODEX_EFFORTS.has(effort)
|
|
92
91
|
: runtime === "claude" ? CLAUDE_EFFORTS.has(effort) : false;
|
|
93
92
|
}
|
|
93
|
+
function takeLegacyAgentFields(agent) {
|
|
94
|
+
const cleaned = {};
|
|
95
|
+
for (const [field, value] of Object.entries(agent)) {
|
|
96
|
+
if (!LEGACY_AGENT_FIELDS.has(field))
|
|
97
|
+
cleaned[field] = value;
|
|
98
|
+
}
|
|
99
|
+
return cleaned;
|
|
100
|
+
}
|
|
101
|
+
function isLegacyBuiltinAgentRecord(agent) {
|
|
102
|
+
if (!isPlainObject(agent))
|
|
103
|
+
return false;
|
|
104
|
+
return agent.piDistribution === "builtin" || agent.runtime === "builtin-pi" || agent.runtimeOption === "builtin-pi";
|
|
105
|
+
}
|
|
106
|
+
function canonicalizeLegacyStoredAgent(key, agent) {
|
|
107
|
+
const cleaned = takeLegacyAgentFields(agent);
|
|
108
|
+
if (cleaned.runtime === "builtin-pi" || isLegacyBuiltinAgentRecord(agent))
|
|
109
|
+
cleaned.runtime = "pi";
|
|
110
|
+
if (typeof cleaned.runtime !== "string" || !cleaned.runtime)
|
|
111
|
+
throw new Error(`Agent ${key}.runtime 必须是非空字符串`);
|
|
112
|
+
return cleaned;
|
|
113
|
+
}
|
|
94
114
|
function validateStoredAgent(key, agent, version) {
|
|
95
115
|
if (!APP_ID.test(key))
|
|
96
116
|
throw new Error(`Agent key 必须是安全的飞书 App ID(cli_ + ASCII 字母数字):${key}`);
|
|
97
117
|
if (!isPlainObject(agent))
|
|
98
118
|
throw new Error(`Agent ${key} 必须是普通 object`);
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
const canonical = canonicalizeLegacyStoredAgent(key, agent);
|
|
120
|
+
assertAllowedFields(canonical, version === 3 ? AGENT_FIELDS_V3 : AGENT_FIELDS_V4, `Agent ${key}`);
|
|
121
|
+
if (typeof canonical.runtime !== "string" || !canonical.runtime)
|
|
101
122
|
throw new Error(`Agent ${key}.runtime 必须是非空字符串`);
|
|
102
|
-
if (!isAdapterRuntime(
|
|
103
|
-
throw new Error(`Agent ${key}.runtime 未知:${
|
|
104
|
-
if (typeof
|
|
123
|
+
if (!isAdapterRuntime(canonical.runtime))
|
|
124
|
+
throw new Error(`Agent ${key}.runtime 未知:${canonical.runtime}`);
|
|
125
|
+
if (typeof canonical.model !== "string" || !canonical.model)
|
|
105
126
|
throw new Error(`Agent ${key}.model 必须是非空字符串`);
|
|
106
|
-
assertModel(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
}
|
|
127
|
+
assertModel(canonical.runtime, canonical.model);
|
|
128
|
+
Object.assign(agent, canonical);
|
|
129
|
+
for (const field of LEGACY_AGENT_FIELDS)
|
|
130
|
+
delete agent[field];
|
|
112
131
|
if (Object.hasOwn(agent, "effort") && (typeof agent.effort !== "string" || !agent.effort))
|
|
113
132
|
throw new Error(`Agent ${key}.effort 必须是非空字符串`);
|
|
114
133
|
if (agent.model === "default" && Object.hasOwn(agent, "effort"))
|
|
115
134
|
throw new Error(`Agent ${key}.model=default 时不能保存 effort`);
|
|
116
135
|
if (typeof agent.effort === "string") {
|
|
117
|
-
if (!runtimeSupportsEffort(
|
|
118
|
-
throw new Error(`Agent ${key}.effort=${agent.effort} 不在 ${
|
|
136
|
+
if (!runtimeSupportsEffort(canonical.runtime, agent.effort))
|
|
137
|
+
throw new Error(`Agent ${key}.effort=${agent.effort} 不在 ${canonical.runtime} 安全档位中`);
|
|
119
138
|
}
|
|
120
139
|
if (version === 3 && Object.hasOwn(agent, "noMentionChats")) {
|
|
121
140
|
if (!Array.isArray(agent.noMentionChats) || agent.noMentionChats.some((chat) => typeof chat !== "string" || !CHAT_ID.test(chat))) {
|
|
@@ -150,7 +169,6 @@ function hydratedStoredAgent(key, agent, version) {
|
|
|
150
169
|
return {
|
|
151
170
|
runtime: agent.runtime,
|
|
152
171
|
model: agent.model,
|
|
153
|
-
...(agent.piDistribution === "external" || agent.piDistribution === "builtin" ? { piDistribution: agent.piDistribution } : {}),
|
|
154
172
|
...(typeof agent.effort === "string" ? { effort: agent.effort } : {}),
|
|
155
173
|
...(version === 4 && (agent.mentionPolicy === "require" || agent.mentionPolicy === "free") ? { mentionPolicy: agent.mentionPolicy } : {}),
|
|
156
174
|
...(Object.keys(chatMentionPolicies).length ? { chatMentionPolicies, noMentionChats: Object.entries(chatMentionPolicies).filter(([, policy]) => policy === "free").map(([chatId]) => chatId) } : {}),
|
|
@@ -162,7 +180,6 @@ export function hydrateAgent(key, agent, configDir) {
|
|
|
162
180
|
return {
|
|
163
181
|
name: key, agentId: key, feishuAppId: key, feishuProfile: key,
|
|
164
182
|
runtime: agent.runtime, model: agent.model,
|
|
165
|
-
...(agent.piDistribution ? { piDistribution: agent.piDistribution } : {}),
|
|
166
183
|
workspaceDir: layout.workspaceDir(key), stateDir: layout.agentStateDir(key),
|
|
167
184
|
larkConfigDir: path.join(layout.agentStateDir(key), "lark-cli-config"),
|
|
168
185
|
...(agent.effort ? { effort: agent.effort } : {}),
|
|
@@ -283,9 +300,137 @@ function readConfigFile(file, root = path.dirname(file)) {
|
|
|
283
300
|
throw error;
|
|
284
301
|
}
|
|
285
302
|
}
|
|
303
|
+
function collectLegacyBuiltinAgentIds(raw) {
|
|
304
|
+
if (!isPlainObject(raw) || !isPlainObject(raw.agents))
|
|
305
|
+
return [];
|
|
306
|
+
return Object.entries(raw.agents).filter(([, agent]) => isLegacyBuiltinAgentRecord(agent)).map(([agentId]) => agentId);
|
|
307
|
+
}
|
|
308
|
+
function ownedPiProviderDirectory(configDir, agentId) {
|
|
309
|
+
if (!APP_ID.test(agentId))
|
|
310
|
+
throw new Error(`Pi Agent ID 格式无效:${agentId}`);
|
|
311
|
+
return path.join(path.resolve(configDir), "providers", "pi", agentId);
|
|
312
|
+
}
|
|
313
|
+
function userPiHome(env) {
|
|
314
|
+
const home = typeof env.HOME === "string" && env.HOME.trim() ? env.HOME : os.homedir();
|
|
315
|
+
return path.resolve(home, ".pi");
|
|
316
|
+
}
|
|
317
|
+
function isInsideOrEqual(candidate, root) {
|
|
318
|
+
const relative = path.relative(root, candidate);
|
|
319
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
320
|
+
}
|
|
321
|
+
function pathExists(target) {
|
|
322
|
+
try {
|
|
323
|
+
fs.lstatSync(target);
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
if (error.code === "ENOENT")
|
|
328
|
+
return false;
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function shouldSkipOwnedPiDeletion(configDir, expected, env) {
|
|
333
|
+
let userPi;
|
|
334
|
+
try {
|
|
335
|
+
userPi = fs.realpathSync(userPiHome(env));
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
userPi = path.resolve(userPiHome(env));
|
|
339
|
+
}
|
|
340
|
+
let configRoot;
|
|
341
|
+
try {
|
|
342
|
+
configRoot = fs.realpathSync(path.resolve(configDir));
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
configRoot = path.resolve(configDir);
|
|
346
|
+
}
|
|
347
|
+
let resolvedExpected;
|
|
348
|
+
try {
|
|
349
|
+
resolvedExpected = fs.realpathSync(path.resolve(expected));
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
resolvedExpected = path.resolve(expected);
|
|
353
|
+
}
|
|
354
|
+
if (isInsideOrEqual(configRoot, userPi))
|
|
355
|
+
return "config root is inside the user ~/.pi directory";
|
|
356
|
+
if (isInsideOrEqual(resolvedExpected, userPi))
|
|
357
|
+
return "owned Pi directory is inside the user ~/.pi directory";
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
function deleteOwnedPiProviderDirectory(configDir, agentId, env = process.env) {
|
|
361
|
+
const expected = ownedPiProviderDirectory(configDir, agentId);
|
|
362
|
+
const skip = shouldSkipOwnedPiDeletion(configDir, expected, env);
|
|
363
|
+
if (skip) {
|
|
364
|
+
process.stderr.write(`[larkin] skipped leftover builtin Pi directory for ${agentId}: ${skip}\n`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const parent = path.dirname(expected);
|
|
368
|
+
if (!pathExists(parent) || !pathExists(expected))
|
|
369
|
+
return;
|
|
370
|
+
assertNoSymlinkAncestors(expected);
|
|
371
|
+
const rootReal = fs.realpathSync(path.resolve(configDir));
|
|
372
|
+
const parentReal = fs.realpathSync(parent);
|
|
373
|
+
const relative = path.relative(rootReal, parentReal);
|
|
374
|
+
if (relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative)) {
|
|
375
|
+
throw new Error("owned Pi provider directory escaped the config root");
|
|
376
|
+
}
|
|
377
|
+
if (path.basename(parent) !== "pi" || path.basename(path.dirname(parent)) !== "providers") {
|
|
378
|
+
throw new Error("owned Pi provider directory is not providers/pi/<agentId>");
|
|
379
|
+
}
|
|
380
|
+
const stat = fs.lstatSync(expected);
|
|
381
|
+
if (stat.isSymbolicLink())
|
|
382
|
+
throw new Error("owned Pi provider directory is a symlink");
|
|
383
|
+
if (!stat.isDirectory())
|
|
384
|
+
throw new Error("owned Pi provider directory must be a directory");
|
|
385
|
+
fs.rmSync(expected, { recursive: true, force: false });
|
|
386
|
+
}
|
|
387
|
+
function rewriteLegacyBuiltinAgents(raw) {
|
|
388
|
+
if (!isPlainObject(raw) || !isPlainObject(raw.agents))
|
|
389
|
+
return raw;
|
|
390
|
+
const legacyIds = new Set(collectLegacyBuiltinAgentIds(raw));
|
|
391
|
+
if (legacyIds.size === 0)
|
|
392
|
+
return raw;
|
|
393
|
+
const agents = {};
|
|
394
|
+
for (const [agentId, agent] of Object.entries(raw.agents)) {
|
|
395
|
+
if (!legacyIds.has(agentId) || !isPlainObject(agent)) {
|
|
396
|
+
agents[agentId] = agent;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const next = takeLegacyAgentFields(agent);
|
|
400
|
+
if (next.runtime === "builtin-pi" || isLegacyBuiltinAgentRecord(agent))
|
|
401
|
+
next.runtime = "pi";
|
|
402
|
+
agents[agentId] = next;
|
|
403
|
+
}
|
|
404
|
+
return { ...raw, agents };
|
|
405
|
+
}
|
|
406
|
+
function persistLegacyBuiltinMigration(layout, opts, env) {
|
|
407
|
+
const current = readConfigFile(layout.configFile, layout.root);
|
|
408
|
+
const migratedIds = collectLegacyBuiltinAgentIds(current.raw);
|
|
409
|
+
if (migratedIds.length === 0)
|
|
410
|
+
return current;
|
|
411
|
+
const rewritten = rewriteLegacyBuiltinAgents(current.raw);
|
|
412
|
+
const config = normalizeConfig(structuredClone(rewritten), layout.root, opts);
|
|
413
|
+
const bytes = atomicWriteConfig(layout.configFile, rewritten);
|
|
414
|
+
for (const agentId of migratedIds) {
|
|
415
|
+
const agent = config.agents[agentId];
|
|
416
|
+
const model = typeof agent?.model === "string" ? agent.model : "unknown";
|
|
417
|
+
process.stderr.write(`[larkin] migrated Agent ${agentId} from builtin-pi to pi (model=${model})\n`);
|
|
418
|
+
try {
|
|
419
|
+
deleteOwnedPiProviderDirectory(layout.root, agentId, env);
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
423
|
+
process.stderr.write(`[larkin] failed to delete leftover builtin Pi directory for ${agentId}: ${reason}\n`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return { raw: rewritten, bytes };
|
|
427
|
+
}
|
|
286
428
|
export function loadConfig(env = process.env, opts = {}) {
|
|
287
429
|
const layout = TargetRootLayout.fromConfigDir(resolveConfigDir(env));
|
|
288
|
-
const
|
|
430
|
+
const initial = readConfigFile(layout.configFile, layout.root);
|
|
431
|
+
const { raw, bytes } = collectLegacyBuiltinAgentIds(initial.raw).length
|
|
432
|
+
? withConfigLock(layout, () => persistLegacyBuiltinMigration(layout, opts, env))
|
|
433
|
+
: initial;
|
|
289
434
|
return { configDir: layout.configDir, file: layout.configFile, revision: revision(bytes), config: normalizeConfig(raw, layout.root, opts) };
|
|
290
435
|
}
|
|
291
436
|
export function selectAgent(config, env = process.env) {
|
|
@@ -306,8 +451,6 @@ export function toStored(config) {
|
|
|
306
451
|
const out = { version: 4, serverId: config.serverId, mentionPolicy: config.mentionPolicy, activeAgent: config.activeAgent, agents: {} };
|
|
307
452
|
for (const [key, agent] of Object.entries(config.agents || {})) {
|
|
308
453
|
const stored = { runtime: agent.runtime, model: agent.model };
|
|
309
|
-
if (agent.piDistribution)
|
|
310
|
-
stored.piDistribution = agent.piDistribution;
|
|
311
454
|
if (typeof agent.effort === "string" && agent.effort)
|
|
312
455
|
stored.effort = agent.effort;
|
|
313
456
|
if (agent.mentionPolicy)
|
|
@@ -349,7 +492,8 @@ function agentSignature(config, agentId) {
|
|
|
349
492
|
throw new Error(`Agent 不存在:${agentId}`);
|
|
350
493
|
const chats = Object.fromEntries(Object.entries(agent.chatMentionPolicies || {}).sort(([left], [right]) => left.localeCompare(right)));
|
|
351
494
|
return revision(JSON.stringify({
|
|
352
|
-
|
|
495
|
+
schema: 2,
|
|
496
|
+
runtime: agent.runtime, model: agent.model, effort: agent.effort ?? null,
|
|
353
497
|
globalMentionPolicy: config.mentionPolicy, agentMentionPolicy: agent.mentionPolicy ?? null, chatMentionPolicies: chats,
|
|
354
498
|
}));
|
|
355
499
|
}
|
|
@@ -386,12 +530,6 @@ function writeApplyFile(configDir, value) {
|
|
|
386
530
|
atomicWriteConfig(applyStateFile(configDir), value);
|
|
387
531
|
}
|
|
388
532
|
function rollbackJournalFile(root) { return path.join(root, CONFIG_ROLLBACK_JOURNAL_FILE); }
|
|
389
|
-
function migrationJournalFile(root) { return path.join(root, ".pi-profile-migration-journal.json"); }
|
|
390
|
-
function assertMigrationTarget(root, agentId, migration) {
|
|
391
|
-
if (migration.agentId !== agentId || path.resolve(migration.targetDir) !== path.join(path.resolve(root), "providers", "pi", agentId)) {
|
|
392
|
-
throw new Error("Pi profile migration target is invalid");
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
533
|
function restoreTargetApplyState(root, targetAgentId, beforeRevision, before) {
|
|
396
534
|
const current = readApplyFile(root);
|
|
397
535
|
const agents = { ...current.agents };
|
|
@@ -430,15 +568,9 @@ function recoverRollbackJournal(root) {
|
|
|
430
568
|
const beforeRaw = JSON.parse(beforeBytes.toString("utf8"));
|
|
431
569
|
normalizeConfig(beforeRaw, root);
|
|
432
570
|
validateApplyState(journal.beforeApplyState);
|
|
433
|
-
if (journal.migration !== undefined) {
|
|
434
|
-
validatePiProfileMigrationState(journal.migration);
|
|
435
|
-
assertMigrationTarget(root, journal.targetAgentId, journal.migration);
|
|
436
|
-
}
|
|
437
571
|
const current = readConfigFile(path.join(root, "config.json"), root);
|
|
438
572
|
const currentRevision = revision(current.bytes);
|
|
439
573
|
if (currentRevision === journal.expectedAfterRevision) {
|
|
440
|
-
if (journal.migration)
|
|
441
|
-
rollbackPiProfileMigration(journal.migration);
|
|
442
574
|
atomicWriteBytes(path.join(root, "config.json"), beforeBytes);
|
|
443
575
|
restoreTargetApplyState(root, journal.targetAgentId, journal.beforeRevision, journal.beforeApplyState);
|
|
444
576
|
}
|
|
@@ -453,47 +585,6 @@ function recoverRollbackJournal(root) {
|
|
|
453
585
|
fs.unlinkSync(file);
|
|
454
586
|
fsyncDirectoryOf(file);
|
|
455
587
|
}
|
|
456
|
-
function recoverMigrationJournal(root) {
|
|
457
|
-
const file = migrationJournalFile(root);
|
|
458
|
-
const bytes = readPrivateFile(file, root, PROFILE_MIGRATION_ROLLBACK_LIMIT_BYTES, "Pi profile migration journal");
|
|
459
|
-
if (bytes === null)
|
|
460
|
-
return;
|
|
461
|
-
let journal;
|
|
462
|
-
try {
|
|
463
|
-
journal = JSON.parse(bytes.toString("utf8"));
|
|
464
|
-
}
|
|
465
|
-
catch {
|
|
466
|
-
throw new Error("Pi profile migration journal is invalid");
|
|
467
|
-
}
|
|
468
|
-
if (journal.version !== 1 || journal.phase !== "forward" || typeof journal.targetAgentId !== "string"
|
|
469
|
-
|| typeof journal.beforeRevision !== "string" || typeof journal.expectedAfterRevision !== "string" || !journal.migration) {
|
|
470
|
-
throw new Error("Pi profile migration journal is invalid");
|
|
471
|
-
}
|
|
472
|
-
validatePiProfileMigrationState(journal.migration);
|
|
473
|
-
assertMigrationTarget(root, journal.targetAgentId, journal.migration);
|
|
474
|
-
const current = readConfigFile(path.join(root, "config.json"), root);
|
|
475
|
-
const currentRevision = revision(current.bytes);
|
|
476
|
-
if (currentRevision === journal.beforeRevision) {
|
|
477
|
-
// Forward import did not publish config. The migration helper restores only
|
|
478
|
-
// files still matching its expected post-import hashes.
|
|
479
|
-
try {
|
|
480
|
-
rollbackPiProfileMigration(journal.migration);
|
|
481
|
-
}
|
|
482
|
-
catch (error) {
|
|
483
|
-
throw new Error(`Pi profile migration recovery refused: ${error instanceof Error ? error.message : String(error)}`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
else if (currentRevision === journal.expectedAfterRevision) {
|
|
487
|
-
// Config won the race; verify the provider target before finalizing.
|
|
488
|
-
const migration = journal.migration;
|
|
489
|
-
assertPiProfileMigrationAfterState(migration);
|
|
490
|
-
}
|
|
491
|
-
else
|
|
492
|
-
throw new Error("Pi profile migration journal conflicts with the current configuration");
|
|
493
|
-
releasePiProfileMigrationLock(journal.migration);
|
|
494
|
-
fs.unlinkSync(file);
|
|
495
|
-
fsyncDirectoryOf(file);
|
|
496
|
-
}
|
|
497
588
|
export function configApplyState(env, config) {
|
|
498
589
|
let state;
|
|
499
590
|
try {
|
|
@@ -570,38 +661,16 @@ function applyMutation(config, mutation) {
|
|
|
570
661
|
agent.noMentionChats = Object.entries(policies).filter(([, value]) => value === "free").map(([chatId]) => chatId);
|
|
571
662
|
return { scope: "chat", agentId: mutation.agentId, runtimeChange: false, affectedAgentIds: [mutation.agentId] };
|
|
572
663
|
}
|
|
573
|
-
if (mutation.kind === "set-agent-
|
|
574
|
-
if (agent.runtime !== "pi")
|
|
575
|
-
throw new Error(`Agent ${mutation.agentId} 不是 Pi runtime`);
|
|
576
|
-
if (mutation.distribution !== "builtin" && mutation.distribution !== "external")
|
|
577
|
-
throw new Error("Pi distribution 只允许 builtin/external");
|
|
578
|
-
agent.piDistribution = mutation.distribution;
|
|
579
|
-
}
|
|
580
|
-
else if (mutation.kind === "set-agent-runtime") {
|
|
664
|
+
if (mutation.kind === "set-agent-runtime") {
|
|
581
665
|
if (!isUserRuntime(mutation.runtime))
|
|
582
666
|
throw new Error(`未知 runtime:${mutation.runtime}`);
|
|
583
667
|
const stored = fromUserRuntime(mutation.runtime);
|
|
584
|
-
if (stored.piDistribution === "builtin") {
|
|
585
|
-
try {
|
|
586
|
-
assertBuiltinPiAgentDirectory(piAgentDirectory(config.configDir, mutation.agentId));
|
|
587
|
-
}
|
|
588
|
-
catch (error) {
|
|
589
|
-
throw new Error(`无法切换到 builtin-pi:${error instanceof Error ? error.message : String(error)}。请使用 Dashboard Provider Credentials 或 larkin pi-auth login。`);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
668
|
assertModel(stored.runtime, mutation.model || "default");
|
|
593
669
|
agent.runtime = stored.runtime;
|
|
594
670
|
agent.model = mutation.model || "default";
|
|
595
|
-
if (stored.piDistribution)
|
|
596
|
-
agent.piDistribution = stored.piDistribution;
|
|
597
|
-
else
|
|
598
|
-
delete agent.piDistribution;
|
|
599
671
|
delete agent.effort;
|
|
600
672
|
}
|
|
601
673
|
else if (mutation.kind === "set-agent-model") {
|
|
602
|
-
if (mutation.requireBuiltinPi && (agent.runtime !== "pi" || agent.piDistribution !== "builtin")) {
|
|
603
|
-
throw new Error(`Agent ${mutation.agentId} 不是内置 Pi`);
|
|
604
|
-
}
|
|
605
674
|
if (!mutation.model.trim())
|
|
606
675
|
throw new Error("model 不能为空");
|
|
607
676
|
assertModel(agent.runtime, mutation.model);
|
|
@@ -807,7 +876,6 @@ function acquireConfigLock(layout) {
|
|
|
807
876
|
function withConfigLock(layout, action) {
|
|
808
877
|
const held = acquireConfigLock(layout);
|
|
809
878
|
try {
|
|
810
|
-
recoverMigrationJournal(layout.root);
|
|
811
879
|
recoverRollbackJournal(layout.root);
|
|
812
880
|
return action();
|
|
813
881
|
}
|
|
@@ -822,7 +890,6 @@ export async function withConfigMutationLockAsync(env, action) {
|
|
|
822
890
|
const layout = TargetRootLayout.fromConfigDir(resolveConfigDir(env));
|
|
823
891
|
const held = acquireConfigLock(layout);
|
|
824
892
|
try {
|
|
825
|
-
recoverMigrationJournal(layout.root);
|
|
826
893
|
recoverRollbackJournal(layout.root);
|
|
827
894
|
return await action();
|
|
828
895
|
}
|
|
@@ -922,10 +989,6 @@ function readConfigSnapshot(file, root) {
|
|
|
922
989
|
if (JSON.stringify(beforeRaw) !== JSON.stringify(parsed.beforeConfig))
|
|
923
990
|
throw new Error("config snapshot before config does not match bytes");
|
|
924
991
|
validateApplyState(parsed.beforeApplyState);
|
|
925
|
-
if (parsed.migration !== undefined) {
|
|
926
|
-
validatePiProfileMigrationState(parsed.migration);
|
|
927
|
-
assertMigrationTarget(root, parsed.targetAgentId, parsed.migration);
|
|
928
|
-
}
|
|
929
992
|
return parsed;
|
|
930
993
|
}
|
|
931
994
|
function atomicWriteBytes(file, bytes, limit = CONFIG_LIMIT_BYTES) {
|
|
@@ -969,12 +1032,6 @@ export function mutateConfig(env, mutation, authority, options = {}) {
|
|
|
969
1032
|
throw new Error(`没找到配置 ${layout.configFile},先跑 larkin setup`);
|
|
970
1033
|
const config = normalizeConfig(current.raw, layout.root);
|
|
971
1034
|
const priorSignatures = Object.fromEntries(Object.keys(config.agents).map((agentId) => [agentId, agentSignature(config, agentId)]));
|
|
972
|
-
if (options.importExternalProfile && (mutation.kind !== "set-agent-pi-distribution" || mutation.distribution !== "builtin")) {
|
|
973
|
-
throw new Error("external Pi profile import is only valid for builtin Pi distribution");
|
|
974
|
-
}
|
|
975
|
-
const migrationPlan = options.importExternalProfile && mutation.kind === "set-agent-pi-distribution"
|
|
976
|
-
? preparePiProfileMigration(env, layout.root, mutation.agentId, "builtin")
|
|
977
|
-
: undefined;
|
|
978
1035
|
const changed = applyMutation(config, mutation);
|
|
979
1036
|
const stored = toStored(config);
|
|
980
1037
|
normalizeConfig(stored, layout.root);
|
|
@@ -995,16 +1052,8 @@ export function mutateConfig(env, mutation, authority, options = {}) {
|
|
|
995
1052
|
version: 2, targetAgentId: changed.agentId, beforeRevision: revision(current.bytes), afterRevision: nextRevision,
|
|
996
1053
|
afterSignature: agentSignature(config, changed.agentId), beforeConfig: current.raw,
|
|
997
1054
|
beforeConfigBytes: current.bytes.toString("base64"), beforeApplyState,
|
|
998
|
-
...(migrationPlan ? { migration: migrationPlan.state } : {}),
|
|
999
1055
|
});
|
|
1000
1056
|
}
|
|
1001
|
-
if (migrationPlan) {
|
|
1002
|
-
atomicWriteConfig(migrationJournalFile(layout.root), {
|
|
1003
|
-
version: 1, phase: "forward", targetAgentId: changed.agentId, beforeRevision: revision(current.bytes),
|
|
1004
|
-
expectedAfterRevision: nextRevision, migration: migrationPlan.state,
|
|
1005
|
-
}, PROFILE_MIGRATION_ROLLBACK_LIMIT_BYTES);
|
|
1006
|
-
applyPiProfileMigration(migrationPlan);
|
|
1007
|
-
}
|
|
1008
1057
|
atomicWriteConfig(layout.configFile, stored);
|
|
1009
1058
|
let fullyApplied = changed.affectedAgentIds.length === 0;
|
|
1010
1059
|
try {
|
|
@@ -1028,17 +1077,6 @@ export function mutateConfig(env, mutation, authority, options = {}) {
|
|
|
1028
1077
|
catch {
|
|
1029
1078
|
fullyApplied = false; /* Config persistence remains authoritative. */
|
|
1030
1079
|
}
|
|
1031
|
-
if (migrationPlan) {
|
|
1032
|
-
releasePiProfileMigrationLock(migrationPlan.state);
|
|
1033
|
-
try {
|
|
1034
|
-
fs.unlinkSync(migrationJournalFile(layout.root));
|
|
1035
|
-
fsyncDirectoryOf(migrationJournalFile(layout.root));
|
|
1036
|
-
}
|
|
1037
|
-
catch (error) {
|
|
1038
|
-
if (error.code !== "ENOENT")
|
|
1039
|
-
throw error;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
1080
|
return {
|
|
1043
1081
|
revision: nextRevision, previousRevision: revision(current.bytes), changedScope: changed.scope,
|
|
1044
1082
|
...(changed.agentId ? { agentId: changed.agentId } : {}), persisted: true,
|
|
@@ -1062,11 +1100,8 @@ export function rollbackConfig(env, snapshotFile) {
|
|
|
1062
1100
|
const journal = {
|
|
1063
1101
|
version: 1, targetAgentId: snapshot.targetAgentId, expectedAfterRevision: snapshot.afterRevision,
|
|
1064
1102
|
beforeRevision: snapshot.beforeRevision, beforeConfigBytes: snapshot.beforeConfigBytes, beforeApplyState: snapshot.beforeApplyState,
|
|
1065
|
-
...(snapshot.migration ? { migration: snapshot.migration } : {}),
|
|
1066
1103
|
};
|
|
1067
1104
|
atomicWriteConfig(rollbackJournalFile(layout.root), journal, PROFILE_MIGRATION_ROLLBACK_LIMIT_BYTES);
|
|
1068
|
-
if (snapshot.migration)
|
|
1069
|
-
rollbackPiProfileMigration(snapshot.migration);
|
|
1070
1105
|
const bytes = atomicWriteBytes(layout.configFile, beforeBytes);
|
|
1071
1106
|
restoreTargetApplyState(layout.root, snapshot.targetAgentId, snapshot.beforeRevision, snapshot.beforeApplyState);
|
|
1072
1107
|
fs.unlinkSync(rollbackJournalFile(layout.root));
|
|
@@ -1088,7 +1123,7 @@ export function commitSetupConfig(env, expectedRevision, nextStored) {
|
|
|
1088
1123
|
export function safeConfigView(config, onlyAgentId, chatId, applyState) {
|
|
1089
1124
|
const entries = onlyAgentId ? [[onlyAgentId, config.agents[onlyAgentId]]] : Object.entries(config.agents);
|
|
1090
1125
|
const agents = entries.filter((entry) => Boolean(entry[1])).map(([agentId, agent]) => ({
|
|
1091
|
-
agentId, runtime: agent.runtime, runtimeOption: toUserRuntime(agent.runtime
|
|
1126
|
+
agentId, runtime: agent.runtime, runtimeOption: toUserRuntime(agent.runtime), model: agent.model, effort: agent.effort ?? null,
|
|
1092
1127
|
mention: {
|
|
1093
1128
|
override: agent.mentionPolicy ?? "inherit",
|
|
1094
1129
|
effective: agent.mentionPolicy ?? config.mentionPolicy,
|
|
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { exactMode } from "../platform/secure-metadata.mjs";
|
|
5
5
|
export const PI_RUNTIME_ARTIFACT_MANIFEST = ".larkin-pi-owned-artifacts.json";
|
|
6
|
-
export const PI_RUNTIME_ARTIFACT_NAMES = ["
|
|
6
|
+
export const PI_RUNTIME_ARTIFACT_NAMES = ["models-store.json", "npm"];
|
|
7
7
|
const MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
8
8
|
const MAX_ENTRIES = 4096;
|
|
9
9
|
function digest(value) {
|
|
@@ -53,7 +53,7 @@ function readManifest(file) {
|
|
|
53
53
|
if (!stat.isFile() || !exactMode(stat, 0o600) || stat.size > 1024 * 1024)
|
|
54
54
|
throw new Error("Pi artifact provenance manifest is unsafe");
|
|
55
55
|
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
56
|
-
if (value?.version !== 1 || value.owner !== "larkin-builtin-pi" || !value.directory || !value.artifacts)
|
|
56
|
+
if (value?.version !== 1 || (value.owner !== "larkin-pi" && value.owner !== "larkin-builtin-pi") || !value.directory || !value.artifacts)
|
|
57
57
|
throw new Error("Pi artifact provenance manifest is invalid");
|
|
58
58
|
return value;
|
|
59
59
|
}
|
|
@@ -83,7 +83,7 @@ export function recordPiRuntimeArtifactProvenance(targetDir, beforeEntries, boun
|
|
|
83
83
|
const manifestFile = path.join(targetDir, PI_RUNTIME_ARTIFACT_MANIFEST);
|
|
84
84
|
const existing = readManifest(manifestFile);
|
|
85
85
|
const manifest = existing ?? {
|
|
86
|
-
version: 1, owner: "larkin-
|
|
86
|
+
version: 1, owner: "larkin-pi", directory: { device: String(target.dev), inode: String(target.ino) },
|
|
87
87
|
recordedAt: new Date(boundaryAt).toISOString(), artifacts: {},
|
|
88
88
|
};
|
|
89
89
|
if (manifest.directory.device !== String(target.dev) || manifest.directory.inode !== String(target.ino))
|
|
@@ -2,16 +2,15 @@ import { createBashToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
/**
|
|
3
3
|
* pi bash 工具超时护栏扩展。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `pi --extension/-e <bundle>` 注入,覆盖内置 `bash` 工具:
|
|
5
|
+
* 通过用户安装的 Pi 的 `pi --extension/-e <bundle>` 注入,覆盖内置 `bash` 工具:
|
|
7
6
|
* - 无论模型是否传 `timeout`,都强制收窄到 <= MAX_BASH_SECONDS(默认 60s),
|
|
8
7
|
* 避免单个 bash 调用无限期占住整个 agent 回合(issue #55)。
|
|
9
8
|
* - 超时时 pi 会杀掉整个进程树(不残留卡死子进程,issue #56 的进程堆积),
|
|
10
9
|
* 并在返回的错误里明确提示:长任务必须改用后台 subagent
|
|
11
10
|
* (Agent(run_in_background: true),来自 pi-subagents 扩展)。
|
|
12
11
|
*
|
|
13
|
-
* 这是 pi 扩展,运行在 Pi RPC 子进程内部;build.mjs
|
|
14
|
-
*
|
|
12
|
+
* 这是 pi 扩展,运行在 Pi RPC 子进程内部;build.mjs 把 `@earendil-works/pi-*`
|
|
13
|
+
* 标为 external,由 host Pi 的 loader 在运行时提供模块。
|
|
15
14
|
*/
|
|
16
15
|
/** 前台 bash 的硬性上限(秒)。issue #55 建议默认 60s。 */
|
|
17
16
|
const MAX_BASH_SECONDS = 60;
|
|
@@ -24,7 +23,7 @@ function effectiveBashTimeout() {
|
|
|
24
23
|
}
|
|
25
24
|
export default function (pi) {
|
|
26
25
|
const cwd = process.cwd();
|
|
27
|
-
//
|
|
26
|
+
// Host bash tool definition (keeps renderCall/renderResult/system-prompt metadata).
|
|
28
27
|
const builtin = createBashToolDefinition(cwd);
|
|
29
28
|
const MAX = effectiveBashTimeout();
|
|
30
29
|
const SUBAGENT_GUIDANCE = `This command exceeded the ${MAX}s foreground hard limit. ` +
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PiCompactionRecoveryMachine = exports.PiCompactionBreaker = exports.PI_COMPACTION_RECOVERY_FILE = exports.PI_COMPACTION_RECOVERY_VERSION = exports.PI_COMPACTION_THRESHOLD = exports.COMPACTION_KEEP_RECENT_TOKENS = exports.COMPACTION_RESERVE_TOKENS = exports.PI_CONTEXT_WINDOW = void 0;
|
|
6
|
+
exports.PiCompactionRecoveryMachine = exports.PiCompactionBreaker = exports.PI_COMPACTION_RECOVERY_FILE = exports.PI_COMPACTION_RECOVERY_VERSION = exports.PI_COMPACTION_THRESHOLD = exports.COMPACTION_KEEP_RECENT_TOKENS = exports.COMPACTION_RESERVE_TOKENS = exports.PI_CONTEXT_WINDOW = exports.MINIMUM_PI_VERSION = void 0;
|
|
7
7
|
exports.calculatePiCompactionSettings = calculatePiCompactionSettings;
|
|
8
8
|
exports.isPiNativeCompactionRequired = isPiNativeCompactionRequired;
|
|
9
9
|
exports.assertEffectivePiCompactionSettings = assertEffectivePiCompactionSettings;
|
|
@@ -14,6 +14,7 @@ exports.prepareOwnedPiDirectory = prepareOwnedPiDirectory;
|
|
|
14
14
|
exports.mergeOwnedPiSettings = mergeOwnedPiSettings;
|
|
15
15
|
exports.writeOwnedPiSettings = writeOwnedPiSettings;
|
|
16
16
|
exports.readOwnedPiSettings = readOwnedPiSettings;
|
|
17
|
+
exports.olderThanMinimumPiVersionMessage = olderThanMinimumPiVersionMessage;
|
|
17
18
|
exports.parsePiExecutableVersion = parsePiExecutableVersion;
|
|
18
19
|
exports.verifyPiCapabilities = verifyPiCapabilities;
|
|
19
20
|
exports.prepareOwnedPiEnvironment = prepareOwnedPiEnvironment;
|
|
@@ -24,7 +25,8 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
|
24
25
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
25
26
|
const node_os_1 = __importDefault(require("node:os"));
|
|
26
27
|
const node_path_1 = __importDefault(require("node:path"));
|
|
27
|
-
|
|
28
|
+
/** External `pi` must be this version or newer; handshake still guards protocol compatibility. */
|
|
29
|
+
exports.MINIMUM_PI_VERSION = "0.84.2";
|
|
28
30
|
/** Fallback values used while importing a Pi profile, before Pi reports its model. */
|
|
29
31
|
exports.PI_CONTEXT_WINDOW = 272_000;
|
|
30
32
|
exports.COMPACTION_RESERVE_TOKENS = 40_800;
|
|
@@ -254,23 +256,53 @@ function readOwnedPiSettings(directory) {
|
|
|
254
256
|
}
|
|
255
257
|
return { compaction: { enabled, reserveTokens: values.reserveTokens, keepRecentTokens: values.keepRecentTokens } };
|
|
256
258
|
}
|
|
259
|
+
const PI_VERSION_LINE = /^(?:pi(?:-coding-agent)?(?:\s+version)?\s+v?)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/i;
|
|
260
|
+
const PI_VERSION_CORE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?$/;
|
|
261
|
+
function comparePiNumericVersions(left, right) {
|
|
262
|
+
const leftParts = left.split(".").map(Number);
|
|
263
|
+
const rightParts = right.split(".").map(Number);
|
|
264
|
+
for (let index = 0; index < 3; index += 1) {
|
|
265
|
+
if (leftParts[index] !== rightParts[index])
|
|
266
|
+
return Math.sign(leftParts[index] - rightParts[index]);
|
|
267
|
+
}
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
function olderThanMinimumPiVersionMessage(version) {
|
|
271
|
+
return `Pi executable version ${version} is older than the minimum ${exports.MINIMUM_PI_VERSION}`;
|
|
272
|
+
}
|
|
273
|
+
function assertSupportedPiVersion(version) {
|
|
274
|
+
if (typeof version !== "string")
|
|
275
|
+
throw new Error(`Pi executable version ${exports.MINIMUM_PI_VERSION} or newer is required`);
|
|
276
|
+
const raw = version.trim();
|
|
277
|
+
const match = PI_VERSION_CORE.exec(raw);
|
|
278
|
+
if (!match)
|
|
279
|
+
throw new Error(`Pi executable version ${raw || "missing"} is not a supported version`);
|
|
280
|
+
const numeric = `${match[1]}.${match[2]}.${match[3]}`;
|
|
281
|
+
if (match[4]) {
|
|
282
|
+
throw new Error(`Pi executable version ${raw} is unsupported: SemVer ${raw} < ${numeric}; Larkin supports stable external pi only`);
|
|
283
|
+
}
|
|
284
|
+
if (comparePiNumericVersions(numeric, exports.MINIMUM_PI_VERSION) < 0) {
|
|
285
|
+
throw new Error(olderThanMinimumPiVersionMessage(numeric));
|
|
286
|
+
}
|
|
287
|
+
return numeric;
|
|
288
|
+
}
|
|
257
289
|
function parsePiExecutableVersion(output) {
|
|
258
290
|
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
259
291
|
if (lines.length !== 1)
|
|
260
292
|
throw new Error("Pi executable version output must contain exactly one version line");
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
293
|
+
const match = PI_VERSION_LINE.exec(lines[0]);
|
|
294
|
+
if (!match)
|
|
295
|
+
throw new Error("Pi executable version output is not a supported version line");
|
|
296
|
+
const version = `${match[1]}.${match[2]}.${match[3]}`;
|
|
297
|
+
if (comparePiNumericVersions(version, exports.MINIMUM_PI_VERSION) < 0) {
|
|
298
|
+
throw new Error(olderThanMinimumPiVersionMessage(version));
|
|
299
|
+
}
|
|
300
|
+
return version;
|
|
268
301
|
}
|
|
269
302
|
function verifyPiCapabilities(capabilities) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
throw new Error("external Pi cannot use the bundled trusted protocol bypass");
|
|
303
|
+
assertSupportedPiVersion(capabilities.version);
|
|
304
|
+
if (capabilities.trustedProtocol === true) {
|
|
305
|
+
throw new Error("external Pi cannot use a trusted protocol bypass");
|
|
274
306
|
}
|
|
275
307
|
const contextWindow = capabilities.contextWindow ?? capabilities.model?.contextWindow;
|
|
276
308
|
const expected = (() => {
|
|
@@ -286,8 +318,6 @@ function verifyPiCapabilities(capabilities) {
|
|
|
286
318
|
}
|
|
287
319
|
if (capabilities.compactRpc !== true)
|
|
288
320
|
throw new Error("Pi compact RPC capability is missing");
|
|
289
|
-
if (capabilities.distribution === "builtin" && capabilities.trustedProtocol === true)
|
|
290
|
-
return;
|
|
291
321
|
if (capabilities.reserveTokens !== expected.reserveTokens
|
|
292
322
|
|| capabilities.keepRecentTokens !== expected.keepRecentTokens) {
|
|
293
323
|
throw new Error(`Pi external effective compaction reserve/keep settings are unproven for context window ${contextWindow}`);
|