larkin 0.4.27 → 0.4.29

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.
Files changed (34) hide show
  1. package/dist/app/agent-cli.mjs +4 -2
  2. package/dist/app/agent-config.mjs +45 -29
  3. package/dist/app/cli.mjs +16 -6
  4. package/dist/app/local-control.mjs +16 -3
  5. package/dist/app/pi-auth-cli.mjs +227 -17
  6. package/dist/app/runtime-model-directory.mjs +3 -2
  7. package/dist/app/runtime-process.mjs +42 -5
  8. package/dist/app/setup.mjs +15 -14
  9. package/dist/dashboard/dashboard-config-controller.mjs +145 -13
  10. package/dist/dashboard/dashboard-view-model.mjs +3 -2
  11. package/dist/dashboard/web/assets/dashboard.css +1 -1
  12. package/dist/dashboard/web/assets/dashboard.js +15 -15
  13. package/dist/feishu/host-shell.cjs +68 -16
  14. package/dist/feishu/host-shell.mjs +69 -17
  15. package/dist/platform/config.cjs +67 -16
  16. package/dist/platform/config.mjs +57 -16
  17. package/dist/runtime/pi-official-auth.cjs +594 -0
  18. package/dist/runtime/pi-official-auth.mjs +53 -0
  19. package/dist/runtime/pi-provider-config.cjs +26 -3
  20. package/dist/runtime/pi-provider-config.mjs +22 -2
  21. package/dist/runtime/pi-provider-login.mjs +240 -0
  22. package/dist/runtime/runtime-adapters.mjs +24 -4
  23. package/dist/runtime/runtime-host.cjs +215 -16
  24. package/dist/runtime/runtime-host.mjs +216 -17
  25. package/dist/runtime/runtime-model-catalog.cjs +4 -3
  26. package/dist/runtime/runtime-model-catalog.mjs +4 -3
  27. package/dist/runtime/runtime-readiness.cjs +123 -3
  28. package/dist/runtime/runtime-readiness.mjs +116 -4
  29. package/dist/runtime/user-runtime.cjs +51 -0
  30. package/dist/runtime/user-runtime.mjs +41 -0
  31. package/dist/setup/bot-register.mjs +3 -3
  32. package/dist/setup/setup-agent-choice.mjs +10 -9
  33. package/dist/setup/setup-bind.mjs +2 -2
  34. package/package.json +1 -1
@@ -208,26 +208,72 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
208
208
  runtimeReadiness: { runtime: agent.runtime, state: "unavailable", reason, nextAction: "Wait for the current daemon epoch to publish Runtime readiness.", observedAt: new Date().toISOString() },
209
209
  });
210
210
  };
211
+ const unresolvedCurrentAuth = (agent) => {
212
+ try {
213
+ const persisted = (0, runtime_readiness_js_1.parsePersistedAuthFailure)(stateStore(agent).readJson("agentState", {}));
214
+ if (!persisted)
215
+ return null;
216
+ // 只使用当前 agentState 作用域标记,不扫描历史 delivery 的 authProvider。
217
+ return (0, runtime_readiness_js_1.authFailureAppliesTo)({
218
+ runtime: agent.runtime,
219
+ model: agent.model,
220
+ piDistribution: agent.piDistribution,
221
+ }, persisted) ? persisted : null;
222
+ }
223
+ catch {
224
+ return null;
225
+ }
226
+ };
227
+ const projectReadyUnlessUnresolvedAuth = (agent, observedAt) => {
228
+ const failure = unresolvedCurrentAuth(agent);
229
+ if (failure) {
230
+ hostState.updateStatus(agent, {
231
+ runtimeReadiness: { ...(0, runtime_readiness_js_1.readinessForPersistedAuthFailure)(failure), observedAt },
232
+ });
233
+ return;
234
+ }
235
+ hostState.updateStatus(agent, { runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt } });
236
+ };
211
237
  const recordInboundDeliveryFailure = (agent, code) => {
212
238
  const at = new Date().toISOString();
213
239
  const reason = "Inbound Runtime delivery failed non-retryably; durable Inbox/ledger state is retained for recovery.";
214
240
  const nextAction = "Inspect the delivery/status error, correct the Runtime or canonical Inbox state, then restart to replay safely.";
241
+ const currentReadiness = hostState.readStatus(agent).runtimeReadiness;
215
242
  hostState.updateStatus(agent, {
216
243
  inboundDeliveryHealth: { state: "error", code, at, reason, nextAction },
217
- runtimeReadiness: { runtime: agent.runtime, state: "incompatible", reason, nextAction },
244
+ ...(currentReadiness?.state === "unauthenticated" ? {} : {
245
+ runtimeReadiness: { runtime: agent.runtime, state: "incompatible", reason, nextAction },
246
+ }),
218
247
  });
219
248
  hostState.recordStatusError(agent, `${reason} ${nextAction} code=${code}`);
220
249
  };
