larkin 0.5.1 → 0.5.3
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 +8 -0
- package/dist/dashboard/dashboard-config-controller.mjs +2 -3
- package/dist/feishu/host-shell.cjs +47 -14
- package/dist/feishu/host-shell.mjs +47 -14
- package/dist/runtime/pi-compaction-recovery.cjs +46 -27
- package/dist/runtime/pi-compaction-recovery.mjs +45 -26
- package/dist/runtime/pi-model-catalog.cjs +3 -39
- package/dist/runtime/pi-model-catalog.mjs +3 -6
- package/dist/runtime/pi-subagent-injection.mjs +42 -14
- package/dist/runtime/provider-error-classifier.cjs +14 -0
- package/dist/runtime/provider-error-classifier.mjs +13 -0
- package/dist/runtime/runtime-adapters.mjs +69 -56
- package/dist/runtime/runtime-host.cjs +46 -26
- package/dist/runtime/runtime-host.mjs +46 -26
- package/dist/runtime/runtime-readiness.cjs +2 -0
- package/dist/runtime/runtime-readiness.mjs +2 -0
- package/package.json +1 -1
- package/dist/runtime/pi-artifact-provenance.mjs +0 -139
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.3
|
|
4
|
+
|
|
5
|
+
External Pi initialization now runs with bounded concurrency and recovers from isolated startup-probe timeouts through the existing retry policy. Shutdown waits for pending initialization and closes late sessions. Generic Runtime and delivery failures show unavailable instead of incompatible, while explicit prerequisite failures and useful provider diagnostics are preserved. Recovery workflow tests wait for delivery consumption and Inbox watermarks to converge and isolate temporary state.
|
|
6
|
+
|
|
7
|
+
## 0.5.2
|
|
8
|
+
|
|
9
|
+
External pi now runs with the user's own Pi home; compaction settings move to the Agent workspace's `.pi/settings.json`; 0.5.0/0.5.1 started external pi with an empty agent dir and saw no logins. Stock pi 0.84.x does not emit `get_state.compactionCapabilities` (that handshake only existed for the bundled build); Larkin accepts the absence and keeps native compaction via the workspace settings file, while a present handshake must still match exactly.
|
|
10
|
+
|
|
3
11
|
## 0.5.1
|
|
4
12
|
|
|
5
13
|
External pi is accepted at 0.84.2 or newer instead of exactly 0.84.2; 0.5.0 refused newer pi and left every migrated Agent not ready.
|
|
@@ -200,7 +200,7 @@ export function createPiModelDirectoryResolver(options = {}) {
|
|
|
200
200
|
}
|
|
201
201
|
},
|
|
202
202
|
async resolve(input) {
|
|
203
|
-
const key = [input.agentId, input.cwd, input.
|
|
203
|
+
const key = [input.agentId, input.cwd, input.command ?? "", ...(input.commandArgs ?? [])].join("\u0000");
|
|
204
204
|
const started = generationOf(input.agentId);
|
|
205
205
|
pruneCache(cache, (entry) => now() >= entry.expiresAt, 64);
|
|
206
206
|
pruneCache(failures, (entry) => now() >= entry.expiresAt, 64);
|
|
@@ -215,7 +215,6 @@ export function createPiModelDirectoryResolver(options = {}) {
|
|
|
215
215
|
try {
|
|
216
216
|
const catalog = await discover({
|
|
217
217
|
cwd: input.cwd,
|
|
218
|
-
...(input.agentDir ? { agentDir: input.agentDir } : {}),
|
|
219
218
|
...(input.command ? { command: input.command } : {}),
|
|
220
219
|
...(input.commandArgs ? { commandArgs: input.commandArgs } : {}),
|
|
221
220
|
});
|
|
@@ -424,7 +423,7 @@ export function createDashboardConfigController({ csrfCapability, env = process.
|
|
|
424
423
|
const agent = config.agents[agentId];
|
|
425
424
|
if (!agent)
|
|
426
425
|
throw new Error("unknown agent");
|
|
427
|
-
// 外部 `pi --mode rpc` 使用用户自己的 Pi home
|
|
426
|
+
// 外部 `pi --mode rpc` 使用用户自己的 Pi home;catalog 探测不改写子进程 agent 目录。
|
|
428
427
|
return await piModelDirectoryResolver.resolve({
|
|
429
428
|
agentId,
|
|
430
429
|
cwd: agent.workspaceDir,
|
|
@@ -57,6 +57,7 @@ const host_channel_business_js_1 = require("./host-channel-business.cjs");
|
|
|
57
57
|
const interaction_orchestrator_js_1 = require("./interaction-orchestrator.cjs");
|
|
58
58
|
const message_policy_js_1 = require("./message-policy.cjs");
|
|
59
59
|
const runtime_readiness_js_1 = require("../runtime/runtime-readiness.cjs");
|
|
60
|
+
const provider_error_classifier_js_1 = require("../runtime/provider-error-classifier.cjs");
|
|
60
61
|
const callback_capability_js_1 = require("../platform/callback-capability.cjs");
|
|
61
62
|
const config_js_1 = require("../platform/config.cjs");
|
|
62
63
|
const internal_command_js_1 = require("../app/internal-command.cjs");
|
|
@@ -119,6 +120,9 @@ function memberNamesFromPayloads(payloads) {
|
|
|
119
120
|
return names;
|
|
120
121
|
}
|
|
121
122
|
function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
|
|
123
|
+
function safeRuntimeStatusDiagnostic(error) {
|
|
124
|
+
return (0, provider_error_classifier_js_1.safeProviderDiagnostic)(error, "Runtime entered an error state", 500);
|
|
125
|
+
}
|
|
122
126
|
function agentConfigSignature(agent) {
|
|
123
127
|
return JSON.stringify({
|
|
124
128
|
agentId: agent.agentId, runtime: agent.runtime, model: agent.model, effort: agent.effort ?? null,
|
|
@@ -233,17 +237,40 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
233
237
|
}
|
|
234
238
|
hostState.updateStatus(agent, { runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt } });
|
|
235
239
|
};
|
|
240
|
+
const projectFallbackRuntimeReadiness = (agent, observedAt, fallback, preserveCurrentUnavailableDiagnostic = false) => {
|
|
241
|
+
const failure = unresolvedCurrentAuth(agent);
|
|
242
|
+
if (failure) {
|
|
243
|
+
hostState.updateStatus(agent, {
|
|
244
|
+
runtimeReadiness: { ...(0, runtime_readiness_js_1.readinessForPersistedAuthFailure)(failure), observedAt },
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const currentReadiness = hostState.readStatus(agent).runtimeReadiness;
|
|
249
|
+
const observedMs = Date.parse(String(currentReadiness?.observedAt || ""));
|
|
250
|
+
const daemonStartedMs = Date.parse(daemonStartedAt);
|
|
251
|
+
const currentEpoch = currentReadiness?.runtime === agent.runtime
|
|
252
|
+
&& Number.isFinite(observedMs) && Number.isFinite(daemonStartedMs) && observedMs >= daemonStartedMs;
|
|
253
|
+
if (currentEpoch && (currentReadiness?.state === "missing" || currentReadiness?.state === "unauthenticated" || currentReadiness?.state === "incompatible"))
|
|
254
|
+
return;
|
|
255
|
+
if (currentEpoch && preserveCurrentUnavailableDiagnostic && currentReadiness?.state === "unavailable"
|
|
256
|
+
&& typeof currentReadiness.reason === "string" && currentReadiness.reason.trim()) {
|
|
257
|
+
hostState.updateStatus(agent, {
|
|
258
|
+
runtimeReadiness: { ...currentReadiness, runtime: agent.runtime, state: "unavailable", observedAt },
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
hostState.updateStatus(agent, {
|
|
263
|
+
runtimeReadiness: { runtime: agent.runtime, ...fallback, observedAt },
|
|
264
|
+
});
|
|
265
|
+
};
|
|
236
266
|
const recordInboundDeliveryFailure = (agent, code) => {
|
|
237
267
|
const at = new Date().toISOString();
|
|
238
268
|
const reason = "Inbound Runtime delivery failed non-retryably; durable Inbox/ledger state is retained for recovery.";
|
|
239
269
|
const nextAction = "Inspect the delivery/status error, correct the Runtime or canonical Inbox state, then restart to replay safely.";
|
|
240
|
-
const currentReadiness = hostState.readStatus(agent).runtimeReadiness;
|
|
241
270
|
hostState.updateStatus(agent, {
|
|
242
271
|
inboundDeliveryHealth: { state: "error", code, at, reason, nextAction },
|
|
243
|
-
...(currentReadiness?.state === "unauthenticated" ? {} : {
|
|
244
|
-
runtimeReadiness: { runtime: agent.runtime, state: "incompatible", reason, nextAction },
|
|
245
|
-
}),
|
|
246
272
|
});
|
|
273
|
+
projectFallbackRuntimeReadiness(agent, at, { state: "unavailable", reason, nextAction }, true);
|
|
247
274
|
hostState.recordStatusError(agent, `${reason} ${nextAction} code=${code}`);
|
|
248
275
|
};
|
|
249
276
|
const agentStates = new Map();
|
|
@@ -1312,15 +1339,21 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1312
1339
|
if (message.type === "agent-status") {
|
|
1313
1340
|
log("agent:status", message.agentId, message.status);
|
|
1314
1341
|
const observedAt = new Date().toISOString();
|
|
1315
|
-
if (message.status === "error" || message.status === "inactive")
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1342
|
+
if (message.status === "error" || message.status === "inactive") {
|
|
1343
|
+
if (message.readiness?.state && message.readiness.state !== "ready") {
|
|
1344
|
+
hostState.updateStatus(agent, { runtimeReadiness: { ...message.readiness, observedAt } });
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
projectFallbackRuntimeReadiness(agent, observedAt, message.status === "error" ? {
|
|
1348
|
+
state: "unavailable",
|
|
1349
|
+
reason: safeRuntimeStatusDiagnostic(message.error),
|
|
1350
|
+
nextAction: "Inspect the Runtime status error, correct the Runtime availability issue, then retry.",
|
|
1351
|
+
} : {
|
|
1352
|
+
state: "missing",
|
|
1353
|
+
reason: "Runtime is inactive",
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1324
1357
|
else if (message.readiness) {
|
|
1325
1358
|
if (message.readiness.state === "ready" && unresolvedCurrentAuth(agent)) {
|
|
1326
1359
|
projectReadyUnlessUnresolvedAuth(agent, observedAt);
|
|
@@ -1338,7 +1371,7 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1338
1371
|
redeliveryTimer.unref?.();
|
|
1339
1372
|
}
|
|
1340
1373
|
if (message.status === "error" && message.error)
|
|
1341
|
-
hostState.recordStatusError(agent, message.error);
|
|
1374
|
+
hostState.recordStatusError(agent, safeRuntimeStatusDiagnostic(message.error));
|
|
1342
1375
|
if (message.status === "error" || message.status === "inactive") {
|
|
1343
1376
|
processingEyes.clear(agent, `agent-status:${message.status}`);
|
|
1344
1377
|
}
|
|
@@ -17,6 +17,7 @@ import { HostChannelBusiness } from "./host-channel-business.mjs";
|
|
|
17
17
|
import { HostInteractionOrchestrator } from "./interaction-orchestrator.mjs";
|
|
18
18
|
import { targetFor } from "./message-policy.mjs";
|
|
19
19
|
import { authFailureAppliesTo, parsePersistedAuthFailure, providerAuthenticationFailureReadiness, readinessForPersistedAuthFailure, RuntimePrerequisiteError, } from "../runtime/runtime-readiness.mjs";
|
|
20
|
+
import { safeProviderDiagnostic } from "../runtime/provider-error-classifier.mjs";
|
|
20
21
|
import { readDocumentCommentSubscription, verifyCallbackProbe } from "../platform/callback-capability.mjs";
|
|
21
22
|
import { loadConfig, resolveMentionPolicy } from "../platform/config.mjs";
|
|
22
23
|
import { processCommandToken } from "../app/internal-command.mjs";
|
|
@@ -79,6 +80,9 @@ export function memberNamesFromPayloads(payloads) {
|
|
|
79
80
|
return names;
|
|
80
81
|
}
|
|
81
82
|
function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
|
|
83
|
+
function safeRuntimeStatusDiagnostic(error) {
|
|
84
|
+
return safeProviderDiagnostic(error, "Runtime entered an error state", 500);
|
|
85
|
+
}
|
|
82
86
|
function agentConfigSignature(agent) {
|
|
83
87
|
return JSON.stringify({
|
|
84
88
|
agentId: agent.agentId, runtime: agent.runtime, model: agent.model, effort: agent.effort ?? null,
|
|
@@ -193,17 +197,40 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
193
197
|
}
|
|
194
198
|
hostState.updateStatus(agent, { runtimeReadiness: { runtime: agent.runtime, state: "ready", observedAt } });
|
|
195
199
|
};
|
|
200
|
+
const projectFallbackRuntimeReadiness = (agent, observedAt, fallback, preserveCurrentUnavailableDiagnostic = false) => {
|
|
201
|
+
const failure = unresolvedCurrentAuth(agent);
|
|
202
|
+
if (failure) {
|
|
203
|
+
hostState.updateStatus(agent, {
|
|
204
|
+
runtimeReadiness: { ...readinessForPersistedAuthFailure(failure), observedAt },
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const currentReadiness = hostState.readStatus(agent).runtimeReadiness;
|
|
209
|
+
const observedMs = Date.parse(String(currentReadiness?.observedAt || ""));
|
|
210
|
+
const daemonStartedMs = Date.parse(daemonStartedAt);
|
|
211
|
+
const currentEpoch = currentReadiness?.runtime === agent.runtime
|
|
212
|
+
&& Number.isFinite(observedMs) && Number.isFinite(daemonStartedMs) && observedMs >= daemonStartedMs;
|
|
213
|
+
if (currentEpoch && (currentReadiness?.state === "missing" || currentReadiness?.state === "unauthenticated" || currentReadiness?.state === "incompatible"))
|
|
214
|
+
return;
|
|
215
|
+
if (currentEpoch && preserveCurrentUnavailableDiagnostic && currentReadiness?.state === "unavailable"
|
|
216
|
+
&& typeof currentReadiness.reason === "string" && currentReadiness.reason.trim()) {
|
|
217
|
+
hostState.updateStatus(agent, {
|
|
218
|
+
runtimeReadiness: { ...currentReadiness, runtime: agent.runtime, state: "unavailable", observedAt },
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
hostState.updateStatus(agent, {
|
|
223
|
+
runtimeReadiness: { runtime: agent.runtime, ...fallback, observedAt },
|
|
224
|
+
});
|
|
225
|
+
};
|
|
196
226
|
const recordInboundDeliveryFailure = (agent, code) => {
|
|
197
227
|
const at = new Date().toISOString();
|
|
198
228
|
const reason = "Inbound Runtime delivery failed non-retryably; durable Inbox/ledger state is retained for recovery.";
|
|
199
229
|
const nextAction = "Inspect the delivery/status error, correct the Runtime or canonical Inbox state, then restart to replay safely.";
|
|
200
|
-
const currentReadiness = hostState.readStatus(agent).runtimeReadiness;
|
|
201
230
|
hostState.updateStatus(agent, {
|
|
202
231
|
inboundDeliveryHealth: { state: "error", code, at, reason, nextAction },
|
|
203
|
-
...(currentReadiness?.state === "unauthenticated" ? {} : {
|
|
204
|
-
runtimeReadiness: { runtime: agent.runtime, state: "incompatible", reason, nextAction },
|
|
205
|
-
}),
|
|
206
232
|
});
|
|
233
|
+
projectFallbackRuntimeReadiness(agent, at, { state: "unavailable", reason, nextAction }, true);
|
|
207
234
|
hostState.recordStatusError(agent, `${reason} ${nextAction} code=${code}`);
|
|
208
235
|
};
|
|
209
236
|
const agentStates = new Map();
|
|
@@ -1272,15 +1299,21 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1272
1299
|
if (message.type === "agent-status") {
|
|
1273
1300
|
log("agent:status", message.agentId, message.status);
|
|
1274
1301
|
const observedAt = new Date().toISOString();
|
|
1275
|
-
if (message.status === "error" || message.status === "inactive")
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1302
|
+
if (message.status === "error" || message.status === "inactive") {
|
|
1303
|
+
if (message.readiness?.state && message.readiness.state !== "ready") {
|
|
1304
|
+
hostState.updateStatus(agent, { runtimeReadiness: { ...message.readiness, observedAt } });
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
projectFallbackRuntimeReadiness(agent, observedAt, message.status === "error" ? {
|
|
1308
|
+
state: "unavailable",
|
|
1309
|
+
reason: safeRuntimeStatusDiagnostic(message.error),
|
|
1310
|
+
nextAction: "Inspect the Runtime status error, correct the Runtime availability issue, then retry.",
|
|
1311
|
+
} : {
|
|
1312
|
+
state: "missing",
|
|
1313
|
+
reason: "Runtime is inactive",
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1284
1317
|
else if (message.readiness) {
|
|
1285
1318
|
if (message.readiness.state === "ready" && unresolvedCurrentAuth(agent)) {
|
|
1286
1319
|
projectReadyUnlessUnresolvedAuth(agent, observedAt);
|
|
@@ -1298,7 +1331,7 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1298
1331
|
redeliveryTimer.unref?.();
|
|
1299
1332
|
}
|
|
1300
1333
|
if (message.status === "error" && message.error)
|
|
1301
|
-
hostState.recordStatusError(agent, message.error);
|
|
1334
|
+
hostState.recordStatusError(agent, safeRuntimeStatusDiagnostic(message.error));
|
|
1302
1335
|
if (message.status === "error" || message.status === "inactive") {
|
|
1303
1336
|
processingEyes.clear(agent, `agent-status:${message.status}`);
|
|
1304
1337
|
}
|
|
@@ -9,6 +9,7 @@ exports.isPiNativeCompactionRequired = isPiNativeCompactionRequired;
|
|
|
9
9
|
exports.assertEffectivePiCompactionSettings = assertEffectivePiCompactionSettings;
|
|
10
10
|
exports.hasProjectPiCompactionOverride = hasProjectPiCompactionOverride;
|
|
11
11
|
exports.ensureOwnedPiAgentDirectory = ensureOwnedPiAgentDirectory;
|
|
12
|
+
exports.projectPiSettingsFile = projectPiSettingsFile;
|
|
12
13
|
exports.ownedPiSettings = ownedPiSettings;
|
|
13
14
|
exports.prepareOwnedPiDirectory = prepareOwnedPiDirectory;
|
|
14
15
|
exports.mergeOwnedPiSettings = mergeOwnedPiSettings;
|
|
@@ -17,7 +18,6 @@ exports.readOwnedPiSettings = readOwnedPiSettings;
|
|
|
17
18
|
exports.olderThanMinimumPiVersionMessage = olderThanMinimumPiVersionMessage;
|
|
18
19
|
exports.parsePiExecutableVersion = parsePiExecutableVersion;
|
|
19
20
|
exports.verifyPiCapabilities = verifyPiCapabilities;
|
|
20
|
-
exports.prepareOwnedPiEnvironment = prepareOwnedPiEnvironment;
|
|
21
21
|
exports.readProjectPiSettings = readProjectPiSettings;
|
|
22
22
|
exports.assertNoProjectPiCompactionOverride = assertNoProjectPiCompactionOverride;
|
|
23
23
|
exports.currentUserLabel = currentUserLabel;
|
|
@@ -25,7 +25,7 @@ const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
|
25
25
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
26
26
|
const node_os_1 = __importDefault(require("node:os"));
|
|
27
27
|
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
-
/** External `pi` must be this version or newer
|
|
28
|
+
/** External `pi` must be this version or newer. Stock 0.84.x does not emit compactionCapabilities. */
|
|
29
29
|
exports.MINIMUM_PI_VERSION = "0.84.2";
|
|
30
30
|
/** Fallback values used while importing a Pi profile, before Pi reports its model. */
|
|
31
31
|
exports.PI_CONTEXT_WINDOW = 272_000;
|
|
@@ -174,15 +174,16 @@ function ensureOwnedPiAgentDirectory(root, agentId) {
|
|
|
174
174
|
ensureDirectory(directory);
|
|
175
175
|
return directory;
|
|
176
176
|
}
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
/** Larkin-owned project settings that Pi deep-merges over the user's global agent dir. */
|
|
178
|
+
function projectPiSettingsFile(workspaceDir) {
|
|
179
|
+
return node_path_1.default.join(workspaceDir, ".pi", "settings.json");
|
|
179
180
|
}
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
writeOwnedPiSettings(
|
|
185
|
-
return
|
|
181
|
+
function ownedPiSettings(workspaceDir) {
|
|
182
|
+
return projectPiSettingsFile(workspaceDir);
|
|
183
|
+
}
|
|
184
|
+
function prepareOwnedPiDirectory(workspaceDir, compaction) {
|
|
185
|
+
writeOwnedPiSettings(workspaceDir, compaction);
|
|
186
|
+
return workspaceDir;
|
|
186
187
|
}
|
|
187
188
|
function mergeOwnedPiSettings(value, compaction = {
|
|
188
189
|
reserveTokens: exports.COMPACTION_RESERVE_TOKENS,
|
|
@@ -202,10 +203,11 @@ function mergeOwnedPiSettings(value, compaction = {
|
|
|
202
203
|
reserveTokens: compaction.reserveTokens, keepRecentTokens: compaction.keepRecentTokens,
|
|
203
204
|
} };
|
|
204
205
|
}
|
|
205
|
-
function writeOwnedPiSettings(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
function writeOwnedPiSettings(workspaceDir, compaction) {
|
|
207
|
+
const projectDir = node_path_1.default.join(workspaceDir, ".pi");
|
|
208
|
+
ensureDirectoryChain(node_path_1.default.resolve(workspaceDir));
|
|
209
|
+
ensureDirectory(projectDir);
|
|
210
|
+
const file = projectPiSettingsFile(workspaceDir);
|
|
209
211
|
try {
|
|
210
212
|
const existing = node_fs_1.default.lstatSync(file);
|
|
211
213
|
if (existing.isSymbolicLink() || !existing.isFile())
|
|
@@ -223,7 +225,7 @@ function writeOwnedPiSettings(directory, compaction) {
|
|
|
223
225
|
if (error.code !== "ENOENT")
|
|
224
226
|
throw new Error("Pi owned settings are invalid");
|
|
225
227
|
}
|
|
226
|
-
const temporary = node_path_1.default.join(
|
|
228
|
+
const temporary = node_path_1.default.join(projectDir, `.${node_path_1.default.basename(file)}.${process.pid}.${node_crypto_1.default.randomUUID()}.tmp`);
|
|
227
229
|
try {
|
|
228
230
|
node_fs_1.default.writeFileSync(temporary, `${JSON.stringify(mergeOwnedPiSettings(value, compaction), null, 2)}\n`, { flag: "wx", mode: 0o600 });
|
|
229
231
|
node_fs_1.default.renameSync(temporary, file);
|
|
@@ -236,10 +238,10 @@ function writeOwnedPiSettings(directory, compaction) {
|
|
|
236
238
|
catch { /* renamed or absent */ }
|
|
237
239
|
}
|
|
238
240
|
}
|
|
239
|
-
function readOwnedPiSettings(
|
|
240
|
-
|
|
241
|
-
assertOwnedDirectory(
|
|
242
|
-
const file =
|
|
241
|
+
function readOwnedPiSettings(workspaceDir) {
|
|
242
|
+
const projectDir = node_path_1.default.join(workspaceDir, ".pi");
|
|
243
|
+
assertOwnedDirectory(projectDir);
|
|
244
|
+
const file = projectPiSettingsFile(workspaceDir);
|
|
243
245
|
const stat = node_fs_1.default.lstatSync(file);
|
|
244
246
|
if (stat.isSymbolicLink() || !stat.isFile())
|
|
245
247
|
throw new Error("Pi owned settings must be a regular file");
|
|
@@ -299,12 +301,30 @@ function parsePiExecutableVersion(output) {
|
|
|
299
301
|
}
|
|
300
302
|
return version;
|
|
301
303
|
}
|
|
304
|
+
function resolveOptionalHandshake(capabilities) {
|
|
305
|
+
if (capabilities.compactionCapabilities !== undefined && capabilities.compactionCapabilities !== null) {
|
|
306
|
+
return capabilities.compactionCapabilities;
|
|
307
|
+
}
|
|
308
|
+
if (capabilities.reserveTokens !== undefined
|
|
309
|
+
|| capabilities.keepRecentTokens !== undefined
|
|
310
|
+
|| capabilities.events !== undefined) {
|
|
311
|
+
return {
|
|
312
|
+
reserveTokens: capabilities.reserveTokens,
|
|
313
|
+
keepRecentTokens: capabilities.keepRecentTokens,
|
|
314
|
+
events: capabilities.events,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
302
319
|
function verifyPiCapabilities(capabilities) {
|
|
303
320
|
assertSupportedPiVersion(capabilities.version);
|
|
304
321
|
if (capabilities.trustedProtocol === true) {
|
|
305
322
|
throw new Error("external Pi cannot use a trusted protocol bypass");
|
|
306
323
|
}
|
|
307
324
|
const contextWindow = capabilities.contextWindow ?? capabilities.model?.contextWindow;
|
|
325
|
+
if (typeof contextWindow !== "number" || !Number.isFinite(contextWindow) || contextWindow <= 0) {
|
|
326
|
+
throw new Error("Pi capability context window is invalid");
|
|
327
|
+
}
|
|
308
328
|
const expected = (() => {
|
|
309
329
|
try {
|
|
310
330
|
return calculatePiCompactionSettings(contextWindow);
|
|
@@ -318,14 +338,18 @@ function verifyPiCapabilities(capabilities) {
|
|
|
318
338
|
}
|
|
319
339
|
if (capabilities.compactRpc !== true)
|
|
320
340
|
throw new Error("Pi compact RPC capability is missing");
|
|
321
|
-
|
|
322
|
-
|
|
341
|
+
const handshake = resolveOptionalHandshake(capabilities);
|
|
342
|
+
if (handshake === undefined)
|
|
343
|
+
return "larkin-settings-only";
|
|
344
|
+
if (handshake.reserveTokens !== expected.reserveTokens
|
|
345
|
+
|| handshake.keepRecentTokens !== expected.keepRecentTokens) {
|
|
323
346
|
throw new Error(`Pi external effective compaction reserve/keep settings are unproven for context window ${contextWindow}`);
|
|
324
347
|
}
|
|
325
|
-
const events = new Set(
|
|
348
|
+
const events = new Set(handshake.events || []);
|
|
326
349
|
for (const event of REQUIRED_PI_EVENTS)
|
|
327
350
|
if (!events.has(event))
|
|
328
351
|
throw new Error(`Pi capability event is unproven: ${event}`);
|
|
352
|
+
return "handshake-reported";
|
|
329
353
|
}
|
|
330
354
|
function stateFile(root) {
|
|
331
355
|
return node_path_1.default.join(root, exports.PI_COMPACTION_RECOVERY_FILE);
|
|
@@ -703,11 +727,6 @@ class PiCompactionRecoveryMachine {
|
|
|
703
727
|
}
|
|
704
728
|
}
|
|
705
729
|
exports.PiCompactionRecoveryMachine = PiCompactionRecoveryMachine;
|
|
706
|
-
function prepareOwnedPiEnvironment(input) {
|
|
707
|
-
const directory = ensureOwnedPiAgentDirectory(input.root, input.agentId);
|
|
708
|
-
writeOwnedPiSettings(directory);
|
|
709
|
-
return { ...input.env, PI_CODING_AGENT_DIR: directory };
|
|
710
|
-
}
|
|
711
730
|
function readProjectPiSettings(workspaceDir) {
|
|
712
731
|
const file = node_path_1.default.join(workspaceDir, ".pi", "settings.json");
|
|
713
732
|
try {
|
|
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
/** External `pi` must be this version or newer
|
|
5
|
+
/** External `pi` must be this version or newer. Stock 0.84.x does not emit compactionCapabilities. */
|
|
6
6
|
export const MINIMUM_PI_VERSION = "0.84.2";
|
|
7
7
|
/** Fallback values used while importing a Pi profile, before Pi reports its model. */
|
|
8
8
|
export const PI_CONTEXT_WINDOW = 272_000;
|
|
@@ -151,15 +151,16 @@ export function ensureOwnedPiAgentDirectory(root, agentId) {
|
|
|
151
151
|
ensureDirectory(directory);
|
|
152
152
|
return directory;
|
|
153
153
|
}
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
/** Larkin-owned project settings that Pi deep-merges over the user's global agent dir. */
|
|
155
|
+
export function projectPiSettingsFile(workspaceDir) {
|
|
156
|
+
return path.join(workspaceDir, ".pi", "settings.json");
|
|
156
157
|
}
|
|
157
|
-
export function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
writeOwnedPiSettings(
|
|
162
|
-
return
|
|
158
|
+
export function ownedPiSettings(workspaceDir) {
|
|
159
|
+
return projectPiSettingsFile(workspaceDir);
|
|
160
|
+
}
|
|
161
|
+
export function prepareOwnedPiDirectory(workspaceDir, compaction) {
|
|
162
|
+
writeOwnedPiSettings(workspaceDir, compaction);
|
|
163
|
+
return workspaceDir;
|
|
163
164
|
}
|
|
164
165
|
export function mergeOwnedPiSettings(value, compaction = {
|
|
165
166
|
reserveTokens: COMPACTION_RESERVE_TOKENS,
|
|
@@ -179,10 +180,11 @@ export function mergeOwnedPiSettings(value, compaction = {
|
|
|
179
180
|
reserveTokens: compaction.reserveTokens, keepRecentTokens: compaction.keepRecentTokens,
|
|
180
181
|
} };
|
|
181
182
|
}
|
|
182
|
-
export function writeOwnedPiSettings(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
export function writeOwnedPiSettings(workspaceDir, compaction) {
|
|
184
|
+
const projectDir = path.join(workspaceDir, ".pi");
|
|
185
|
+
ensureDirectoryChain(path.resolve(workspaceDir));
|
|
186
|
+
ensureDirectory(projectDir);
|
|
187
|
+
const file = projectPiSettingsFile(workspaceDir);
|
|
186
188
|
try {
|
|
187
189
|
const existing = fs.lstatSync(file);
|
|
188
190
|
if (existing.isSymbolicLink() || !existing.isFile())
|
|
@@ -200,7 +202,7 @@ export function writeOwnedPiSettings(directory, compaction) {
|
|
|
200
202
|
if (error.code !== "ENOENT")
|
|
201
203
|
throw new Error("Pi owned settings are invalid");
|
|
202
204
|
}
|
|
203
|
-
const temporary = path.join(
|
|
205
|
+
const temporary = path.join(projectDir, `.${path.basename(file)}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
204
206
|
try {
|
|
205
207
|
fs.writeFileSync(temporary, `${JSON.stringify(mergeOwnedPiSettings(value, compaction), null, 2)}\n`, { flag: "wx", mode: 0o600 });
|
|
206
208
|
fs.renameSync(temporary, file);
|
|
@@ -213,10 +215,10 @@ export function writeOwnedPiSettings(directory, compaction) {
|
|
|
213
215
|
catch { /* renamed or absent */ }
|
|
214
216
|
}
|
|
215
217
|
}
|
|
216
|
-
export function readOwnedPiSettings(
|
|
217
|
-
|
|
218
|
-
assertOwnedDirectory(
|
|
219
|
-
const file =
|
|
218
|
+
export function readOwnedPiSettings(workspaceDir) {
|
|
219
|
+
const projectDir = path.join(workspaceDir, ".pi");
|
|
220
|
+
assertOwnedDirectory(projectDir);
|
|
221
|
+
const file = projectPiSettingsFile(workspaceDir);
|
|
220
222
|
const stat = fs.lstatSync(file);
|
|
221
223
|
if (stat.isSymbolicLink() || !stat.isFile())
|
|
222
224
|
throw new Error("Pi owned settings must be a regular file");
|
|
@@ -276,12 +278,30 @@ export function parsePiExecutableVersion(output) {
|
|
|
276
278
|
}
|
|
277
279
|
return version;
|
|
278
280
|
}
|
|
281
|
+
function resolveOptionalHandshake(capabilities) {
|
|
282
|
+
if (capabilities.compactionCapabilities !== undefined && capabilities.compactionCapabilities !== null) {
|
|
283
|
+
return capabilities.compactionCapabilities;
|
|
284
|
+
}
|
|
285
|
+
if (capabilities.reserveTokens !== undefined
|
|
286
|
+
|| capabilities.keepRecentTokens !== undefined
|
|
287
|
+
|| capabilities.events !== undefined) {
|
|
288
|
+
return {
|
|
289
|
+
reserveTokens: capabilities.reserveTokens,
|
|
290
|
+
keepRecentTokens: capabilities.keepRecentTokens,
|
|
291
|
+
events: capabilities.events,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
279
296
|
export function verifyPiCapabilities(capabilities) {
|
|
280
297
|
assertSupportedPiVersion(capabilities.version);
|
|
281
298
|
if (capabilities.trustedProtocol === true) {
|
|
282
299
|
throw new Error("external Pi cannot use a trusted protocol bypass");
|
|
283
300
|
}
|
|
284
301
|
const contextWindow = capabilities.contextWindow ?? capabilities.model?.contextWindow;
|
|
302
|
+
if (typeof contextWindow !== "number" || !Number.isFinite(contextWindow) || contextWindow <= 0) {
|
|
303
|
+
throw new Error("Pi capability context window is invalid");
|
|
304
|
+
}
|
|
285
305
|
const expected = (() => {
|
|
286
306
|
try {
|
|
287
307
|
return calculatePiCompactionSettings(contextWindow);
|
|
@@ -295,14 +315,18 @@ export function verifyPiCapabilities(capabilities) {
|
|
|
295
315
|
}
|
|
296
316
|
if (capabilities.compactRpc !== true)
|
|
297
317
|
throw new Error("Pi compact RPC capability is missing");
|
|
298
|
-
|
|
299
|
-
|
|
318
|
+
const handshake = resolveOptionalHandshake(capabilities);
|
|
319
|
+
if (handshake === undefined)
|
|
320
|
+
return "larkin-settings-only";
|
|
321
|
+
if (handshake.reserveTokens !== expected.reserveTokens
|
|
322
|
+
|| handshake.keepRecentTokens !== expected.keepRecentTokens) {
|
|
300
323
|
throw new Error(`Pi external effective compaction reserve/keep settings are unproven for context window ${contextWindow}`);
|
|
301
324
|
}
|
|
302
|
-
const events = new Set(
|
|
325
|
+
const events = new Set(handshake.events || []);
|
|
303
326
|
for (const event of REQUIRED_PI_EVENTS)
|
|
304
327
|
if (!events.has(event))
|
|
305
328
|
throw new Error(`Pi capability event is unproven: ${event}`);
|
|
329
|
+
return "handshake-reported";
|
|
306
330
|
}
|
|
307
331
|
function stateFile(root) {
|
|
308
332
|
return path.join(root, PI_COMPACTION_RECOVERY_FILE);
|
|
@@ -678,11 +702,6 @@ export class PiCompactionRecoveryMachine {
|
|
|
678
702
|
this.change("closed");
|
|
679
703
|
}
|
|
680
704
|
}
|
|
681
|
-
export function prepareOwnedPiEnvironment(input) {
|
|
682
|
-
const directory = ensureOwnedPiAgentDirectory(input.root, input.agentId);
|
|
683
|
-
writeOwnedPiSettings(directory);
|
|
684
|
-
return { ...input.env, PI_CODING_AGENT_DIR: directory };
|
|
685
|
-
}
|
|
686
705
|
export function readProjectPiSettings(workspaceDir) {
|
|
687
706
|
const file = path.join(workspaceDir, ".pi", "settings.json");
|
|
688
707
|
try {
|
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.PI_THINKING_LEVELS = void 0;
|
|
37
4
|
exports.piModelId = piModelId;
|
|
@@ -40,7 +7,6 @@ exports.isPiThinkingLevel = isPiThinkingLevel;
|
|
|
40
7
|
exports.findExactPiModel = findExactPiModel;
|
|
41
8
|
exports.discoverPiModelCatalog = discoverPiModelCatalog;
|
|
42
9
|
const node_child_process_1 = require("node:child_process");
|
|
43
|
-
const path = __importStar(require("node:path"));
|
|
44
10
|
const pi_rpc_client_js_1 = require("./pi-rpc-client.cjs");
|
|
45
11
|
const runtime_readiness_js_1 = require("./runtime-readiness.cjs");
|
|
46
12
|
exports.PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -77,17 +43,15 @@ function piCatalogChildEnv(options) {
|
|
|
77
43
|
...options.env,
|
|
78
44
|
NO_COLOR: "1",
|
|
79
45
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
else
|
|
83
|
-
delete env.PI_CODING_AGENT_DIR;
|
|
46
|
+
// External pi 必须看见用户自己的 home;Larkin 从不注入或转发 PI_CODING_AGENT_DIR。
|
|
47
|
+
delete env.PI_CODING_AGENT_DIR;
|
|
84
48
|
return env;
|
|
85
49
|
}
|
|
86
50
|
async function discoverPiModelCatalog(options) {
|
|
87
51
|
if (!options.spawn) {
|
|
88
52
|
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
89
53
|
const childEnv = piCatalogChildEnv(options);
|
|
90
|
-
const key = `${command}|${(options.commandArgs ?? []).join("\0")}|${childEnv.
|
|
54
|
+
const key = `${command}|${(options.commandArgs ?? []).join("\0")}|${childEnv.PI_PACKAGE_DIR ?? ""}|${options.packageDir ?? ""}`;
|
|
91
55
|
const cached = discoveryCache.get(key);
|
|
92
56
|
if (cached)
|
|
93
57
|
return cached;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
|
-
import * as path from "node:path";
|
|
3
2
|
import { PiRpcClient } from "./pi-rpc-client.mjs";
|
|
4
3
|
import { classifyRuntimePrerequisite, RuntimePrerequisiteError } from "./runtime-readiness.mjs";
|
|
5
4
|
export const PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -36,17 +35,15 @@ function piCatalogChildEnv(options) {
|
|
|
36
35
|
...options.env,
|
|
37
36
|
NO_COLOR: "1",
|
|
38
37
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
else
|
|
42
|
-
delete env.PI_CODING_AGENT_DIR;
|
|
38
|
+
// External pi 必须看见用户自己的 home;Larkin 从不注入或转发 PI_CODING_AGENT_DIR。
|
|
39
|
+
delete env.PI_CODING_AGENT_DIR;
|
|
43
40
|
return env;
|
|
44
41
|
}
|
|
45
42
|
export async function discoverPiModelCatalog(options) {
|
|
46
43
|
if (!options.spawn) {
|
|
47
44
|
const command = options.command ?? options.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
48
45
|
const childEnv = piCatalogChildEnv(options);
|
|
49
|
-
const key = `${command}|${(options.commandArgs ?? []).join("\0")}|${childEnv.
|
|
46
|
+
const key = `${command}|${(options.commandArgs ?? []).join("\0")}|${childEnv.PI_PACKAGE_DIR ?? ""}|${options.packageDir ?? ""}`;
|
|
50
47
|
const cached = discoveryCache.get(key);
|
|
51
48
|
if (cached)
|
|
52
49
|
return cached;
|