larkin 0.4.29 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -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 +9 -11
- package/dist/runtime/pi-compaction-recovery.mjs +8 -10
- 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 +42 -99
- package/dist/runtime/runtime-readiness.mjs +40 -98
- 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
|
@@ -121,7 +121,7 @@ function memberNamesFromPayloads(payloads) {
|
|
|
121
121
|
function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
|
|
122
122
|
function agentConfigSignature(agent) {
|
|
123
123
|
return JSON.stringify({
|
|
124
|
-
agentId: agent.agentId, runtime: agent.runtime, model: agent.model,
|
|
124
|
+
agentId: agent.agentId, runtime: agent.runtime, model: agent.model, effort: agent.effort ?? null,
|
|
125
125
|
feishuAppId: agent.feishuAppId, feishuProfile: agent.feishuProfile,
|
|
126
126
|
larkConfigDir: agent.larkConfigDir, feishuDomain: agent.feishuDomain,
|
|
127
127
|
feishuAppSecret: agent.feishuAppSecret, workspaceDir: agent.workspaceDir, stateDir: agent.stateDir,
|
|
@@ -217,7 +217,6 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
217
217
|
return (0, runtime_readiness_js_1.authFailureAppliesTo)({
|
|
218
218
|
runtime: agent.runtime,
|
|
219
219
|
model: agent.model,
|
|
220
|
-
piDistribution: agent.piDistribution,
|
|
221
220
|
}, persisted) ? persisted : null;
|
|
222
221
|
}
|
|
223
222
|
catch {
|
|
@@ -81,7 +81,7 @@ export function memberNamesFromPayloads(payloads) {
|
|
|
81
81
|
function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
|
|
82
82
|
function agentConfigSignature(agent) {
|
|
83
83
|
return JSON.stringify({
|
|
84
|
-
agentId: agent.agentId, runtime: agent.runtime, model: agent.model,
|
|
84
|
+
agentId: agent.agentId, runtime: agent.runtime, model: agent.model, effort: agent.effort ?? null,
|
|
85
85
|
feishuAppId: agent.feishuAppId, feishuProfile: agent.feishuProfile,
|
|
86
86
|
larkConfigDir: agent.larkConfigDir, feishuDomain: agent.feishuDomain,
|
|
87
87
|
feishuAppSecret: agent.feishuAppSecret, workspaceDir: agent.workspaceDir, stateDir: agent.stateDir,
|
|
@@ -177,7 +177,6 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
177
177
|
return authFailureAppliesTo({
|
|
178
178
|
runtime: agent.runtime,
|
|
179
179
|
model: agent.model,
|
|
180
|
-
piDistribution: agent.piDistribution,
|
|
181
180
|
}, persisted) ? persisted : null;
|
|
182
181
|
}
|
|
183
182
|
catch {
|
package/dist/platform/config.cjs
CHANGED
|
@@ -66,10 +66,8 @@ const path = __importStar(require("node:path"));
|
|
|
66
66
|
const root_layout_js_1 = require("./root-layout.cjs");
|
|
67
67
|
const secure_metadata_js_1 = require("./secure-metadata.cjs");
|
|
68
68
|
const runtime_model_catalog_js_1 = require("../runtime/runtime-model-catalog.cjs");
|
|
69
|
-
const pi_provider_config_js_1 = require("../runtime/pi-provider-config.cjs");
|
|
70
69
|
const user_runtime_js_1 = require("../runtime/user-runtime.cjs");
|
|
71
70
|
const process_inspect_cjs_1 = __importDefault(require("./process-inspect.cjs"));
|
|
72
|
-
const pi_profile_migration_js_1 = require("../runtime/pi-profile-migration.cjs");
|
|
73
71
|
var user_runtime_js_2 = require("../runtime/user-runtime.cjs");
|
|
74
72
|
Object.defineProperty(exports, "RUNTIME_OPTIONS", { enumerable: true, get: function () { return user_runtime_js_2.RUNTIME_OPTIONS; } });
|
|
75
73
|
Object.defineProperty(exports, "fromUserRuntime", { enumerable: true, get: function () { return user_runtime_js_2.fromUserRuntime; } });
|
|
@@ -81,7 +79,8 @@ Object.defineProperty(exports, "toUserRuntime", { enumerable: true, get: functio
|
|
|
81
79
|
const TOP_FIELDS_V3 = new Set(["version", "serverId", "activeAgent", "agents"]);
|
|
82
80
|
const TOP_FIELDS_V4 = new Set(["version", "serverId", "mentionPolicy", "activeAgent", "agents"]);
|
|
83
81
|
const AGENT_FIELDS_V3 = new Set(["runtime", "model", "effort", "noMentionChats", "createdAt"]);
|
|
84
|
-
const AGENT_FIELDS_V4 = new Set(["runtime", "model", "
|
|
82
|
+
const AGENT_FIELDS_V4 = new Set(["runtime", "model", "effort", "mentionPolicy", "chatMentionPolicies", "createdAt"]);
|
|
83
|
+
const LEGACY_AGENT_FIELDS = new Set(["piDistribution", "runtimeOption"]);
|
|
85
84
|
const APP_ID = /^cli_[A-Za-z0-9]+$/;
|
|
86
85
|
const CHAT_ID = /^oc_[A-Za-z0-9_-]+$/;
|
|
87
86
|
const PI_EFFORTS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
@@ -159,31 +158,51 @@ function runtimeSupportsEffort(runtime, effort) {
|
|
|
159
158
|
: runtime === "codex" ? CODEX_EFFORTS.has(effort)
|
|
160
159
|
: runtime === "claude" ? CLAUDE_EFFORTS.has(effort) : false;
|
|
161
160
|
}
|
|
161
|
+
function takeLegacyAgentFields(agent) {
|
|
162
|
+
const cleaned = {};
|
|
163
|
+
for (const [field, value] of Object.entries(agent)) {
|
|
164
|
+
if (!LEGACY_AGENT_FIELDS.has(field))
|
|
165
|
+
cleaned[field] = value;
|
|
166
|
+
}
|
|
167
|
+
return cleaned;
|
|
168
|
+
}
|
|
169
|
+
function isLegacyBuiltinAgentRecord(agent) {
|
|
170
|
+
if (!isPlainObject(agent))
|
|
171
|
+
return false;
|
|
172
|
+
return agent.piDistribution === "builtin" || agent.runtime === "builtin-pi" || agent.runtimeOption === "builtin-pi";
|
|
173
|
+
}
|
|
174
|
+
function canonicalizeLegacyStoredAgent(key, agent) {
|
|
175
|
+
const cleaned = takeLegacyAgentFields(agent);
|
|
176
|
+
if (cleaned.runtime === "builtin-pi" || isLegacyBuiltinAgentRecord(agent))
|
|
177
|
+
cleaned.runtime = "pi";
|
|
178
|
+
if (typeof cleaned.runtime !== "string" || !cleaned.runtime)
|
|
179
|
+
throw new Error(`Agent ${key}.runtime 必须是非空字符串`);
|
|
180
|
+
return cleaned;
|
|
181
|
+
}
|
|
162
182
|
function validateStoredAgent(key, agent, version) {
|
|
163
183
|
if (!APP_ID.test(key))
|
|
164
184
|
throw new Error(`Agent key 必须是安全的飞书 App ID(cli_ + ASCII 字母数字):${key}`);
|
|
165
185
|
if (!isPlainObject(agent))
|
|
166
186
|
throw new Error(`Agent ${key} 必须是普通 object`);
|
|
167
|
-
|
|
168
|
-
|
|
187
|
+
const canonical = canonicalizeLegacyStoredAgent(key, agent);
|
|
188
|
+
assertAllowedFields(canonical, version === 3 ? AGENT_FIELDS_V3 : AGENT_FIELDS_V4, `Agent ${key}`);
|
|
189
|
+
if (typeof canonical.runtime !== "string" || !canonical.runtime)
|
|
169
190
|
throw new Error(`Agent ${key}.runtime 必须是非空字符串`);
|
|
170
|
-
if (!(0, user_runtime_js_1.isAdapterRuntime)(
|
|
171
|
-
throw new Error(`Agent ${key}.runtime 未知:${
|
|
172
|
-
if (typeof
|
|
191
|
+
if (!(0, user_runtime_js_1.isAdapterRuntime)(canonical.runtime))
|
|
192
|
+
throw new Error(`Agent ${key}.runtime 未知:${canonical.runtime}`);
|
|
193
|
+
if (typeof canonical.model !== "string" || !canonical.model)
|
|
173
194
|
throw new Error(`Agent ${key}.model 必须是非空字符串`);
|
|
174
|
-
assertModel(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
}
|
|
195
|
+
assertModel(canonical.runtime, canonical.model);
|
|
196
|
+
Object.assign(agent, canonical);
|
|
197
|
+
for (const field of LEGACY_AGENT_FIELDS)
|
|
198
|
+
delete agent[field];
|
|
180
199
|
if (Object.hasOwn(agent, "effort") && (typeof agent.effort !== "string" || !agent.effort))
|
|
181
200
|
throw new Error(`Agent ${key}.effort 必须是非空字符串`);
|
|
182
201
|
if (agent.model === "default" && Object.hasOwn(agent, "effort"))
|
|
183
202
|
throw new Error(`Agent ${key}.model=default 时不能保存 effort`);
|
|
184
203
|
if (typeof agent.effort === "string") {
|
|
185
|
-
if (!runtimeSupportsEffort(
|
|
186
|
-
throw new Error(`Agent ${key}.effort=${agent.effort} 不在 ${
|
|
204
|
+
if (!runtimeSupportsEffort(canonical.runtime, agent.effort))
|
|
205
|
+
throw new Error(`Agent ${key}.effort=${agent.effort} 不在 ${canonical.runtime} 安全档位中`);
|
|
187
206
|
}
|
|
188
207
|
if (version === 3 && Object.hasOwn(agent, "noMentionChats")) {
|
|
189
208
|
if (!Array.isArray(agent.noMentionChats) || agent.noMentionChats.some((chat) => typeof chat !== "string" || !CHAT_ID.test(chat))) {
|
|
@@ -218,7 +237,6 @@ function hydratedStoredAgent(key, agent, version) {
|
|
|
218
237
|
return {
|
|
219
238
|
runtime: agent.runtime,
|
|
220
239
|
model: agent.model,
|
|
221
|
-
...(agent.piDistribution === "external" || agent.piDistribution === "builtin" ? { piDistribution: agent.piDistribution } : {}),
|
|
222
240
|
...(typeof agent.effort === "string" ? { effort: agent.effort } : {}),
|
|
223
241
|
...(version === 4 && (agent.mentionPolicy === "require" || agent.mentionPolicy === "free") ? { mentionPolicy: agent.mentionPolicy } : {}),
|
|
224
242
|
...(Object.keys(chatMentionPolicies).length ? { chatMentionPolicies, noMentionChats: Object.entries(chatMentionPolicies).filter(([, policy]) => policy === "free").map(([chatId]) => chatId) } : {}),
|
|
@@ -230,7 +248,6 @@ function hydrateAgent(key, agent, configDir) {
|
|
|
230
248
|
return {
|
|
231
249
|
name: key, agentId: key, feishuAppId: key, feishuProfile: key,
|
|
232
250
|
runtime: agent.runtime, model: agent.model,
|
|
233
|
-
...(agent.piDistribution ? { piDistribution: agent.piDistribution } : {}),
|
|
234
251
|
workspaceDir: layout.workspaceDir(key), stateDir: layout.agentStateDir(key),
|
|
235
252
|
larkConfigDir: path.join(layout.agentStateDir(key), "lark-cli-config"),
|
|
236
253
|
...(agent.effort ? { effort: agent.effort } : {}),
|
|
@@ -351,9 +368,137 @@ function readConfigFile(file, root = path.dirname(file)) {
|
|
|
351
368
|
throw error;
|
|
352
369
|
}
|
|
353
370
|
}
|
|
371
|
+
function collectLegacyBuiltinAgentIds(raw) {
|
|
372
|
+
if (!isPlainObject(raw) || !isPlainObject(raw.agents))
|
|
373
|
+
return [];
|
|
374
|
+
return Object.entries(raw.agents).filter(([, agent]) => isLegacyBuiltinAgentRecord(agent)).map(([agentId]) => agentId);
|
|
375
|
+
}
|
|
376
|
+
function ownedPiProviderDirectory(configDir, agentId) {
|
|
377
|
+
if (!APP_ID.test(agentId))
|
|
378
|
+
throw new Error(`Pi Agent ID 格式无效:${agentId}`);
|
|
379
|
+
return path.join(path.resolve(configDir), "providers", "pi", agentId);
|
|
380
|
+
}
|
|
381
|
+
function userPiHome(env) {
|
|
382
|
+
const home = typeof env.HOME === "string" && env.HOME.trim() ? env.HOME : os.homedir();
|
|
383
|
+
return path.resolve(home, ".pi");
|
|
384
|
+
}
|
|
385
|
+
function isInsideOrEqual(candidate, root) {
|
|
386
|
+
const relative = path.relative(root, candidate);
|
|
387
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
388
|
+
}
|
|
389
|
+
function pathExists(target) {
|
|
390
|
+
try {
|
|
391
|
+
fs.lstatSync(target);
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
if (error.code === "ENOENT")
|
|
396
|
+
return false;
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function shouldSkipOwnedPiDeletion(configDir, expected, env) {
|
|
401
|
+
let userPi;
|
|
402
|
+
try {
|
|
403
|
+
userPi = fs.realpathSync(userPiHome(env));
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
userPi = path.resolve(userPiHome(env));
|
|
407
|
+
}
|
|
408
|
+
let configRoot;
|
|
409
|
+
try {
|
|
410
|
+
configRoot = fs.realpathSync(path.resolve(configDir));
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
configRoot = path.resolve(configDir);
|
|
414
|
+
}
|
|
415
|
+
let resolvedExpected;
|
|
416
|
+
try {
|
|
417
|
+
resolvedExpected = fs.realpathSync(path.resolve(expected));
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
resolvedExpected = path.resolve(expected);
|
|
421
|
+
}
|
|
422
|
+
if (isInsideOrEqual(configRoot, userPi))
|
|
423
|
+
return "config root is inside the user ~/.pi directory";
|
|
424
|
+
if (isInsideOrEqual(resolvedExpected, userPi))
|
|
425
|
+
return "owned Pi directory is inside the user ~/.pi directory";
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
function deleteOwnedPiProviderDirectory(configDir, agentId, env = process.env) {
|
|
429
|
+
const expected = ownedPiProviderDirectory(configDir, agentId);
|
|
430
|
+
const skip = shouldSkipOwnedPiDeletion(configDir, expected, env);
|
|
431
|
+
if (skip) {
|
|
432
|
+
process.stderr.write(`[larkin] skipped leftover builtin Pi directory for ${agentId}: ${skip}\n`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const parent = path.dirname(expected);
|
|
436
|
+
if (!pathExists(parent) || !pathExists(expected))
|
|
437
|
+
return;
|
|
438
|
+
assertNoSymlinkAncestors(expected);
|
|
439
|
+
const rootReal = fs.realpathSync(path.resolve(configDir));
|
|
440
|
+
const parentReal = fs.realpathSync(parent);
|
|
441
|
+
const relative = path.relative(rootReal, parentReal);
|
|
442
|
+
if (relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative)) {
|
|
443
|
+
throw new Error("owned Pi provider directory escaped the config root");
|
|
444
|
+
}
|
|
445
|
+
if (path.basename(parent) !== "pi" || path.basename(path.dirname(parent)) !== "providers") {
|
|
446
|
+
throw new Error("owned Pi provider directory is not providers/pi/<agentId>");
|
|
447
|
+
}
|
|
448
|
+
const stat = fs.lstatSync(expected);
|
|
449
|
+
if (stat.isSymbolicLink())
|
|
450
|
+
throw new Error("owned Pi provider directory is a symlink");
|
|
451
|
+
if (!stat.isDirectory())
|
|
452
|
+
throw new Error("owned Pi provider directory must be a directory");
|
|
453
|
+
fs.rmSync(expected, { recursive: true, force: false });
|
|
454
|
+
}
|
|
455
|
+
function rewriteLegacyBuiltinAgents(raw) {
|
|
456
|
+
if (!isPlainObject(raw) || !isPlainObject(raw.agents))
|
|
457
|
+
return raw;
|
|
458
|
+
const legacyIds = new Set(collectLegacyBuiltinAgentIds(raw));
|
|
459
|
+
if (legacyIds.size === 0)
|
|
460
|
+
return raw;
|
|
461
|
+
const agents = {};
|
|
462
|
+
for (const [agentId, agent] of Object.entries(raw.agents)) {
|
|
463
|
+
if (!legacyIds.has(agentId) || !isPlainObject(agent)) {
|
|
464
|
+
agents[agentId] = agent;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const next = takeLegacyAgentFields(agent);
|
|
468
|
+
if (next.runtime === "builtin-pi" || isLegacyBuiltinAgentRecord(agent))
|
|
469
|
+
next.runtime = "pi";
|
|
470
|
+
agents[agentId] = next;
|
|
471
|
+
}
|
|
472
|
+
return { ...raw, agents };
|
|
473
|
+
}
|
|
474
|
+
function persistLegacyBuiltinMigration(layout, opts, env) {
|
|
475
|
+
const current = readConfigFile(layout.configFile, layout.root);
|
|
476
|
+
const migratedIds = collectLegacyBuiltinAgentIds(current.raw);
|
|
477
|
+
if (migratedIds.length === 0)
|
|
478
|
+
return current;
|
|
479
|
+
const rewritten = rewriteLegacyBuiltinAgents(current.raw);
|
|
480
|
+
const config = normalizeConfig(structuredClone(rewritten), layout.root, opts);
|
|
481
|
+
const bytes = atomicWriteConfig(layout.configFile, rewritten);
|
|
482
|
+
for (const agentId of migratedIds) {
|
|
483
|
+
const agent = config.agents[agentId];
|
|
484
|
+
const model = typeof agent?.model === "string" ? agent.model : "unknown";
|
|
485
|
+
process.stderr.write(`[larkin] migrated Agent ${agentId} from builtin-pi to pi (model=${model})\n`);
|
|
486
|
+
try {
|
|
487
|
+
deleteOwnedPiProviderDirectory(layout.root, agentId, env);
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
491
|
+
process.stderr.write(`[larkin] failed to delete leftover builtin Pi directory for ${agentId}: ${reason}\n`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return { raw: rewritten, bytes };
|
|
495
|
+
}
|
|
354
496
|
function loadConfig(env = process.env, opts = {}) {
|
|
355
497
|
const layout = root_layout_js_1.TargetRootLayout.fromConfigDir(resolveConfigDir(env));
|
|
356
|
-
const
|
|
498
|
+
const initial = readConfigFile(layout.configFile, layout.root);
|
|
499
|
+
const { raw, bytes } = collectLegacyBuiltinAgentIds(initial.raw).length
|
|
500
|
+
? withConfigLock(layout, () => persistLegacyBuiltinMigration(layout, opts, env))
|
|
501
|
+
: initial;
|
|
357
502
|
return { configDir: layout.configDir, file: layout.configFile, revision: revision(bytes), config: normalizeConfig(raw, layout.root, opts) };
|
|
358
503
|
}
|
|
359
504
|
function selectAgent(config, env = process.env) {
|
|
@@ -374,8 +519,6 @@ function toStored(config) {
|
|
|
374
519
|
const out = { version: 4, serverId: config.serverId, mentionPolicy: config.mentionPolicy, activeAgent: config.activeAgent, agents: {} };
|
|
375
520
|
for (const [key, agent] of Object.entries(config.agents || {})) {
|
|
376
521
|
const stored = { runtime: agent.runtime, model: agent.model };
|
|
377
|
-
if (agent.piDistribution)
|
|
378
|
-
stored.piDistribution = agent.piDistribution;
|
|
379
522
|
if (typeof agent.effort === "string" && agent.effort)
|
|
380
523
|
stored.effort = agent.effort;
|
|
381
524
|
if (agent.mentionPolicy)
|
|
@@ -417,7 +560,8 @@ function agentSignature(config, agentId) {
|
|
|
417
560
|
throw new Error(`Agent 不存在:${agentId}`);
|
|
418
561
|
const chats = Object.fromEntries(Object.entries(agent.chatMentionPolicies || {}).sort(([left], [right]) => left.localeCompare(right)));
|
|
419
562
|
return revision(JSON.stringify({
|
|
420
|
-
|
|
563
|
+
schema: 2,
|
|
564
|
+
runtime: agent.runtime, model: agent.model, effort: agent.effort ?? null,
|
|
421
565
|
globalMentionPolicy: config.mentionPolicy, agentMentionPolicy: agent.mentionPolicy ?? null, chatMentionPolicies: chats,
|
|
422
566
|
}));
|
|
423
567
|
}
|
|
@@ -454,12 +598,6 @@ function writeApplyFile(configDir, value) {
|
|
|
454
598
|
atomicWriteConfig(applyStateFile(configDir), value);
|
|
455
599
|
}
|
|
456
600
|
function rollbackJournalFile(root) { return path.join(root, CONFIG_ROLLBACK_JOURNAL_FILE); }
|
|
457
|
-
function migrationJournalFile(root) { return path.join(root, ".pi-profile-migration-journal.json"); }
|
|
458
|
-
function assertMigrationTarget(root, agentId, migration) {
|
|
459
|
-
if (migration.agentId !== agentId || path.resolve(migration.targetDir) !== path.join(path.resolve(root), "providers", "pi", agentId)) {
|
|
460
|
-
throw new Error("Pi profile migration target is invalid");
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
601
|
function restoreTargetApplyState(root, targetAgentId, beforeRevision, before) {
|
|
464
602
|
const current = readApplyFile(root);
|
|
465
603
|
const agents = { ...current.agents };
|
|
@@ -498,15 +636,9 @@ function recoverRollbackJournal(root) {
|
|
|
498
636
|
const beforeRaw = JSON.parse(beforeBytes.toString("utf8"));
|
|
499
637
|
normalizeConfig(beforeRaw, root);
|
|
500
638
|
validateApplyState(journal.beforeApplyState);
|
|
501
|
-
if (journal.migration !== undefined) {
|
|
502
|
-
(0, pi_profile_migration_js_1.validatePiProfileMigrationState)(journal.migration);
|
|
503
|
-
assertMigrationTarget(root, journal.targetAgentId, journal.migration);
|
|
504
|
-
}
|
|
505
639
|
const current = readConfigFile(path.join(root, "config.json"), root);
|
|
506
640
|
const currentRevision = revision(current.bytes);
|
|
507
641
|
if (currentRevision === journal.expectedAfterRevision) {
|
|
508
|
-
if (journal.migration)
|
|
509
|
-
(0, pi_profile_migration_js_1.rollbackPiProfileMigration)(journal.migration);
|
|
510
642
|
atomicWriteBytes(path.join(root, "config.json"), beforeBytes);
|
|
511
643
|
restoreTargetApplyState(root, journal.targetAgentId, journal.beforeRevision, journal.beforeApplyState);
|
|
512
644
|
}
|
|
@@ -521,47 +653,6 @@ function recoverRollbackJournal(root) {
|
|
|
521
653
|
fs.unlinkSync(file);
|
|
522
654
|
(0, secure_metadata_js_1.fsyncDirectoryOf)(file);
|
|
523
655
|
}
|
|
524
|
-
function recoverMigrationJournal(root) {
|
|
525
|
-
const file = migrationJournalFile(root);
|
|
526
|
-
const bytes = readPrivateFile(file, root, PROFILE_MIGRATION_ROLLBACK_LIMIT_BYTES, "Pi profile migration journal");
|
|
527
|
-
if (bytes === null)
|
|
528
|
-
return;
|
|
529
|
-
let journal;
|
|
530
|
-
try {
|
|
531
|
-
journal = JSON.parse(bytes.toString("utf8"));
|
|
532
|
-
}
|
|
533
|
-
catch {
|
|
534
|
-
throw new Error("Pi profile migration journal is invalid");
|
|
535
|
-
}
|
|
536
|
-
if (journal.version !== 1 || journal.phase !== "forward" || typeof journal.targetAgentId !== "string"
|
|
537
|
-
|| typeof journal.beforeRevision !== "string" || typeof journal.expectedAfterRevision !== "string" || !journal.migration) {
|
|
538
|
-
throw new Error("Pi profile migration journal is invalid");
|
|
539
|
-
}
|
|
540
|
-
(0, pi_profile_migration_js_1.validatePiProfileMigrationState)(journal.migration);
|
|
541
|
-
assertMigrationTarget(root, journal.targetAgentId, journal.migration);
|
|
542
|
-
const current = readConfigFile(path.join(root, "config.json"), root);
|
|
543
|
-
const currentRevision = revision(current.bytes);
|
|
544
|
-
if (currentRevision === journal.beforeRevision) {
|
|
545
|
-
// Forward import did not publish config. The migration helper restores only
|
|
546
|
-
// files still matching its expected post-import hashes.
|
|
547
|
-
try {
|
|
548
|
-
(0, pi_profile_migration_js_1.rollbackPiProfileMigration)(journal.migration);
|
|
549
|
-
}
|
|
550
|
-
catch (error) {
|
|
551
|
-
throw new Error(`Pi profile migration recovery refused: ${error instanceof Error ? error.message : String(error)}`);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
else if (currentRevision === journal.expectedAfterRevision) {
|
|
555
|
-
// Config won the race; verify the provider target before finalizing.
|
|
556
|
-
const migration = journal.migration;
|
|
557
|
-
(0, pi_profile_migration_js_1.assertPiProfileMigrationAfterState)(migration);
|
|
558
|
-
}
|
|
559
|
-
else
|
|
560
|
-
throw new Error("Pi profile migration journal conflicts with the current configuration");
|
|
561
|
-
(0, pi_profile_migration_js_1.releasePiProfileMigrationLock)(journal.migration);
|
|
562
|
-
fs.unlinkSync(file);
|
|
563
|
-
(0, secure_metadata_js_1.fsyncDirectoryOf)(file);
|
|
564
|
-
}
|
|
565
656
|
function configApplyState(env, config) {
|
|
566
657
|
let state;
|
|
567
658
|
try {
|
|
@@ -638,38 +729,16 @@ function applyMutation(config, mutation) {
|
|
|
638
729
|
agent.noMentionChats = Object.entries(policies).filter(([, value]) => value === "free").map(([chatId]) => chatId);
|
|
639
730
|
return { scope: "chat", agentId: mutation.agentId, runtimeChange: false, affectedAgentIds: [mutation.agentId] };
|
|
640
731
|
}
|
|
641
|
-
if (mutation.kind === "set-agent-
|
|
642
|
-
if (agent.runtime !== "pi")
|
|
643
|
-
throw new Error(`Agent ${mutation.agentId} 不是 Pi runtime`);
|
|
644
|
-
if (mutation.distribution !== "builtin" && mutation.distribution !== "external")
|
|
645
|
-
throw new Error("Pi distribution 只允许 builtin/external");
|
|
646
|
-
agent.piDistribution = mutation.distribution;
|
|
647
|
-
}
|
|
648
|
-
else if (mutation.kind === "set-agent-runtime") {
|
|
732
|
+
if (mutation.kind === "set-agent-runtime") {
|
|
649
733
|
if (!(0, user_runtime_js_1.isUserRuntime)(mutation.runtime))
|
|
650
734
|
throw new Error(`未知 runtime:${mutation.runtime}`);
|
|
651
735
|
const stored = (0, user_runtime_js_1.fromUserRuntime)(mutation.runtime);
|
|
652
|
-
if (stored.piDistribution === "builtin") {
|
|
653
|
-
try {
|
|
654
|
-
(0, pi_provider_config_js_1.assertBuiltinPiAgentDirectory)((0, pi_provider_config_js_1.piAgentDirectory)(config.configDir, mutation.agentId));
|
|
655
|
-
}
|
|
656
|
-
catch (error) {
|
|
657
|
-
throw new Error(`无法切换到 builtin-pi:${error instanceof Error ? error.message : String(error)}。请使用 Dashboard Provider Credentials 或 larkin pi-auth login。`);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
736
|
assertModel(stored.runtime, mutation.model || "default");
|
|
661
737
|
agent.runtime = stored.runtime;
|
|
662
738
|
agent.model = mutation.model || "default";
|
|
663
|
-
if (stored.piDistribution)
|
|
664
|
-
agent.piDistribution = stored.piDistribution;
|
|
665
|
-
else
|
|
666
|
-
delete agent.piDistribution;
|
|
667
739
|
delete agent.effort;
|
|
668
740
|
}
|
|
669
741
|
else if (mutation.kind === "set-agent-model") {
|
|
670
|
-
if (mutation.requireBuiltinPi && (agent.runtime !== "pi" || agent.piDistribution !== "builtin")) {
|
|
671
|
-
throw new Error(`Agent ${mutation.agentId} 不是内置 Pi`);
|
|
672
|
-
}
|
|
673
742
|
if (!mutation.model.trim())
|
|
674
743
|
throw new Error("model 不能为空");
|
|
675
744
|
assertModel(agent.runtime, mutation.model);
|
|
@@ -875,7 +944,6 @@ function acquireConfigLock(layout) {
|
|
|
875
944
|
function withConfigLock(layout, action) {
|
|
876
945
|
const held = acquireConfigLock(layout);
|
|
877
946
|
try {
|
|
878
|
-
recoverMigrationJournal(layout.root);
|
|
879
947
|
recoverRollbackJournal(layout.root);
|
|
880
948
|
return action();
|
|
881
949
|
}
|
|
@@ -890,7 +958,6 @@ async function withConfigMutationLockAsync(env, action) {
|
|
|
890
958
|
const layout = root_layout_js_1.TargetRootLayout.fromConfigDir(resolveConfigDir(env));
|
|
891
959
|
const held = acquireConfigLock(layout);
|
|
892
960
|
try {
|
|
893
|
-
recoverMigrationJournal(layout.root);
|
|
894
961
|
recoverRollbackJournal(layout.root);
|
|
895
962
|
return await action();
|
|
896
963
|
}
|
|
@@ -990,10 +1057,6 @@ function readConfigSnapshot(file, root) {
|
|
|
990
1057
|
if (JSON.stringify(beforeRaw) !== JSON.stringify(parsed.beforeConfig))
|
|
991
1058
|
throw new Error("config snapshot before config does not match bytes");
|
|
992
1059
|
validateApplyState(parsed.beforeApplyState);
|
|
993
|
-
if (parsed.migration !== undefined) {
|
|
994
|
-
(0, pi_profile_migration_js_1.validatePiProfileMigrationState)(parsed.migration);
|
|
995
|
-
assertMigrationTarget(root, parsed.targetAgentId, parsed.migration);
|
|
996
|
-
}
|
|
997
1060
|
return parsed;
|
|
998
1061
|
}
|
|
999
1062
|
function atomicWriteBytes(file, bytes, limit = CONFIG_LIMIT_BYTES) {
|
|
@@ -1037,12 +1100,6 @@ function mutateConfig(env, mutation, authority, options = {}) {
|
|
|
1037
1100
|
throw new Error(`没找到配置 ${layout.configFile},先跑 larkin setup`);
|
|
1038
1101
|
const config = normalizeConfig(current.raw, layout.root);
|
|
1039
1102
|
const priorSignatures = Object.fromEntries(Object.keys(config.agents).map((agentId) => [agentId, agentSignature(config, agentId)]));
|
|
1040
|
-
if (options.importExternalProfile && (mutation.kind !== "set-agent-pi-distribution" || mutation.distribution !== "builtin")) {
|
|
1041
|
-
throw new Error("external Pi profile import is only valid for builtin Pi distribution");
|
|
1042
|
-
}
|
|
1043
|
-
const migrationPlan = options.importExternalProfile && mutation.kind === "set-agent-pi-distribution"
|
|
1044
|
-
? (0, pi_profile_migration_js_1.preparePiProfileMigration)(env, layout.root, mutation.agentId, "builtin")
|
|
1045
|
-
: undefined;
|
|
1046
1103
|
const changed = applyMutation(config, mutation);
|
|
1047
1104
|
const stored = toStored(config);
|
|
1048
1105
|
normalizeConfig(stored, layout.root);
|
|
@@ -1063,16 +1120,8 @@ function mutateConfig(env, mutation, authority, options = {}) {
|
|
|
1063
1120
|
version: 2, targetAgentId: changed.agentId, beforeRevision: revision(current.bytes), afterRevision: nextRevision,
|
|
1064
1121
|
afterSignature: agentSignature(config, changed.agentId), beforeConfig: current.raw,
|
|
1065
1122
|
beforeConfigBytes: current.bytes.toString("base64"), beforeApplyState,
|
|
1066
|
-
...(migrationPlan ? { migration: migrationPlan.state } : {}),
|
|
1067
1123
|
});
|
|
1068
1124
|
}
|
|
1069
|
-
if (migrationPlan) {
|
|
1070
|
-
atomicWriteConfig(migrationJournalFile(layout.root), {
|
|
1071
|
-
version: 1, phase: "forward", targetAgentId: changed.agentId, beforeRevision: revision(current.bytes),
|
|
1072
|
-
expectedAfterRevision: nextRevision, migration: migrationPlan.state,
|
|
1073
|
-
}, PROFILE_MIGRATION_ROLLBACK_LIMIT_BYTES);
|
|
1074
|
-
(0, pi_profile_migration_js_1.applyPiProfileMigration)(migrationPlan);
|
|
1075
|
-
}
|
|
1076
1125
|
atomicWriteConfig(layout.configFile, stored);
|
|
1077
1126
|
let fullyApplied = changed.affectedAgentIds.length === 0;
|
|
1078
1127
|
try {
|
|
@@ -1096,17 +1145,6 @@ function mutateConfig(env, mutation, authority, options = {}) {
|
|
|
1096
1145
|
catch {
|
|
1097
1146
|
fullyApplied = false; /* Config persistence remains authoritative. */
|
|
1098
1147
|
}
|
|
1099
|
-
if (migrationPlan) {
|
|
1100
|
-
(0, pi_profile_migration_js_1.releasePiProfileMigrationLock)(migrationPlan.state);
|
|
1101
|
-
try {
|
|
1102
|
-
fs.unlinkSync(migrationJournalFile(layout.root));
|
|
1103
|
-
(0, secure_metadata_js_1.fsyncDirectoryOf)(migrationJournalFile(layout.root));
|
|
1104
|
-
}
|
|
1105
|
-
catch (error) {
|
|
1106
|
-
if (error.code !== "ENOENT")
|
|
1107
|
-
throw error;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
1148
|
return {
|
|
1111
1149
|
revision: nextRevision, previousRevision: revision(current.bytes), changedScope: changed.scope,
|
|
1112
1150
|
...(changed.agentId ? { agentId: changed.agentId } : {}), persisted: true,
|
|
@@ -1130,11 +1168,8 @@ function rollbackConfig(env, snapshotFile) {
|
|
|
1130
1168
|
const journal = {
|
|
1131
1169
|
version: 1, targetAgentId: snapshot.targetAgentId, expectedAfterRevision: snapshot.afterRevision,
|
|
1132
1170
|
beforeRevision: snapshot.beforeRevision, beforeConfigBytes: snapshot.beforeConfigBytes, beforeApplyState: snapshot.beforeApplyState,
|
|
1133
|
-
...(snapshot.migration ? { migration: snapshot.migration } : {}),
|
|
1134
1171
|
};
|
|
1135
1172
|
atomicWriteConfig(rollbackJournalFile(layout.root), journal, PROFILE_MIGRATION_ROLLBACK_LIMIT_BYTES);
|
|
1136
|
-
if (snapshot.migration)
|
|
1137
|
-
(0, pi_profile_migration_js_1.rollbackPiProfileMigration)(snapshot.migration);
|
|
1138
1173
|
const bytes = atomicWriteBytes(layout.configFile, beforeBytes);
|
|
1139
1174
|
restoreTargetApplyState(layout.root, snapshot.targetAgentId, snapshot.beforeRevision, snapshot.beforeApplyState);
|
|
1140
1175
|
fs.unlinkSync(rollbackJournalFile(layout.root));
|
|
@@ -1156,7 +1191,7 @@ function commitSetupConfig(env, expectedRevision, nextStored) {
|
|
|
1156
1191
|
function safeConfigView(config, onlyAgentId, chatId, applyState) {
|
|
1157
1192
|
const entries = onlyAgentId ? [[onlyAgentId, config.agents[onlyAgentId]]] : Object.entries(config.agents);
|
|
1158
1193
|
const agents = entries.filter((entry) => Boolean(entry[1])).map(([agentId, agent]) => ({
|
|
1159
|
-
agentId, runtime: agent.runtime, runtimeOption: (0, user_runtime_js_1.toUserRuntime)(agent.runtime
|
|
1194
|
+
agentId, runtime: agent.runtime, runtimeOption: (0, user_runtime_js_1.toUserRuntime)(agent.runtime), model: agent.model, effort: agent.effort ?? null,
|
|
1160
1195
|
mention: {
|
|
1161
1196
|
override: agent.mentionPolicy ?? "inherit",
|
|
1162
1197
|
effective: agent.mentionPolicy ?? config.mentionPolicy,
|