221
250
  const agentStates = new Map();
251
+ const mergePersistedAgentState = (record) => {
252
+ const latest = record.store.readJson("agentState", {});
253
+ if (isRecord(latest.previousSessions)) {
254
+ record.state.previousSessions = {
255
+ ...latest.previousSessions,
256
+ ...(record.state.previousSessions ?? {}),
257
+ };
258
+ }
259
+ const persisted = (0, runtime_readiness_js_1.parsePersistedAuthFailure)(latest);
260
+ if (persisted) {
261
+ record.state.authFailure = persisted;
262
+ if (persisted.kind === "missing-provider" && persisted.provider) {
263
+ record.state.authFailureProvider = persisted.provider;
264
+ }
265
+ else {
266
+ delete record.state.authFailureProvider;
267
+ }
268
+ }
269
+ else {
270
+ delete record.state.authFailure;
271
+ delete record.state.authFailureProvider;
272
+ }
273
+ };
222
274
  const saveAgentState = (record) => {
223
275
  try {
224
- const latest = record.store.readJson("agentState", {});
225
- if (isRecord(latest.previousSessions)) {
226
- record.state.previousSessions = {
227
- ...latest.previousSessions,
228
- ...(record.state.previousSessions ?? {}),
229
- };
230
- }
276
+ mergePersistedAgentState(record);
231
277
  record.store.writeJson("agentState", record.state);
232
278
  }
233
279
  catch (error) {
@@ -1276,12 +1322,16 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
1276
1322
  observedAt,
1277
1323
  },
1278
1324
  });
1279
- else if (message.readiness)
1280
- hostState.updateStatus(agent, { runtimeReadiness: { ...message.readiness, observedAt } });
1325
+ else if (message.readiness) {
1326
+ if (message.readiness.state === "ready" && unresolvedCurrentAuth(agent)) {
1327
+ projectReadyUnlessUnresolvedAuth(agent, observedAt);
1328
+ }
1329
+ else {
1330
+ hostState.updateStatus(agent, { runtimeReadiness: { ...message.readiness, observedAt } });
1331
+ }
1332
+ }
1281
1333
  else if (message.status === "active")
1282
- hostState.updateStatus(agent, { runtimeReadiness: {
1283
- runtime: agent.runtime, state: "ready", observedAt,
1284
- } });
1334
+ projectReadyUnlessUnresolvedAuth(agent, observedAt);
1285
1335
  if (message.status === "active") {
1286
1336
  const redeliveryTimer = setTimeout(() => {
1287
1337
  void reminder.redeliverUnread(agent).catch((error) => log("启动补投失败", errorMessage(error)));
@@ -1448,6 +1498,7 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
1448
1498
  record.state.sessions[agent.runtime] = reset.sessionId;
1449
1499
  else
1450
1500
  delete record.state.sessions[agent.runtime];
1501
+ mergePersistedAgentState(record);
1451
1502
  record.store.writeJson("agentState", record.state);
1452
1503
  }
1453
1504
  if (!reset.sessionId) {
@@ -1455,11 +1506,11 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
1455
1506
  hostState.updateStatus(agent, {
1456
1507
  session: { runtime: agent.runtime, id: null, launchId: null, startedAt: observedAt,
1457
1508
  lastSeenAt: null, lastTurnAt: null, turns: 0 },
1458
- runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt },
1459
1509
  });
1510
+ projectReadyUnlessUnresolvedAuth(agent, observedAt);
1460
1511
  }
1461
1512
  else if (reset.runtimeReady) {
1462
- hostState.updateStatus(agent, { runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt: new Date().toISOString() } });
1513
+ projectReadyUnlessUnresolvedAuth(agent, new Date().toISOString());
1463
1514
  }
1464
1515
  }
