larkin 0.4.24 → 0.4.26
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/agent/context-prompt.cjs +18 -0
- package/dist/agent/context-prompt.mjs +18 -0
- package/dist/app/agent-cli.mjs +1 -1
- package/dist/app/agent-config.mjs +9 -3
- package/dist/app/runtime-model-directory.mjs +22 -3
- package/dist/dashboard/dashboard-config-controller.mjs +24 -14
- package/dist/dashboard/dashboard-view-model.mjs +5 -1
- package/dist/dashboard/web/assets/dashboard.css +1 -1
- package/dist/dashboard/web/assets/dashboard.js +15 -15
- package/dist/feishu/host-shell.cjs +28 -8
- package/dist/feishu/host-shell.mjs +28 -8
- package/dist/runtime/pi-distribution-label.cjs +6 -0
- package/dist/runtime/pi-distribution-label.mjs +3 -0
- package/dist/runtime/pi-provider-config.cjs +20 -1
- package/dist/runtime/pi-provider-config.mjs +16 -0
- package/dist/runtime/runtime-adapters.mjs +20 -6
- package/dist/runtime/runtime-host.cjs +100 -48
- package/dist/runtime/runtime-host.mjs +100 -48
- package/package.json +1 -1
|
@@ -221,6 +221,13 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
221
221
|
const agentStates = new Map();
|
|
222
222
|
const saveAgentState = (record) => {
|
|
223
223
|
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
|
+
}
|
|
224
231
|
record.store.writeJson("agentState", record.state);
|
|
225
232
|
}
|
|
226
233
|
catch (error) {
|
|
@@ -235,7 +242,11 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
235
242
|
let state;
|
|
236
243
|
try {
|
|
237
244
|
const loaded = store.readJson("agentState", {});
|
|
238
|
-
state = {
|
|
245
|
+
state = {
|
|
246
|
+
...loaded,
|
|
247
|
+
sessions: isRecord(loaded.sessions) ? loaded.sessions : {},
|
|
248
|
+
...(isRecord(loaded.previousSessions) ? { previousSessions: loaded.previousSessions } : {}),
|
|
249
|
+
};
|
|
239
250
|
}
|
|
240
251
|
catch {
|
|
241
252
|
state = { sessions: {} };
|
|
@@ -1309,13 +1320,21 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1309
1320
|
}
|
|
1310
1321
|
if (message.type === "session") {
|
|
1311
1322
|
const record = agentStates.get(agent.agentId);
|
|
1312
|
-
if (record
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1323
|
+
if (record) {
|
|
1324
|
+
const sessionChanged = record.state.sessions[message.runtime] !== message.sessionId;
|
|
1325
|
+
if (sessionChanged) {
|
|
1326
|
+
if (message.sessionId)
|
|
1327
|
+
record.state.sessions[message.runtime] = message.sessionId;
|
|
1328
|
+
else
|
|
1329
|
+
delete record.state.sessions[message.runtime];
|
|
1330
|
+
}
|
|
1331
|
+
if (message.previousSession) {
|
|
1332
|
+
record.state.previousSessions = { ...(record.state.previousSessions ?? {}), [message.runtime]: message.previousSession };
|
|
1333
|
+
}
|
|
1334
|
+
if (sessionChanged || message.previousSession) {
|
|
1335
|
+
saveAgentState(record);
|
|
1336
|
+
log(`持久化 agent=${agent.name} runtime=${message.runtime} sessionId=${message.sessionId}`);
|
|
1337
|
+
}
|
|
1319
1338
|
}
|
|
1320
1339
|
hostState.updateStatus(agent, (0, host_business_state_js_1.projectSessionStatus)(hostState.readStatus(agent), message.runtime, message.sessionId, message.launchId, new Date(), {
|
|
1321
1340
|
...(message.model ? { model: message.model } : {}),
|
|
@@ -1564,6 +1583,7 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1564
1583
|
await runtimeHost.start(agents.map((agent) => ({
|
|
1565
1584
|
...agent,
|
|
1566
1585
|
sessionId: agentStates.get(agent.agentId)?.state.sessions[agent.runtime] || null,
|
|
1586
|
+
previousSession: agentStates.get(agent.agentId)?.state.previousSessions?.[agent.runtime] ?? null,
|
|
1567
1587
|
})));
|
|
1568
1588
|
reminder.startSync();
|
|
1569
1589
|
inboxAudit.start();
|
|
@@ -181,6 +181,13 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
181
181
|
const agentStates = new Map();
|
|
182
182
|
const saveAgentState = (record) => {
|
|
183
183
|
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
|
+
}
|
|
184
191
|
record.store.writeJson("agentState", record.state);
|
|
185
192
|
}
|
|
186
193
|
catch (error) {
|
|
@@ -195,7 +202,11 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
195
202
|
let state;
|
|
196
203
|
try {
|
|
197
204
|
const loaded = store.readJson("agentState", {});
|
|
198
|
-
state = {
|
|
205
|
+
state = {
|
|
206
|
+
...loaded,
|
|
207
|
+
sessions: isRecord(loaded.sessions) ? loaded.sessions : {},
|
|
208
|
+
...(isRecord(loaded.previousSessions) ? { previousSessions: loaded.previousSessions } : {}),
|
|
209
|
+
};
|
|
199
210
|
}
|
|
200
211
|
catch {
|
|
201
212
|
state = { sessions: {} };
|
|
@@ -1269,13 +1280,21 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1269
1280
|
}
|
|
1270
1281
|
if (message.type === "session") {
|
|
1271
1282
|
const record = agentStates.get(agent.agentId);
|
|
1272
|
-
if (record
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1283
|
+
if (record) {
|
|
1284
|
+
const sessionChanged = record.state.sessions[message.runtime] !== message.sessionId;
|
|
1285
|
+
if (sessionChanged) {
|
|
1286
|
+
if (message.sessionId)
|
|
1287
|
+
record.state.sessions[message.runtime] = message.sessionId;
|
|
1288
|
+
else
|
|
1289
|
+
delete record.state.sessions[message.runtime];
|
|
1290
|
+
}
|
|
1291
|
+
if (message.previousSession) {
|
|
1292
|
+
record.state.previousSessions = { ...(record.state.previousSessions ?? {}), [message.runtime]: message.previousSession };
|
|
1293
|
+
}
|
|
1294
|
+
if (sessionChanged || message.previousSession) {
|
|
1295
|
+
saveAgentState(record);
|
|
1296
|
+
log(`持久化 agent=${agent.name} runtime=${message.runtime} sessionId=${message.sessionId}`);
|
|
1297
|
+
}
|
|
1279
1298
|
}
|
|
1280
1299
|
hostState.updateStatus(agent, projectSessionStatus(hostState.readStatus(agent), message.runtime, message.sessionId, message.launchId, new Date(), {
|
|
1281
1300
|
...(message.model ? { model: message.model } : {}),
|
|
@@ -1524,6 +1543,7 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1524
1543
|
await runtimeHost.start(agents.map((agent) => ({
|
|
1525
1544
|
...agent,
|
|
1526
1545
|
sessionId: agentStates.get(agent.agentId)?.state.sessions[agent.runtime] || null,
|
|
1546
|
+
previousSession: agentStates.get(agent.agentId)?.state.previousSessions?.[agent.runtime] ?? null,
|
|
1527
1547
|
})));
|
|
1528
1548
|
reminder.startSync();
|
|
1529
1549
|
inboxAudit.start();
|
|
@@ -3,19 +3,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PI_PROVIDER_PRESETS = exports.BUNDLED_PI_VERSION = void 0;
|
|
6
|
+
exports.PI_PROVIDER_PRESETS = exports.BUNDLED_PI_VERSION = exports.piDistributionLabel = void 0;
|
|
7
7
|
exports.validatePiBaseUrl = validatePiBaseUrl;
|
|
8
8
|
exports.listProviderModels = listProviderModels;
|
|
9
9
|
exports.resolveBuiltinPiProviderSetupSelection = resolveBuiltinPiProviderSetupSelection;
|
|
10
10
|
exports.validateBuiltinPiProviderSelection = validateBuiltinPiProviderSelection;
|
|
11
11
|
exports.piAgentDirectory = piAgentDirectory;
|
|
12
|
+
exports.ownedPiCatalogAgentDir = ownedPiCatalogAgentDir;
|
|
13
|
+
exports.piCatalogCommandSpec = piCatalogCommandSpec;
|
|
12
14
|
exports.assertBuiltinPiAgentDirectory = assertBuiltinPiAgentDirectory;
|
|
13
15
|
exports.configureBuiltinPiProviderModel = configureBuiltinPiProviderModel;
|
|
14
16
|
exports.stageBuiltinPiProvider = stageBuiltinPiProvider;
|
|
15
17
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
16
18
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
17
19
|
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
const internal_command_js_1 = require("../app/internal-command.cjs");
|
|
18
21
|
const secure_metadata_js_1 = require("../platform/secure-metadata.cjs");
|
|
22
|
+
var pi_distribution_label_js_1 = require("./pi-distribution-label.cjs");
|
|
23
|
+
Object.defineProperty(exports, "piDistributionLabel", { enumerable: true, get: function () { return pi_distribution_label_js_1.piDistributionLabel; } });
|
|
19
24
|
exports.BUNDLED_PI_VERSION = "0.84.2";
|
|
20
25
|
// These values mirror the catalog shipped by the pinned official Pi distribution.
|
|
21
26
|
// Keeping the user-facing defaults here makes endpoint changes reviewable in Larkin.
|
|
@@ -137,6 +142,20 @@ function piAgentDirectory(configDir, agentId) {
|
|
|
137
142
|
throw new Error("Pi Agent ID 格式无效");
|
|
138
143
|
return node_path_1.default.join(node_path_1.default.resolve(configDir), "providers", "pi", agentId);
|
|
139
144
|
}
|
|
145
|
+
/** Catalog/CLI/dashboard must use the Agent-owned provider dir, never host ~/.pi. */
|
|
146
|
+
function ownedPiCatalogAgentDir(configDir, agentId) {
|
|
147
|
+
return piAgentDirectory(configDir, agentId);
|
|
148
|
+
}
|
|
149
|
+
function piCatalogCommandSpec(distribution, env = process.env) {
|
|
150
|
+
if (distribution === "builtin") {
|
|
151
|
+
const spec = (0, internal_command_js_1.internalCommandSpec)("pi-rpc", [], env);
|
|
152
|
+
return { command: spec.command, commandArgs: spec.args };
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
command: env.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi",
|
|
156
|
+
commandArgs: [],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
140
159
|
function assertPrivateDirectory(directory) {
|
|
141
160
|
if (secure_metadata_js_1.isWindows) {
|
|
142
161
|
// Windows 无 POSIX 权限位:icacls 收紧为「当前用户 + SYSTEM」并回读校验。
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { internalCommandSpec } from "../app/internal-command.mjs";
|
|
4
5
|
import { isWindows, secureWindowsDirectoryAcl } from "../platform/secure-metadata.mjs";
|
|
6
|
+
export { piDistributionLabel } from "./pi-distribution-label.mjs";
|
|
5
7
|
export const BUNDLED_PI_VERSION = "0.84.2";
|
|
6
8
|
// These values mirror the catalog shipped by the pinned official Pi distribution.
|
|
7
9
|
// Keeping the user-facing defaults here makes endpoint changes reviewable in Larkin.
|
|
@@ -123,6 +125,20 @@ export function piAgentDirectory(configDir, agentId) {
|
|
|
123
125
|
throw new Error("Pi Agent ID 格式无效");
|
|
124
126
|
return path.join(path.resolve(configDir), "providers", "pi", agentId);
|
|
125
127
|
}
|
|
128
|
+
/** Catalog/CLI/dashboard must use the Agent-owned provider dir, never host ~/.pi. */
|
|
129
|
+
export function ownedPiCatalogAgentDir(configDir, agentId) {
|
|
130
|
+
return piAgentDirectory(configDir, agentId);
|
|
131
|
+
}
|
|
132
|
+
export function piCatalogCommandSpec(distribution, env = process.env) {
|
|
133
|
+
if (distribution === "builtin") {
|
|
134
|
+
const spec = internalCommandSpec("pi-rpc", [], env);
|
|
135
|
+
return { command: spec.command, commandArgs: spec.args };
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
command: env.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi",
|
|
139
|
+
commandArgs: [],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
126
142
|
function assertPrivateDirectory(directory) {
|
|
127
143
|
if (isWindows) {
|
|
128
144
|
// Windows 无 POSIX 权限位:icacls 收紧为「当前用户 + SYSTEM」并回读校验。
|
|
@@ -479,6 +479,7 @@ class PiSession extends EventSession {
|
|
|
479
479
|
}));
|
|
480
480
|
}
|
|
481
481
|
get sessionId() { return this.sdk.sessionId ?? null; }
|
|
482
|
+
get sessionFile() { return this.sdk.sessionFile ?? null; }
|
|
482
483
|
get effectiveModel() { return this.sdk.model ? `${this.sdk.model.provider}/${this.sdk.model.id}` : null; }
|
|
483
484
|
get effectiveReasoningEffort() { return this.sdk.thinkingLevel ?? null; }
|
|
484
485
|
async prompt(input) { return this.enqueue(input, () => this.sdk.prompt(input.text)); }
|
|
@@ -925,6 +926,7 @@ class PiRpcBackend {
|
|
|
925
926
|
policy;
|
|
926
927
|
policyManaged;
|
|
927
928
|
sessionId;
|
|
929
|
+
sessionFile;
|
|
928
930
|
model;
|
|
929
931
|
thinkingLevel;
|
|
930
932
|
constructor(client, state, policy) {
|
|
@@ -932,6 +934,7 @@ class PiRpcBackend {
|
|
|
932
934
|
this.policy = policy;
|
|
933
935
|
this.policyManaged = Boolean(policy);
|
|
934
936
|
this.sessionId = state.sessionId ?? null;
|
|
937
|
+
this.sessionFile = typeof state.sessionFile === "string" && state.sessionFile ? state.sessionFile : null;
|
|
935
938
|
if (state.model?.provider && state.model.id)
|
|
936
939
|
this.model = {
|
|
937
940
|
provider: state.model.provider, id: state.model.id,
|
|
@@ -953,12 +956,23 @@ class PiRpcBackend {
|
|
|
953
956
|
if (effectiveModel !== this.policy.model || state.model?.contextWindow !== this.policy.contextWindow) {
|
|
954
957
|
throw new Error("Pi model or context window changed after startup; compaction policy is no longer safe");
|
|
955
958
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
959
|
+
if (typeof state.sessionFile === "string" && state.sessionFile)
|
|
960
|
+
this.sessionFile = state.sessionFile;
|
|
961
|
+
if (typeof state.sessionId === "string" && state.sessionId)
|
|
962
|
+
this.sessionId = state.sessionId;
|
|
963
|
+
// Use the live process handshake, not a disk re-read of owned settings.json.
|
|
964
|
+
// Disk reserveTokens can drift after startup; that must not reject prompt.
|
|
965
|
+
const handshake = state.compactionCapabilities;
|
|
966
|
+
if (typeof handshake?.reserveTokens === "number" && typeof handshake?.keepRecentTokens === "number") {
|
|
967
|
+
assertEffectivePiCompactionSettings({ contextWindow: state.model?.contextWindow, compaction: {
|
|
968
|
+
enabled: state.autoCompactionEnabled,
|
|
969
|
+
reserveTokens: handshake.reserveTokens,
|
|
970
|
+
keepRecentTokens: handshake.keepRecentTokens,
|
|
971
|
+
} });
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (state.autoCompactionEnabled !== true)
|
|
975
|
+
throw new Error("Pi native compaction must be enabled");
|
|
962
976
|
}
|
|
963
977
|
getState() { return this.client.request("get_state"); }
|
|
964
978
|
dispose() { return this.client.close(); }
|
|
@@ -45,6 +45,63 @@ const BACKGROUND_COMPLETION_IMMEDIATE_RETRY_LIMIT = 5;
|
|
|
45
45
|
const DEFAULT_SUBAGENT_RECONCILE_INTERVAL_MS = 15_000;
|
|
46
46
|
const now = () => new Date().toISOString();
|
|
47
47
|
const isActiveDelivery = (status) => ["pending", "submitting", "accepted"].includes(status);
|
|
48
|
+
const previousSessionRefFor = (session, reason) => session.sessionId
|
|
49
|
+
? { sessionId: session.sessionId, file: session.sessionFile ?? null, closedAt: new Date().toISOString(), reason }
|
|
50
|
+
: null;
|
|
51
|
+
function parsePreviousSessionRef(value) {
|
|
52
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
53
|
+
return null;
|
|
54
|
+
const record = value;
|
|
55
|
+
if (typeof record.sessionId !== "string" || !record.sessionId)
|
|
56
|
+
return null;
|
|
57
|
+
if (typeof record.closedAt !== "string" || typeof record.reason !== "string")
|
|
58
|
+
return null;
|
|
59
|
+
return {
|
|
60
|
+
sessionId: record.sessionId,
|
|
61
|
+
file: typeof record.file === "string" && record.file ? record.file : null,
|
|
62
|
+
closedAt: record.closedAt,
|
|
63
|
+
reason: record.reason,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function loadPersistedPreviousSession(config, store) {
|
|
67
|
+
const fromConfig = parsePreviousSessionRef(config.previousSession);
|
|
68
|
+
if (fromConfig)
|
|
69
|
+
return fromConfig;
|
|
70
|
+
if (!store)
|
|
71
|
+
return null;
|
|
72
|
+
try {
|
|
73
|
+
const state = store.readJson("agentState", {});
|
|
74
|
+
return parsePreviousSessionRef(state.previousSessions?.[config.runtime]);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function persistPreviousSession(agent, previous) {
|
|
81
|
+
agent.previousSession = previous;
|
|
82
|
+
agent.config.previousSession = previous;
|
|
83
|
+
const store = agent.stateStore;
|
|
84
|
+
if (!store)
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
const current = store.readJson("agentState", { sessions: {} });
|
|
88
|
+
// Delivery-ledger mocks ignore the JSON key and would be clobbered by this write.
|
|
89
|
+
if (Array.isArray(current.records) && !("sessions" in current))
|
|
90
|
+
return;
|
|
91
|
+
const previousSessions = current.previousSessions && typeof current.previousSessions === "object"
|
|
92
|
+
&& !Array.isArray(current.previousSessions)
|
|
93
|
+
? { ...current.previousSessions }
|
|
94
|
+
: {};
|
|
95
|
+
if (previous)
|
|
96
|
+
previousSessions[agent.adapter.id] = previous;
|
|
97
|
+
else
|
|
98
|
+
delete previousSessions[agent.adapter.id];
|
|
99
|
+
store.writeJson("agentState", { ...current, previousSessions });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// In-memory ref still holds for this process; restart will miss it only if this write failed.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
48
105
|
const REPLAY_FAILURE_CODES = new Set([
|
|
49
106
|
"canonical_inbox_row_missing", "canonical_inbox_malformed", "duplicate_message_id",
|
|
50
107
|
"inbox_state_conflict", "delivery_target_conflict", "structured_target_invalid",
|
|
@@ -466,17 +523,12 @@ function createRuntimeHost(options) {
|
|
|
466
523
|
&& agent.piProactiveCompactionGeneration === agent.generation
|
|
467
524
|
&& agent.piProactiveCompactionSession === agent.session
|
|
468
525
|
? agent.piProactiveCompaction : null;
|
|
469
|
-
if (proactive
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
if (agent.adapter.id === "pi" && agent.piProactiveCompactionFailedGeneration === agent.generation) {
|
|
473
|
-
return { status: "deferred", deliveryId: record.deliveryId, reason: "Pi proactive compaction failed for the current session generation" };
|
|
474
|
-
}
|
|
526
|
+
if (proactive)
|
|
527
|
+
await proactive;
|
|
475
528
|
if (agent.adapter.id === "pi" && !busy && !agent.busy && !agent.turnInProgress && agent.session) {
|
|
476
529
|
const idleGate = proactivelyCompactPiAtIdle(agent, agent.session);
|
|
477
|
-
if (idleGate
|
|
478
|
-
|
|
479
|
-
}
|
|
530
|
+
if (idleGate)
|
|
531
|
+
await idleGate;
|
|
480
532
|
}
|
|
481
533
|
agent.submitting = true;
|
|
482
534
|
if (!busy)
|
|
@@ -550,20 +602,18 @@ function createRuntimeHost(options) {
|
|
|
550
602
|
&& agent.piProactiveCompactionGeneration === agent.generation
|
|
551
603
|
&& agent.piProactiveCompactionSession === agent.session
|
|
552
604
|
? agent.piProactiveCompaction : null;
|
|
553
|
-
if (proactive
|
|
554
|
-
|
|
605
|
+
if (proactive)
|
|
606
|
+
await proactive;
|
|
555
607
|
if (agent.submitting || agent.starting) {
|
|
556
608
|
agent.retryAfterSubmit = true;
|
|
557
609
|
return;
|
|
558
610
|
}
|
|
559
|
-
if (agent.adapter.id === "pi" && agent.piProactiveCompactionFailedGeneration === agent.generation)
|
|
560
|
-
return;
|
|
561
611
|
if (agent.busy || agent.turnInProgress || !agent.session)
|
|
562
612
|
return;
|
|
563
613
|
if (agent.adapter.id === "pi") {
|
|
564
614
|
const idleGate = proactivelyCompactPiAtIdle(agent, agent.session);
|
|
565
|
-
if (idleGate
|
|
566
|
-
|
|
615
|
+
if (idleGate)
|
|
616
|
+
await idleGate;
|
|
567
617
|
}
|
|
568
618
|
const record = [...agent.records.values()].find((candidate) => candidate.status === "pending" || candidate.status === "submitting");
|
|
569
619
|
if (record)
|
|
@@ -769,14 +819,12 @@ function createRuntimeHost(options) {
|
|
|
769
819
|
&& agent.piProactiveCompactionGeneration === agent.generation
|
|
770
820
|
&& agent.piProactiveCompactionSession === agent.session
|
|
771
821
|
? agent.piProactiveCompaction : null;
|
|
772
|
-
if (proactive
|
|
773
|
-
|
|
774
|
-
if (agent.adapter.id === "pi" && agent.piProactiveCompactionFailedGeneration === agent.generation)
|
|
775
|
-
return;
|
|
822
|
+
if (proactive)
|
|
823
|
+
await proactive;
|
|
776
824
|
if (agent.adapter.id === "pi" && !agent.busy && !agent.turnInProgress && agent.session) {
|
|
777
825
|
const idleGate = proactivelyCompactPiAtIdle(agent, agent.session);
|
|
778
|
-
if (idleGate
|
|
779
|
-
|
|
826
|
+
if (idleGate)
|
|
827
|
+
await idleGate;
|
|
780
828
|
}
|
|
781
829
|
if (agent.stopped || agent.busy || agent.turnInProgress || agent.submitting)
|
|
782
830
|
return;
|
|
@@ -958,8 +1006,8 @@ function createRuntimeHost(options) {
|
|
|
958
1006
|
void ensureSession(agent).then(async () => {
|
|
959
1007
|
const session = agent.session;
|
|
960
1008
|
const proactive = session ? proactivelyCompactPiAtIdle(agent, session) : null;
|
|
961
|
-
if (proactive
|
|
962
|
-
|
|
1009
|
+
if (proactive)
|
|
1010
|
+
await proactive;
|
|
963
1011
|
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "pi session gone" });
|
|
964
1012
|
enqueueUndeliveredTerminalWakes(agent);
|
|
965
1013
|
await retryPending(agent);
|
|
@@ -1199,8 +1247,6 @@ function createRuntimeHost(options) {
|
|
|
1199
1247
|
})();
|
|
1200
1248
|
const tracked = task.catch((error) => {
|
|
1201
1249
|
agent.piProactiveCompactionFailedGeneration = generation;
|
|
1202
|
-
emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
|
|
1203
|
-
error: "Pi proactive compaction failed; the current session generation is degraded" });
|
|
1204
1250
|
log("Pi proactive compaction failed", String(error));
|
|
1205
1251
|
return "failed";
|
|
1206
1252
|
}).finally(() => {
|
|
@@ -1334,10 +1380,7 @@ function createRuntimeHost(options) {
|
|
|
1334
1380
|
emit({ type: "agent-status", agentId: agent.config.agentId, status: "active", readiness: agent.readiness });
|
|
1335
1381
|
}
|
|
1336
1382
|
const proactive = proactivelyCompactPiAtIdle(agent, session);
|
|
1337
|
-
void (proactive ?? Promise.resolve("noop")).then((
|
|
1338
|
-
if (outcome !== "failed")
|
|
1339
|
-
return retryPending(agent);
|
|
1340
|
-
});
|
|
1383
|
+
void (proactive ?? Promise.resolve("noop")).then(() => retryPending(agent));
|
|
1341
1384
|
queueMicrotask(() => { void scanAndPromoteAcceptedInboxUpdates(agent); });
|
|
1342
1385
|
}
|
|
1343
1386
|
else if (event.type === "runtime-observation") {
|
|
@@ -1466,7 +1509,7 @@ function createRuntimeHost(options) {
|
|
|
1466
1509
|
const standingPrompt = options.promptBuilder.build({
|
|
1467
1510
|
agentId: agent.config.agentId, name: agent.config.displayName || agent.config.name,
|
|
1468
1511
|
description: agent.config.description || "", runtime: agent.adapter.id,
|
|
1469
|
-
cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"),
|
|
1512
|
+
cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"), previousSession: agent.previousSession,
|
|
1470
1513
|
});
|
|
1471
1514
|
const generation = ++agent.generation;
|
|
1472
1515
|
let completedSession = null;
|
|
@@ -1526,9 +1569,11 @@ function createRuntimeHost(options) {
|
|
|
1526
1569
|
workspaceDir: agent.config.workspaceDir, stateDir: agent.config.stateDir, env: probeEnv }) : { runtime: "pi", state: "ready" };
|
|
1527
1570
|
if (readiness.state !== "ready")
|
|
1528
1571
|
throw new RuntimeSessionRecoveryError("recovery_staged_not_committed", "Pi context fallback readiness failed");
|
|
1572
|
+
const previousRef = previousSessionRefFor(oldSession, "context-window overflow");
|
|
1529
1573
|
const standingPrompt = options.promptBuilder.build({
|
|
1530
1574
|
agentId: agent.config.agentId, name: agent.config.displayName || agent.config.name,
|
|
1531
1575
|
description: agent.config.description || "", runtime: agent.adapter.id, cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"),
|
|
1576
|
+
previousSession: previousRef,
|
|
1532
1577
|
});
|
|
1533
1578
|
let fresh;
|
|
1534
1579
|
try {
|
|
@@ -1557,6 +1602,7 @@ function createRuntimeHost(options) {
|
|
|
1557
1602
|
const oldConfigSessionId = agent.config.sessionId;
|
|
1558
1603
|
const oldReadiness = agent.readiness;
|
|
1559
1604
|
const oldDisabledReason = agent.disabledReason;
|
|
1605
|
+
const oldPreviousSession = agent.previousSession;
|
|
1560
1606
|
const oldRecord = { ...record, input: { ...record.input } };
|
|
1561
1607
|
let durableRollback = null;
|
|
1562
1608
|
let rearmedCount = 1;
|
|
@@ -1572,6 +1618,7 @@ function createRuntimeHost(options) {
|
|
|
1572
1618
|
agent.launchId = node_crypto_1.default.randomUUID();
|
|
1573
1619
|
agent.config.sessionId = null;
|
|
1574
1620
|
agent.session = fresh;
|
|
1621
|
+
persistPreviousSession(agent, previousRef);
|
|
1575
1622
|
agent.readiness = readiness;
|
|
1576
1623
|
agent.disabledReason = null;
|
|
1577
1624
|
for (const candidate of agent.records.values()) {
|
|
@@ -1606,6 +1653,7 @@ function createRuntimeHost(options) {
|
|
|
1606
1653
|
agent.launchId = oldLaunchId;
|
|
1607
1654
|
agent.config.sessionId = oldConfigSessionId;
|
|
1608
1655
|
agent.session = oldSession;
|
|
1656
|
+
persistPreviousSession(agent, oldPreviousSession);
|
|
1609
1657
|
agent.readiness = oldReadiness;
|
|
1610
1658
|
agent.disabledReason = oldDisabledReason;
|
|
1611
1659
|
Object.assign(record, oldRecord);
|
|
@@ -1620,17 +1668,15 @@ function createRuntimeHost(options) {
|
|
|
1620
1668
|
agent.config.sessionId = fresh.sessionId;
|
|
1621
1669
|
emit({ type: "session", agentId, runtime: agent.adapter.id, sessionId: fresh.sessionId, launchId: agent.launchId,
|
|
1622
1670
|
...(fresh.effectiveModel ? { model: fresh.effectiveModel } : {}),
|
|
1623
|
-
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {})
|
|
1671
|
+
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
|
|
1672
|
+
...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
|
|
1624
1673
|
}
|
|
1625
1674
|
emit({ type: "agent-status", agentId, status: "active", readiness });
|
|
1626
1675
|
agent.piSessionOwner = probeEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1627
1676
|
await oldSession.close("Pi context fallback committed").catch((error) => log("previous Pi session close after fallback failed", String(error)));
|
|
1628
1677
|
reconcileAfterSessionSwap(agent);
|
|
1629
1678
|
const proactive = proactivelyCompactPiAtIdle(agent, fresh);
|
|
1630
|
-
void (proactive ?? Promise.resolve("noop")).then((
|
|
1631
|
-
if (outcome !== "failed")
|
|
1632
|
-
return retryPending(agent);
|
|
1633
|
-
});
|
|
1679
|
+
void (proactive ?? Promise.resolve("noop")).then(() => retryPending(agent));
|
|
1634
1680
|
return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
|
|
1635
1681
|
runtimeReady: true, pendingCount: remainingPendingCount, rearmedCount,
|
|
1636
1682
|
replayStatus: remainingPendingCount > 0 ? "pending" : "consumed", sessionId: fresh.sessionId };
|
|
@@ -1664,7 +1710,7 @@ function createRuntimeHost(options) {
|
|
|
1664
1710
|
const standingPrompt = options.promptBuilder.build({
|
|
1665
1711
|
agentId: config.agentId, name: config.displayName || config.name,
|
|
1666
1712
|
description: config.description || "", runtime: adapter.id,
|
|
1667
|
-
cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"),
|
|
1713
|
+
cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"), previousSession: previous.previousSession,
|
|
1668
1714
|
});
|
|
1669
1715
|
const session = await adapter.createSession({
|
|
1670
1716
|
agentId: config.agentId, model: config.model, reasoningEffort: config.effort || null,
|
|
@@ -1770,10 +1816,11 @@ function createRuntimeHost(options) {
|
|
|
1770
1816
|
: { runtime: agent.adapter.id, state: "ready" };
|
|
1771
1817
|
if (readiness.state !== "ready")
|
|
1772
1818
|
throw new runtime_readiness_js_1.RuntimePrerequisiteError(readiness);
|
|
1819
|
+
const previousRef = previousSessionRefFor(oldSession, "operator reset");
|
|
1773
1820
|
const standingPrompt = options.promptBuilder.build({
|
|
1774
1821
|
agentId: agent.config.agentId, name: agent.config.displayName || agent.config.name,
|
|
1775
1822
|
description: agent.config.description || "", runtime: agent.adapter.id,
|
|
1776
|
-
cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"),
|
|
1823
|
+
cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"), previousSession: previousRef,
|
|
1777
1824
|
});
|
|
1778
1825
|
const create = agent.adapter.createSession({
|
|
1779
1826
|
agentId: agent.config.agentId, model: agent.config.model, reasoningEffort: agent.config.effort || null,
|
|
@@ -1802,6 +1849,7 @@ function createRuntimeHost(options) {
|
|
|
1802
1849
|
agent.launchId = node_crypto_1.default.randomUUID();
|
|
1803
1850
|
agent.config.sessionId = null;
|
|
1804
1851
|
agent.session = fresh;
|
|
1852
|
+
persistPreviousSession(agent, previousRef);
|
|
1805
1853
|
agent.readiness = readiness;
|
|
1806
1854
|
agent.disabledReason = null;
|
|
1807
1855
|
};
|
|
@@ -1820,7 +1868,8 @@ function createRuntimeHost(options) {
|
|
|
1820
1868
|
agent.config.sessionId = fresh.sessionId;
|
|
1821
1869
|
emit({ type: "session", agentId, runtime: agent.adapter.id, sessionId: fresh.sessionId, launchId: agent.launchId,
|
|
1822
1870
|
...(fresh.effectiveModel ? { model: fresh.effectiveModel } : {}),
|
|
1823
|
-
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {})
|
|
1871
|
+
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
|
|
1872
|
+
...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
|
|
1824
1873
|
}
|
|
1825
1874
|
emit({ type: "agent-status", agentId, status: "active", readiness });
|
|
1826
1875
|
agent.piSessionOwner = probeEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
@@ -1880,10 +1929,12 @@ function createRuntimeHost(options) {
|
|
|
1880
1929
|
}
|
|
1881
1930
|
if (readiness.state !== "ready")
|
|
1882
1931
|
throw new runtime_readiness_js_1.RuntimePrerequisiteError(readiness);
|
|
1932
|
+
const previousRef = previousSessionRefFor(oldSession, "context-window recovery");
|
|
1933
|
+
const oldPreviousSession = agent.previousSession;
|
|
1883
1934
|
const standingPrompt = options.promptBuilder.build({
|
|
1884
1935
|
agentId: agent.config.agentId, name: agent.config.displayName || agent.config.name,
|
|
1885
1936
|
description: agent.config.description || "", runtime: agent.adapter.id,
|
|
1886
|
-
cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"),
|
|
1937
|
+
cli: (0, agent_cli_capabilities_js_1.agentCliPromptCapabilities)("larkin"), previousSession: previousRef,
|
|
1887
1938
|
});
|
|
1888
1939
|
let fresh;
|
|
1889
1940
|
try {
|
|
@@ -1924,6 +1975,7 @@ function createRuntimeHost(options) {
|
|
|
1924
1975
|
agent.generation = oldGeneration;
|
|
1925
1976
|
agent.launchId = oldLaunchId;
|
|
1926
1977
|
agent.config.sessionId = oldConfigSessionId;
|
|
1978
|
+
persistPreviousSession(agent, oldPreviousSession);
|
|
1927
1979
|
agent.readiness = oldReadiness;
|
|
1928
1980
|
agent.disabledReason = oldDisabledReason;
|
|
1929
1981
|
agent.records = new Map(oldRecords);
|
|
@@ -1956,6 +2008,7 @@ function createRuntimeHost(options) {
|
|
|
1956
2008
|
agent.launchId = node_crypto_1.default.randomUUID();
|
|
1957
2009
|
agent.config.sessionId = null;
|
|
1958
2010
|
agent.session = fresh;
|
|
2011
|
+
persistPreviousSession(agent, previousRef);
|
|
1959
2012
|
agent.readiness = readiness;
|
|
1960
2013
|
agent.disabledReason = null;
|
|
1961
2014
|
for (const record of agent.records.values()) {
|
|
@@ -1971,7 +2024,8 @@ function createRuntimeHost(options) {
|
|
|
1971
2024
|
agent.config.sessionId = fresh.sessionId;
|
|
1972
2025
|
emit({ type: "session", agentId, runtime: agent.adapter.id, sessionId: fresh.sessionId, launchId: agent.launchId,
|
|
1973
2026
|
...(fresh.effectiveModel ? { model: fresh.effectiveModel } : {}),
|
|
1974
|
-
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {})
|
|
2027
|
+
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
|
|
2028
|
+
...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
|
|
1975
2029
|
}
|
|
1976
2030
|
emit({ type: "agent-status", agentId, status: "active", readiness });
|
|
1977
2031
|
durableRollback = null;
|
|
@@ -1980,10 +2034,7 @@ function createRuntimeHost(options) {
|
|
|
1980
2034
|
.catch((error) => log("previous runtime close after context-window recovery failed", String(error)));
|
|
1981
2035
|
reconcileAfterSessionSwap(agent);
|
|
1982
2036
|
const proactive = proactivelyCompactPiAtIdle(agent, fresh);
|
|
1983
|
-
void (proactive ?? Promise.resolve("noop")).then((
|
|
1984
|
-
if (outcome !== "failed")
|
|
1985
|
-
return retryPending(agent);
|
|
1986
|
-
});
|
|
2037
|
+
void (proactive ?? Promise.resolve("noop")).then(() => retryPending(agent));
|
|
1987
2038
|
return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
|
|
1988
2039
|
runtimeReady: true, pendingCount: rearmed.remainingPendingCount, rearmedCount: rearmed.rearmedCount,
|
|
1989
2040
|
replayStatus: rearmed.remainingPendingCount > 0 ? "pending" : "consumed", sessionId: fresh.sessionId };
|
|
@@ -2063,7 +2114,8 @@ function createRuntimeHost(options) {
|
|
|
2063
2114
|
}) : undefined,
|
|
2064
2115
|
compactionMachines: new Map(), compactionRecoveryInFlight: new Set(), piOverflowCompactionFailed: new Set(),
|
|
2065
2116
|
piProactiveCompaction: null, piProactiveCompactionGeneration: null, piProactiveCompactionSession: null,
|
|
2066
|
-
piProactiveCompactionFailedGeneration: null,
|
|
2117
|
+
piProactiveCompactionFailedGeneration: null,
|
|
2118
|
+
previousSession: loadPersistedPreviousSession(config, stateStore), readiness: null,
|
|
2067
2119
|
turnInProgress: false, turnHadFailure: false, turnHadAuthenticatedOutput: false, authFailureActive: false,
|
|
2068
2120
|
promotedInboxUpdateIds: new Set(),
|
|
2069
2121
|
backgroundCompletionQueue: [], backgroundCompletionKeys: new Set(),
|
|
@@ -2105,10 +2157,10 @@ function createRuntimeHost(options) {
|
|
|
2105
2157
|
await recoverStalePiCompaction(agent);
|
|
2106
2158
|
const startupSession = agent.session;
|
|
2107
2159
|
const proactive = startupSession ? proactivelyCompactPiAtIdle(agent, startupSession) : null;
|
|
2108
|
-
|
|
2160
|
+
if (proactive)
|
|
2161
|
+
await proactive;
|
|
2109
2162
|
activeCount += 1;
|
|
2110
|
-
|
|
2111
|
-
await retryPending(agent);
|
|
2163
|
+
await retryPending(agent);
|
|
2112
2164
|
}
|
|
2113
2165
|
catch (error) {
|
|
2114
2166
|
const reason = error instanceof Error ? error.message : String(error);
|