larkin 0.4.1 → 0.4.2
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/README.md +72 -33
- package/assets/readme/larkin-hero.svg +111 -0
- package/dist/agent/agent-state-store.cjs +194 -1
- package/dist/agent/agent-state-store.mjs +192 -0
- package/dist/app/cli.mjs +3 -1
- package/dist/app/local-control.mjs +108 -2
- package/dist/app/runtime-process.mjs +4 -0
- package/dist/app/session-cli.mjs +55 -20
- package/dist/feishu/host-shell.cjs +72 -1
- package/dist/feishu/host-shell.mjs +72 -1
- package/dist/runtime/pi-compaction-recovery.cjs +623 -0
- package/dist/runtime/pi-compaction-recovery.mjs +602 -0
- package/dist/runtime/pi-provider-config.cjs +2 -2
- package/dist/runtime/pi-provider-config.mjs +2 -2
- package/dist/runtime/pi-rpc-client.cjs +39 -22
- package/dist/runtime/pi-rpc-client.mjs +39 -22
- package/dist/runtime/provider-error-classifier.cjs +39 -0
- package/dist/runtime/provider-error-classifier.mjs +34 -0
- package/dist/runtime/runtime-adapters.mjs +82 -19
- package/dist/runtime/runtime-host.cjs +490 -2
- package/dist/runtime/runtime-host.mjs +488 -1
- package/package.json +1 -1
|
@@ -1244,7 +1244,10 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1244
1244
|
if (message.type === "session") {
|
|
1245
1245
|
const record = agentStates.get(agent.agentId);
|
|
1246
1246
|
if (record && record.state.sessions[message.runtime] !== message.sessionId) {
|
|
1247
|
-
|
|
1247
|
+
if (message.sessionId)
|
|
1248
|
+
record.state.sessions[message.runtime] = message.sessionId;
|
|
1249
|
+
else
|
|
1250
|
+
delete record.state.sessions[message.runtime];
|
|
1248
1251
|
saveAgentState(record);
|
|
1249
1252
|
log(`持久化 agent=${agent.name} runtime=${message.runtime} sessionId=${message.sessionId}`);
|
|
1250
1253
|
}
|
|
@@ -1392,6 +1395,74 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1392
1395
|
: unavailable ? "reset committed but Runtime/channel readiness is unavailable"
|
|
1393
1396
|
: "reset committed but readiness did not converge before timeout" };
|
|
1394
1397
|
},
|
|
1398
|
+
async recoverSession(agentId, reason, waitReadyMs = 30_000) {
|
|
1399
|
+
const agent = agents.find((candidate) => candidate.agentId === agentId);
|
|
1400
|
+
if (!agent)
|
|
1401
|
+
throw Object.assign(new Error(`未知 Agent: ${agentId}`), { code: "unknown_agent" });
|
|
1402
|
+
if (reason !== "context-overflow")
|
|
1403
|
+
throw Object.assign(new Error("unsupported recovery reason"), { code: "recovery_refused" });
|
|
1404
|
+
if (!runtimeHost.recoverSession)
|
|
1405
|
+
throw Object.assign(new Error("Runtime session recovery 尚未就绪"), { code: "recovery_unavailable" });
|
|
1406
|
+
const connectionState = (status = hostState.readStatus(agent)) => {
|
|
1407
|
+
const connectedAt = Date.parse(String(status.connectedAt || ""));
|
|
1408
|
+
const channelConnected = Number.isFinite(connectedAt) && connectedAt >= Date.parse(daemonStartedAt) - 1000;
|
|
1409
|
+
return { channelConnected, reconnecting: isChannelReconnecting(status) };
|
|
1410
|
+
};
|
|
1411
|
+
const readinessProjection = () => {
|
|
1412
|
+
const status = hostState.readStatus(agent);
|
|
1413
|
+
const turns = status.session && typeof status.session === "object"
|
|
1414
|
+
? Math.max(0, Number(status.session.turns) || 0) : 0;
|
|
1415
|
+
const runtimeReady = Boolean(status.runtimeReadiness && typeof status.runtimeReadiness === "object"
|
|
1416
|
+
&& status.runtimeReadiness.state === "ready");
|
|
1417
|
+
const pendingCount = stateStore(agent).withInboxTransaction(() => stateStore(agent).readNdjson("inbox").length);
|
|
1418
|
+
return { turns, runtimeReady, ...connectionState(status), pendingCount };
|
|
1419
|
+
};
|
|
1420
|
+
const initialProjection = readinessProjection();
|
|
1421
|
+
if (!initialProjection.channelConnected)
|
|
1422
|
+
throw Object.assign(new Error(`Agent ${agentId} channel is not connected`), { code: "channel_unavailable", ...initialProjection });
|
|
1423
|
+
if (initialProjection.reconnecting)
|
|
1424
|
+
throw Object.assign(new Error(`Agent ${agentId} channel is reconnecting`), { code: "channel_reconnecting", ...initialProjection });
|
|
1425
|
+
let recovery;
|
|
1426
|
+
try {
|
|
1427
|
+
recovery = await runtimeHost.recoverSession(agentId, reason);
|
|
1428
|
+
}
|
|
1429
|
+
catch (error) {
|
|
1430
|
+
throw Object.assign(error instanceof Error ? error : new Error(String(error)), readinessProjection());
|
|
1431
|
+
}
|
|
1432
|
+
const record = agentStates.get(agentId);
|
|
1433
|
+
try {
|
|
1434
|
+
if (record) {
|
|
1435
|
+
if (recovery.sessionId)
|
|
1436
|
+
record.state.sessions[agent.runtime] = recovery.sessionId;
|
|
1437
|
+
else
|
|
1438
|
+
delete record.state.sessions[agent.runtime];
|
|
1439
|
+
record.store.writeJson("agentState", record.state);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
catch (error) {
|
|
1443
|
+
const projection = readinessProjection();
|
|
1444
|
+
return { recoveryCommitted: true, generationChanged: recovery.generationChanged, sessionChanged: recovery.sessionChanged,
|
|
1445
|
+
...projection, rearmedCount: recovery.rearmedCount, replayStatus: "pending", remainingPendingCount: projection.pendingCount,
|
|
1446
|
+
readyForFreshScenario: false, inboundObserved: false, code: "state_persistence_failed",
|
|
1447
|
+
error: "recovery committed but Agent session state persistence failed" };
|
|
1448
|
+
}
|
|
1449
|
+
const deadline = Date.now() + Math.max(0, waitReadyMs);
|
|
1450
|
+
let projection = readinessProjection();
|
|
1451
|
+
do {
|
|
1452
|
+
projection = readinessProjection();
|
|
1453
|
+
if (projection.turns === 0 && projection.runtimeReady && projection.channelConnected && !projection.reconnecting)
|
|
1454
|
+
break;
|
|
1455
|
+
if (Date.now() >= deadline)
|
|
1456
|
+
break;
|
|
1457
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(25, Math.max(1, deadline - Date.now()))));
|
|
1458
|
+
} while (true);
|
|
1459
|
+
const replayStatus = projection.pendingCount === 0 ? "consumed" : "pending";
|
|
1460
|
+
const ready = projection.turns === 0 && projection.runtimeReady && projection.channelConnected && !projection.reconnecting;
|
|
1461
|
+
return { recoveryCommitted: true, generationChanged: recovery.generationChanged, sessionChanged: recovery.sessionChanged,
|
|
1462
|
+
...projection, rearmedCount: recovery.rearmedCount, replayStatus, remainingPendingCount: projection.pendingCount,
|
|
1463
|
+
readyForFreshScenario: ready, inboundObserved: false,
|
|
1464
|
+
...(!ready ? { code: "recovery_timeout", error: "recovery committed but Runtime/channel readiness did not converge before timeout" } : {}) };
|
|
1465
|
+
},
|
|
1395
1466
|
async start() {
|
|
1396
1467
|
try {
|
|
1397
1468
|
fs.mkdirSync(larkinHome, { recursive: true });
|