1465
1516
  catch (error) {
@@ -1527,7 +1578,7 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
1527
1578
  throw Object.assign(error instanceof Error ? error : new Error(String(error)), readinessProjection());
1528
1579
  }
1529
1580
  if (recovery.runtimeReady) {
1530
- hostState.updateStatus(agent, { runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt: new Date().toISOString() } });
1581
+ projectReadyUnlessUnresolvedAuth(agent, new Date().toISOString());
1531
1582
  }
1532
1583
  const record = agentStates.get(agentId);
1533
1584
  try {
@@ -1536,6 +1587,7 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
1536
1587
  record.state.sessions[agent.runtime] = recovery.sessionId;
1537
1588
  else
1538
1589
  delete record.state.sessions[agent.runtime];
1590
+ mergePersistedAgentState(record);
1539
1591
  record.store.writeJson("agentState", record.state);
1540
1592
  }
1541
1593
  }
@@ -16,7 +16,7 @@ import { inboxAuditRegistryFile, observeInboxAuditTarget } from "../agent/missed
16
16
  import { HostChannelBusiness } from "./host-channel-business.mjs";
17
17
  import { HostInteractionOrchestrator } from "./interaction-orchestrator.mjs";
18
18
  import { targetFor } from "./message-policy.mjs";
19
- import { providerAuthenticationFailureReadiness, RuntimePrerequisiteError } from "../runtime/runtime-readiness.mjs";
19
+ import { authFailureAppliesTo, parsePersistedAuthFailure, providerAuthenticationFailureReadiness, readinessForPersistedAuthFailure, RuntimePrerequisiteError, } from "../runtime/runtime-readiness.mjs";
20
20
  import { readDocumentCommentSubscription, verifyCallbackProbe } from "../platform/callback-capability.mjs";
21
21
  import { loadConfig, resolveMentionPolicy } from "../platform/config.mjs";
22
22
  import { processCommandToken } from "../app/internal-command.mjs";
@@ -168,26 +168,72 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
168
168
  runtimeReadiness: { runtime: agent.runtime, state: "unavailable", reason, nextAction: "Wait for the current daemon epoch to publish Runtime readiness.", observedAt: new Date().toISOString() },
169
169
  });
170
170
  };
171
+ const unresolvedCurrentAuth = (agent) => {
172
+ try {
173
+ const persisted = parsePersistedAuthFailure(stateStore(agent).readJson("agentState", {}));
174
+ if (!persisted)
175
+ return null;
176
+ // 只使用当前 agentState 作用域标记,不扫描历史 delivery 的 authProvider。
177
+ return authFailureAppliesTo({
178
+ runtime: agent.runtime,
179
+ model: agent.model,
180
+ piDistribution: agent.piDistribution,
181
+ }, persisted) ? persisted : null;
182
+ }
183
+ catch {
184
+ return null;
185
+ }
186
+ };
187
+ const projectReadyUnlessUnresolvedAuth = (agent, observedAt) => {
188
+ const failure = unresolvedCurrentAuth(agent);
189
+ if (failure) {
190
+ hostState.updateStatus(agent, {
191
+ runtimeReadiness: { ...readinessForPersistedAuthFailure(failure), observedAt },
192
+ });
193
+ return;
194
+ }
195
+ hostState.updateStatus(agent, { runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt } });
196
+ };
171
197
  const recordInboundDeliveryFailure = (agent, code) => {
172
198
  const at = new Date().toISOString();
173
199
  const reason = "Inbound Runtime delivery failed non-retryably; durable Inbox/ledger state is retained for recovery.";
174
200
  const nextAction = "Inspect the delivery/status error, correct the Runtime or canonical Inbox state, then restart to replay safely.";
201
+ const currentReadiness = hostState.readStatus(agent).runtimeReadiness;
175
202
  hostState.updateStatus(agent, {
176
203
  inboundDeliveryHealth: { state: "error", code, at, reason, nextAction },
177
- runtimeReadiness: { runtime: agent.runtime, state: "incompatible", reason, nextAction },
204
+ ...(currentReadiness?.state === "unauthenticated" ? {} : {
205
+ runtimeReadiness: { runtime: agent.runtime, state: "incompatible", reason, nextAction },
206
+ }),
178
207
  });
179
208
  hostState.recordStatusError(agent, `${reason} ${nextAction} code=${code}`);
180
209
  };
