larkin 0.4.28 → 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.
- package/dist/app/agent-config.mjs +3 -1
- package/dist/app/cli.mjs +13 -3
- package/dist/app/local-control.mjs +16 -3
- package/dist/app/pi-auth-cli.mjs +227 -17
- package/dist/app/runtime-process.mjs +42 -5
- package/dist/dashboard/dashboard-config-controller.mjs +130 -5
- package/dist/dashboard/web/assets/dashboard.css +1 -1
- package/dist/dashboard/web/assets/dashboard.js +15 -15
- package/dist/feishu/host-shell.cjs +68 -16
- package/dist/feishu/host-shell.mjs +69 -17
- package/dist/platform/config.cjs +38 -10
- package/dist/platform/config.mjs +36 -10
- package/dist/runtime/pi-official-auth.cjs +594 -0
- package/dist/runtime/pi-official-auth.mjs +53 -0
- package/dist/runtime/pi-provider-config.cjs +26 -3
- package/dist/runtime/pi-provider-config.mjs +22 -2
- package/dist/runtime/pi-provider-login.mjs +240 -0
- package/dist/runtime/runtime-adapters.mjs +24 -4
- package/dist/runtime/runtime-host.cjs +215 -16
- package/dist/runtime/runtime-host.mjs +216 -17
- package/dist/runtime/runtime-readiness.cjs +123 -3
- package/dist/runtime/runtime-readiness.mjs +116 -4
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/platform/config.cjs
CHANGED
|
@@ -53,6 +53,8 @@ exports.resolveAgentGlobalMentionPolicy = resolveAgentGlobalMentionPolicy;
|
|
|
53
53
|
exports.runtimeConfigSignature = runtimeConfigSignature;
|
|
54
54
|
exports.configApplyState = configApplyState;
|
|
55
55
|
exports.markConfigApplied = markConfigApplied;
|
|
56
|
+
exports.withConfigMutationLock = withConfigMutationLock;
|
|
57
|
+
exports.withConfigMutationLockAsync = withConfigMutationLockAsync;
|
|
56
58
|
exports.mutateConfig = mutateConfig;
|
|
57
59
|
exports.rollbackConfig = rollbackConfig;
|
|
58
60
|
exports.commitSetupConfig = commitSetupConfig;
|
|
@@ -652,7 +654,7 @@ function applyMutation(config, mutation) {
|
|
|
652
654
|
(0, pi_provider_config_js_1.assertBuiltinPiAgentDirectory)((0, pi_provider_config_js_1.piAgentDirectory)(config.configDir, mutation.agentId));
|
|
653
655
|
}
|
|
654
656
|
catch (error) {
|
|
655
|
-
throw new Error(`无法切换到 builtin-pi:${error instanceof Error ? error.message : String(error)}
|
|
657
|
+
throw new Error(`无法切换到 builtin-pi:${error instanceof Error ? error.message : String(error)}。请使用 Dashboard Provider Credentials 或 larkin pi-auth login。`);
|
|
656
658
|
}
|
|
657
659
|
}
|
|
658
660
|
assertModel(stored.runtime, mutation.model || "default");
|
|
@@ -665,6 +667,9 @@ function applyMutation(config, mutation) {
|
|
|
665
667
|
delete agent.effort;
|
|
666
668
|
}
|
|
667
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
|
+
}
|
|
668
673
|
if (!mutation.model.trim())
|
|
669
674
|
throw new Error("model 不能为空");
|
|
670
675
|
assertModel(agent.runtime, mutation.model);
|
|
@@ -815,7 +820,7 @@ function assertConfigRoot(root) {
|
|
|
815
820
|
if (typeof process.getuid === "function" && stat.uid !== process.getuid())
|
|
816
821
|
throw new Error("config root owner is unsafe");
|
|
817
822
|
}
|
|
818
|
-
function
|
|
823
|
+
function acquireConfigLock(layout) {
|
|
819
824
|
assertNoSymlinkAncestors(layout.root);
|
|
820
825
|
fs.mkdirSync(layout.root, { recursive: true, mode: 0o700 });
|
|
821
826
|
const rootStat = fs.lstatSync(layout.root);
|
|
@@ -854,20 +859,43 @@ function withConfigLock(layout, action) {
|
|
|
854
859
|
}
|
|
855
860
|
if (!acquired)
|
|
856
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);
|
|
857
877
|
try {
|
|
858
878
|
recoverMigrationJournal(layout.root);
|
|
859
879
|
recoverRollbackJournal(layout.root);
|
|
860
880
|
return action();
|
|
861
881
|
}
|
|
862
882
|
finally {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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();
|
|
871
899
|
}
|
|
872
900
|
}
|
|
873
901
|
const CONFIG_ROLLBACK_JOURNAL_FILE = ".config-rollback-journal.json";
|
package/dist/platform/config.mjs
CHANGED
|
@@ -586,7 +586,7 @@ function applyMutation(config, mutation) {
|
|
|
586
586
|
assertBuiltinPiAgentDirectory(piAgentDirectory(config.configDir, mutation.agentId));
|
|
587
587
|
}
|
|
588
588
|
catch (error) {
|
|
589
|
-
throw new Error(`无法切换到 builtin-pi:${error instanceof Error ? error.message : String(error)}
|
|
589
|
+
throw new Error(`无法切换到 builtin-pi:${error instanceof Error ? error.message : String(error)}。请使用 Dashboard Provider Credentials 或 larkin pi-auth login。`);
|
|
590
590
|
}
|
|
591
591
|
}
|
|
592
592
|
assertModel(stored.runtime, mutation.model || "default");
|
|
@@ -599,6 +599,9 @@ function applyMutation(config, mutation) {
|
|
|
599
599
|
delete agent.effort;
|
|
600
600
|
}
|
|
601
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
|
+
}
|
|
602
605
|
if (!mutation.model.trim())
|
|
603
606
|
throw new Error("model 不能为空");
|
|
604
607
|
assertModel(agent.runtime, mutation.model);
|
|
@@ -749,7 +752,7 @@ function assertConfigRoot(root) {
|
|
|
749
752
|
if (typeof process.getuid === "function" && stat.uid !== process.getuid())
|
|
750
753
|
throw new Error("config root owner is unsafe");
|
|
751
754
|
}
|
|
752
|
-
function
|
|
755
|
+
function acquireConfigLock(layout) {
|
|
753
756
|
assertNoSymlinkAncestors(layout.root);
|
|
754
757
|
fs.mkdirSync(layout.root, { recursive: true, mode: 0o700 });
|
|
755
758
|
const rootStat = fs.lstatSync(layout.root);
|
|
@@ -788,20 +791,43 @@ function withConfigLock(layout, action) {
|
|
|
788
791
|
}
|
|
789
792
|
if (!acquired)
|
|
790
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);
|
|
791
809
|
try {
|
|
792
810
|
recoverMigrationJournal(layout.root);
|
|
793
811
|
recoverRollbackJournal(layout.root);
|
|
794
812
|
return action();
|
|
795
813
|
}
|
|
796
814
|
finally {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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();
|
|
805
831
|
}
|
|
806
832
|
}
|
|
807
833
|
const CONFIG_ROLLBACK_JOURNAL_FILE = ".config-rollback-journal.json";
|