181
210
  const agentStates = new Map();
211
+ const mergePersistedAgentState = (record) => {
212
+ const latest = record.store.readJson("agentState", {});
213
+ if (isRecord(latest.previousSessions)) {
214
+ record.state.previousSessions = {
215
+ ...latest.previousSessions,
216
+ ...(record.state.previousSessions ?? {}),
217
+ };
218
+ }
219
+ const persisted = parsePersistedAuthFailure(latest);
220
+ if (persisted) {
221
+ record.state.authFailure = persisted;
222
+ if (persisted.kind === "missing-provider" && persisted.provider) {
223
+ record.state.authFailureProvider = persisted.provider;
224
+ }
225
+ else {
226
+ delete record.state.authFailureProvider;
227
+ }
228
+ }
229
+ else {
230
+ delete record.state.authFailure;
231
+ delete record.state.authFailureProvider;
232
+ }
233
+ };
182
234
  const saveAgentState = (record) => {
183
235
  try {
184
- const latest = record.store.readJson("agentState", {});
185
- if (isRecord(latest.previousSessions)) {
186
- record.state.previousSessions = {
187
- ...latest.previousSessions,
188
- ...(record.state.previousSessions ?? {}),
189
- };
190
- }
236
+ mergePersistedAgentState(record);
191
237
  record.store.writeJson("agentState", record.state);
192
238
  }
193
239
  catch (error) {
@@ -1236,12 +1282,16 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
1236
1282
  observedAt,
1237
1283
  },
1238
1284
  });
1239
- else if (message.readiness)
1240
- hostState.updateStatus(agent, { runtimeReadiness: { ...message.readiness, observedAt } });
1285
+ else if (message.readiness) {
1286
+ if (message.readiness.state === "ready" && unresolvedCurrentAuth(agent)) {
1287
+ projectReadyUnlessUnresolvedAuth(agent, observedAt);
1288
+ }
1289
+ else {
1290
+ hostState.updateStatus(agent, { runtimeReadiness: { ...message.readiness, observedAt } });
1291
+ }
1292
+ }
1241
1293
  else if (message.status === "active")
1242
- hostState.updateStatus(agent, { runtimeReadiness: {
1243
- runtime: agent.runtime, state: "ready", observedAt,
1244
- } });
1294
+ projectReadyUnlessUnresolvedAuth(agent, observedAt);
1245
1295
  if (message.status === "active") {
1246
1296
  const redeliveryTimer = setTimeout(() => {
1247
1297
  void reminder.redeliverUnread(agent).catch((error) => log("启动补投失败", errorMessage(error)));
@@ -1408,6 +1458,7 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
1408
1458
  record.state.sessions[agent.runtime] = reset.sessionId;
1409
1459
  else
1410
1460
  delete record.state.sessions[agent.runtime];
1461
+ mergePersistedAgentState(record);
1411
1462
  record.store.writeJson("agentState", record.state);
1412
1463
  }
1413
1464
  if (!reset.sessionId) {
@@ -1415,11 +1466,11 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
1415
1466
  hostState.updateStatus(agent, {
1416
1467
  session: { runtime: agent.runtime, id: null, launchId: null, startedAt: observedAt,
1417
1468
  lastSeenAt: null, lastTurnAt: null, turns: 0 },
1418
- runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt },
1419
1469
  });
1470
+ projectReadyUnlessUnresolvedAuth(agent, observedAt);
1420
1471
  }
1421
1472
  else if (reset.runtimeReady) {
1422
- hostState.updateStatus(agent, { runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt: new Date().toISOString() } });
1473
+ projectReadyUnlessUnresolvedAuth(agent, new Date().toISOString());
1423
1474
  }
1424
1475
  }
1425
1476
  catch (error) {
@@ -1487,7 +1538,7 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
1487
1538
  throw Object.assign(error instanceof Error ? error : new Error(String(error)), readinessProjection());
1488
1539
  }
1489
1540
  if (recovery.runtimeReady) {
1490
- hostState.updateStatus(agent, { runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt: new Date().toISOString() } });
1541
+ projectReadyUnlessUnresolvedAuth(agent, new Date().toISOString());
1491
1542
  }
1492
1543
  const record = agentStates.get(agentId);
1493
1544
  try {
@@ -1496,6 +1547,7 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
1496
1547
  record.state.sessions[agent.runtime] = recovery.sessionId;
1497
1548
  else
1498
1549
  delete record.state.sessions[agent.runtime];
1550
+ mergePersistedAgentState(record);
1499
1551
  record.store.writeJson("agentState", record.state);
1500
1552
  }
1501
1553
  }
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.toUserRuntime = exports.runtimeOptionTarget = exports.runtimeOptionOf = exports.isUserRuntime = exports.isAdapterRuntime = exports.fromUserRuntime = exports.RUNTIME_OPTIONS = void 0;
39
40
  exports.loadRuntimeModels = loadRuntimeModels;
40
41
  exports.defaultModelFor = defaultModelFor;
41
42
  exports.resolveConfigDir = resolveConfigDir;
@@ -52,6 +53,8 @@ exports.resolveAgentGlobalMentionPolicy = resolveAgentGlobalMentionPolicy;
52
53
  exports.runtimeConfigSignature = runtimeConfigSignature;
53
54
  exports.configApplyState = configApplyState;
54
55
  exports.markConfigApplied = markConfigApplied;
56
+ exports.withConfigMutationLock = withConfigMutationLock;
57
+ exports.withConfigMutationLockAsync = withConfigMutationLockAsync;
55
58
  exports.mutateConfig = mutateConfig;
56
59
  exports.rollbackConfig = rollbackConfig;
57
60
  exports.commitSetupConfig = commitSetupConfig;
@@ -63,8 +66,18 @@ const path = __importStar(require("node:path"));
63
66
  const root_layout_js_1 = require("./root-layout.cjs");
64
67
  const secure_metadata_js_1 = require("./secure-metadata.cjs");
65
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
+ const user_runtime_js_1 = require("../runtime/user-runtime.cjs");
66
71
  const process_inspect_cjs_1 = __importDefault(require("./process-inspect.cjs"));
67
72
  const pi_profile_migration_js_1 = require("../runtime/pi-profile-migration.cjs");
73
+ var user_runtime_js_2 = require("../runtime/user-runtime.cjs");
74
+ Object.defineProperty(exports, "RUNTIME_OPTIONS", { enumerable: true, get: function () { return user_runtime_js_2.RUNTIME_OPTIONS; } });
75
+ Object.defineProperty(exports, "fromUserRuntime", { enumerable: true, get: function () { return user_runtime_js_2.fromUserRuntime; } });
76
+ Object.defineProperty(exports, "isAdapterRuntime", { enumerable: true, get: function () { return user_runtime_js_2.isAdapterRuntime; } });
77
+ Object.defineProperty(exports, "isUserRuntime", { enumerable: true, get: function () { return user_runtime_js_2.isUserRuntime; } });
78
+ Object.defineProperty(exports, "runtimeOptionOf", { enumerable: true, get: function () { return user_runtime_js_2.runtimeOptionOf; } });
79
+ Object.defineProperty(exports, "runtimeOptionTarget", { enumerable: true, get: function () { return user_runtime_js_2.runtimeOptionTarget; } });
80
+ Object.defineProperty(exports, "toUserRuntime", { enumerable: true, get: function () { return user_runtime_js_2.toUserRuntime; } });
68
81
  const TOP_FIELDS_V3 = new Set(["version", "serverId", "activeAgent", "agents"]);
69
82
  const TOP_FIELDS_V4 = new Set(["version", "serverId", "mentionPolicy", "activeAgent", "agents"]);
70
83
  const AGENT_FIELDS_V3 = new Set(["runtime", "model", "effort", "noMentionChats", "createdAt"]);
@@ -105,7 +118,8 @@ function loadRuntimeModels() {
105
118
  return runtimeModels;
106
119
  }
107
120
  function defaultModelFor(runtime) {
108
- const models = loadRuntimeModels()[runtime];
121
+ const adapter = (0, user_runtime_js_1.isUserRuntime)(runtime) ? (0, user_runtime_js_1.fromUserRuntime)(runtime).runtime : runtime;
122
+ const models = loadRuntimeModels()[adapter];
109
123
  if (!models)
110
124
  throw new Error(`runtime 模型目录不存在 runtime: ${runtime}`);
111
125
  return models[0].id;
@@ -153,7 +167,7 @@ function validateStoredAgent(key, agent, version) {
153
167
  assertAllowedFields(agent, version === 3 ? AGENT_FIELDS_V3 : AGENT_FIELDS_V4, `Agent ${key}`);
154
168
  if (typeof agent.runtime !== "string" || !agent.runtime)
155
169
  throw new Error(`Agent ${key}.runtime 必须是非空字符串`);
156
- if (!loadRuntimeModels()[agent.runtime])
170
+ if (!(0, user_runtime_js_1.isAdapterRuntime)(agent.runtime))
157
171
  throw new Error(`Agent ${key}.runtime 未知:${agent.runtime}`);
158
172
  if (typeof agent.model !== "string" || !agent.model)
159
173
  throw new Error(`Agent ${key}.model 必须是非空字符串`);
@@ -632,16 +646,30 @@ function applyMutation(config, mutation) {
632
646
  agent.piDistribution = mutation.distribution;
633
647
  }
634
648
  else if (mutation.kind === "set-agent-runtime") {
635
- if (!loadRuntimeModels()[mutation.runtime])
649
+ if (!(0, user_runtime_js_1.isUserRuntime)(mutation.runtime))
636
650
  throw new Error(`未知 runtime:${mutation.runtime}`);
637
- assertModel(mutation.runtime, mutation.model || "default");
638
- agent.runtime = mutation.runtime;
651
+ 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
+ assertModel(stored.runtime, mutation.model || "default");
661
+ agent.runtime = stored.runtime;
639
662
  agent.model = mutation.model || "default";
640
- if (mutation.runtime !== "pi")
663
+ if (stored.piDistribution)
664
+ agent.piDistribution = stored.piDistribution;
665
+ else
641
666
  delete agent.piDistribution;
642
667
  delete agent.effort;
643
668
  }
644
669
  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
+ }
645
673
  if (!mutation.model.trim())
646
674
  throw new Error("model 不能为空");
647
675
  assertModel(agent.runtime, mutation.model);
@@ -792,7 +820,7 @@ function assertConfigRoot(root) {
792
820
  if (typeof process.getuid === "function" && stat.uid !== process.getuid())
793
821
  throw new Error("config root owner is unsafe");
794
822
  }
795
- function withConfigLock(layout, action) {
823
+ function acquireConfigLock(layout) {
796
824
  assertNoSymlinkAncestors(layout.root);
797
825
  fs.mkdirSync(layout.root, { recursive: true, mode: 0o700 });
798
826
  const rootStat = fs.lstatSync(layout.root);
@@ -831,20 +859,43 @@ function withConfigLock(layout, action) {
831
859
  }
832
860
  if (!acquired)
833
861
  throw new Error("配置正被其他进程修改,请稍后重试");
862
+ return {
863
+ release() {
864
+ const current = readLockRecord(lock);
865
+ if (current?.record?.nonce === owner.nonce && current.record.pid === owner.pid) {
866
+ try {
867
+ fs.unlinkSync(lock);
868
+ (0, secure_metadata_js_1.fsyncDirectoryOf)(lock);
869
+ }
870
+ catch { /* best effort */ }
871
+ }
872
+ },
873
+ };
874
+ }
875
+ function withConfigLock(layout, action) {
876
+ const held = acquireConfigLock(layout);
834
877
  try {
835
878
  recoverMigrationJournal(layout.root);
836
879
  recoverRollbackJournal(layout.root);
837
880
  return action();
838
881
  }
839
882
  finally {
840
- const current = readLockRecord(lock);
841
- if (current?.record?.nonce === owner.nonce && current.record.pid === owner.pid) {
842
- try {
843
- fs.unlinkSync(lock);
844
- (0, secure_metadata_js_1.fsyncDirectoryOf)(lock);
845
- }
846
- catch { /* best effort */ }
847
- }
883
+ held.release();
884
+ }
885
+ }
886
+ function withConfigMutationLock(env, action) {
887
+ return withConfigLock(root_layout_js_1.TargetRootLayout.fromConfigDir(resolveConfigDir(env)), action);
888
+ }
889
+ async function withConfigMutationLockAsync(env, action) {
890
+ const layout = root_layout_js_1.TargetRootLayout.fromConfigDir(resolveConfigDir(env));
891
+ const held = acquireConfigLock(layout);
892
+ try {
893
+ recoverMigrationJournal(layout.root);
894
+ recoverRollbackJournal(layout.root);
895
+ return await action();
896
+ }
897
+ finally {
898
+ held.release();
848
899
  }
849
900
  }
850
901
  const CONFIG_ROLLBACK_JOURNAL_FILE = ".config-rollback-journal.json";
@@ -1105,7 +1156,7 @@ function commitSetupConfig(env, expectedRevision, nextStored) {
1105
1156
  function safeConfigView(config, onlyAgentId, chatId, applyState) {
1106
1157
  const entries = onlyAgentId ? [[onlyAgentId, config.agents[onlyAgentId]]] : Object.entries(config.agents);
1107
1158
  const agents = entries.filter((entry) => Boolean(entry[1])).map(([agentId, agent]) => ({
1108
- agentId, runtime: agent.runtime, model: agent.model, piDistribution: agent.piDistribution ?? (agent.runtime === "pi" ? "external" : null), effort: agent.effort ?? null,
1159
+ agentId, runtime: agent.runtime, runtimeOption: (0, user_runtime_js_1.toUserRuntime)(agent.runtime, agent.piDistribution), model: agent.model, piDistribution: agent.piDistribution ?? (agent.runtime === "pi" ? "external" : null), effort: agent.effort ?? null,
1109
1160
  mention: {
1110
1161
  override: agent.mentionPolicy ?? "inherit",
1111
1162
  effective: agent.mentionPolicy ?? config.mentionPolicy,
@@ -5,8 +5,11 @@ 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
+ import { fromUserRuntime, isAdapterRuntime, isUserRuntime, toUserRuntime } from "../runtime/user-runtime.mjs";
8
10
  import processInspect from "./process-inspect.cjs";
9
11
  import { applyPiProfileMigration, assertPiProfileMigrationAfterState, preparePiProfileMigration, releasePiProfileMigrationLock, rollbackPiProfileMigration, validatePiProfileMigrationState, } from "../runtime/pi-profile-migration.mjs";
12
+ export { RUNTIME_OPTIONS, fromUserRuntime, isAdapterRuntime, isUserRuntime, runtimeOptionOf, runtimeOptionTarget, toUserRuntime, } from "../runtime/user-runtime.mjs";
10
13
  const TOP_FIELDS_V3 = new Set(["version", "serverId", "activeAgent", "agents"]);
11
14
  const TOP_FIELDS_V4 = new Set(["version", "serverId", "mentionPolicy", "activeAgent", "agents"]);
12
15
  const AGENT_FIELDS_V3 = new Set(["runtime", "model", "effort", "noMentionChats", "createdAt"]);
@@ -47,7 +50,8 @@ export function loadRuntimeModels() {
47
50
  return runtimeModels;
48
51
  }
49
52
  export function defaultModelFor(runtime) {
50
- const models = loadRuntimeModels()[runtime];
53
+ const adapter = isUserRuntime(runtime) ? fromUserRuntime(runtime).runtime : runtime;
54
+ const models = loadRuntimeModels()[adapter];
51
55
  if (!models)
52
56
  throw new Error(`runtime 模型目录不存在 runtime: ${runtime}`);
53
57
  return models[0].id;
@@ -95,7 +99,7 @@ function validateStoredAgent(key, agent, version) {
95
99
  assertAllowedFields(agent, version === 3 ? AGENT_FIELDS_V3 : AGENT_FIELDS_V4, `Agent ${key}`);
96
100
  if (typeof agent.runtime !== "string" || !agent.runtime)
97
101
  throw new Error(`Agent ${key}.runtime 必须是非空字符串`);
98
- if (!loadRuntimeModels()[agent.runtime])
102
+ if (!isAdapterRuntime(agent.runtime))
99
103
  throw new Error(`Agent ${key}.runtime 未知:${agent.runtime}`);
100
104
  if (typeof agent.model !== "string" || !agent.model)
101
105
  throw new Error(`Agent ${key}.model 必须是非空字符串`);
@@ -574,16 +578,30 @@ function applyMutation(config, mutation) {
574
578
  agent.piDistribution = mutation.distribution;
575
579
  }
576
580
  else if (mutation.kind === "set-agent-runtime") {
577
- if (!loadRuntimeModels()[mutation.runtime])
581
+ if (!isUserRuntime(mutation.runtime))
578
582
  throw new Error(`未知 runtime:${mutation.runtime}`);
579
- assertModel(mutation.runtime, mutation.model || "default");
580
- agent.runtime = mutation.runtime;
583
+ 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
+ assertModel(stored.runtime, mutation.model || "default");
593
+ agent.runtime = stored.runtime;
581
594
  agent.model = mutation.model || "default";
582
- if (mutation.runtime !== "pi")
595
+ if (stored.piDistribution)
596
+ agent.piDistribution = stored.piDistribution;
597
+ else
583
598
  delete agent.piDistribution;
584
599
  delete agent.effort;
585
600
  }
586
601
  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
+ }
587
605
  if (!mutation.model.trim())
588
606
  throw new Error("model 不能为空");
589
607
  assertModel(agent.runtime, mutation.model);
@@ -734,7 +752,7 @@ function assertConfigRoot(root) {
734
752
  if (typeof process.getuid === "function" && stat.uid !== process.getuid())
735
753
  throw new Error("config root owner is unsafe");
736
754
  }
737
- function withConfigLock(layout, action) {
755
+ function acquireConfigLock(layout) {
738
756
  assertNoSymlinkAncestors(layout.root);
739
757
  fs.mkdirSync(layout.root, { recursive: true, mode: 0o700 });
740
758
  const rootStat = fs.lstatSync(layout.root);
@@ -773,20 +791,43 @@ function withConfigLock(layout, action) {
773
791
  }
774
792
  if (!acquired)
775
793
  throw new Error("配置正被其他进程修改,请稍后重试");
794
+ return {
795
+ release() {
796
+ const current = readLockRecord(lock);
797
+ if (current?.record?.nonce === owner.nonce && current.record.pid === owner.pid) {
798
+ try {
799
+ fs.unlinkSync(lock);
800
+ fsyncDirectoryOf(lock);
801
+ }
802
+ catch { /* best effort */ }
803
+ }
804
+ },
805
+ };
806
+ }
807
+ function withConfigLock(layout, action) {
808
+ const held = acquireConfigLock(layout);
776
809
  try {
777
810
  recoverMigrationJournal(layout.root);
778
811
  recoverRollbackJournal(layout.root);
779
812
  return action();
780
813
  }
781
814
  finally {
782
- const current = readLockRecord(lock);
783
- if (current?.record?.nonce === owner.nonce && current.record.pid === owner.pid) {
784
- try {
785
- fs.unlinkSync(lock);
786
- fsyncDirectoryOf(lock);
787
- }
788
- catch { /* best effort */ }
789
- }
815
+ held.release();
816
+ }
817
+ }
818
+ export function withConfigMutationLock(env, action) {
819
+ return withConfigLock(TargetRootLayout.fromConfigDir(resolveConfigDir(env)), action);
820
+ }
821
+ export async function withConfigMutationLockAsync(env, action) {
822
+ const layout = TargetRootLayout.fromConfigDir(resolveConfigDir(env));
823
+ const held = acquireConfigLock(layout);
824
+ try {
825
+ recoverMigrationJournal(layout.root);
826
+ recoverRollbackJournal(layout.root);
827
+ return await action();
828
+ }
829
+ finally {
830
+ held.release();
790
831
  }
791
832
  }
792
833
  const CONFIG_ROLLBACK_JOURNAL_FILE = ".config-rollback-journal.json";
@@ -1047,7 +1088,7 @@ export function commitSetupConfig(env, expectedRevision, nextStored) {
1047
1088
  export function safeConfigView(config, onlyAgentId, chatId, applyState) {
1048
1089
  const entries = onlyAgentId ? [[onlyAgentId, config.agents[onlyAgentId]]] : Object.entries(config.agents);
1049
1090
  const agents = entries.filter((entry) => Boolean(entry[1])).map(([agentId, agent]) => ({
1050
- agentId, runtime: agent.runtime, model: agent.model, piDistribution: agent.piDistribution ?? (agent.runtime === "pi" ? "external" : null), effort: agent.effort ?? null,
1091
+ agentId, runtime: agent.runtime, runtimeOption: toUserRuntime(agent.runtime, agent.piDistribution), model: agent.model, piDistribution: agent.piDistribution ?? (agent.runtime === "pi" ? "external" : null), effort: agent.effort ?? null,
1051
1092
  mention: {
1052
1093
  override: agent.mentionPolicy ?? "inherit",
1053
1094
  effective: agent.mentionPolicy ?? config.mentionPolicy,