backup current stable ejclaw state
This commit is contained in:
66
src/index.ts
66
src/index.ts
@@ -21,6 +21,7 @@ import { writeGroupsSnapshot } from './agent-runner.js';
|
||||
import {
|
||||
type AssignRoomInput,
|
||||
getAllTasks,
|
||||
getEarliestUnansweredHumanSeq,
|
||||
hasRecentRestartAnnouncement,
|
||||
initDatabase,
|
||||
storeChatMetadata,
|
||||
@@ -55,6 +56,7 @@ import {
|
||||
import { createMessageRuntime } from './message-runtime.js';
|
||||
import { nudgeSchedulerLoop, startSchedulerLoop } from './task-scheduler.js';
|
||||
import { startUnifiedDashboard } from './unified-dashboard.js';
|
||||
import { startUsagePrimer } from './usage-primer.js';
|
||||
import { startWebDashboardServer } from './web-dashboard-server.js';
|
||||
import { Channel, NewMessage, RegisteredGroup } from './types.js';
|
||||
import { logger } from './logger.js';
|
||||
@@ -270,14 +272,29 @@ async function main(): Promise<void> {
|
||||
status: 'processing' | 'waiting';
|
||||
} => status.status !== 'inactive',
|
||||
)
|
||||
.map((status) => ({
|
||||
chatJid: status.jid,
|
||||
groupName: roomBindings[status.jid]?.name || status.jid,
|
||||
status: status.status,
|
||||
elapsedMs: status.elapsedMs,
|
||||
pendingMessages: status.pendingMessages,
|
||||
pendingTasks: status.pendingTasks,
|
||||
}));
|
||||
.map((status) => {
|
||||
// Capture the seq of the earliest unanswered human message for this
|
||||
// chat so the next startup can rewind the agent cursor and re-run
|
||||
// the interrupted turn automatically.
|
||||
let rewindToSeq: number | null = null;
|
||||
try {
|
||||
rewindToSeq = getEarliestUnansweredHumanSeq(status.jid);
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ err, chatJid: status.jid },
|
||||
'Failed to capture rewind seq for shutdown snapshot',
|
||||
);
|
||||
}
|
||||
return {
|
||||
chatJid: status.jid,
|
||||
groupName: roomBindings[status.jid]?.name || status.jid,
|
||||
status: status.status,
|
||||
elapsedMs: status.elapsedMs,
|
||||
pendingMessages: status.pendingMessages,
|
||||
pendingTasks: status.pendingTasks,
|
||||
rewindToSeq,
|
||||
};
|
||||
});
|
||||
const writtenPaths = writeShutdownRestartContext(
|
||||
roomBindings,
|
||||
interruptedGroups,
|
||||
@@ -488,6 +505,37 @@ async function main(): Promise<void> {
|
||||
restartContext,
|
||||
roomBindings,
|
||||
)) {
|
||||
// Auto-resume: rewind the agent cursor to just before the earliest
|
||||
// unanswered human message so the missed-message processor picks it up
|
||||
// again. Claude/Codex sessions are persisted by group folder, so the
|
||||
// agent will resume mid-turn rather than restarting from scratch.
|
||||
if (candidate.rewindToSeq != null && candidate.rewindToSeq > 0) {
|
||||
const cursors = runtimeState.getLastAgentTimestamps();
|
||||
const previousCursor = cursors[candidate.chatJid];
|
||||
const targetCursor = String(candidate.rewindToSeq - 1);
|
||||
// Only rewind if the current cursor is past the target — never advance.
|
||||
const previousNum = previousCursor
|
||||
? Number.parseInt(previousCursor, 10)
|
||||
: 0;
|
||||
if (
|
||||
Number.isFinite(previousNum) &&
|
||||
previousNum >= candidate.rewindToSeq
|
||||
) {
|
||||
cursors[candidate.chatJid] = targetCursor;
|
||||
runtimeState.saveState();
|
||||
logger.info(
|
||||
{
|
||||
chatJid: candidate.chatJid,
|
||||
groupFolder: candidate.groupFolder,
|
||||
previousCursor,
|
||||
rewoundTo: targetCursor,
|
||||
rewindToSeq: candidate.rewindToSeq,
|
||||
},
|
||||
'Rewound agent cursor for interrupted-turn auto-resume',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
queue.enqueueMessageCheck(
|
||||
candidate.chatJid,
|
||||
resolveGroupIpcPath(candidate.groupFolder),
|
||||
@@ -499,6 +547,7 @@ async function main(): Promise<void> {
|
||||
status: candidate.status,
|
||||
pendingMessages: candidate.pendingMessages,
|
||||
pendingTasks: candidate.pendingTasks,
|
||||
rewindToSeq: candidate.rewindToSeq,
|
||||
},
|
||||
'Queued interrupted group for restart recovery',
|
||||
);
|
||||
@@ -522,6 +571,7 @@ async function main(): Promise<void> {
|
||||
purgeOnStart: true,
|
||||
});
|
||||
webDashboardServer = startWebDashboardServer(WEB_DASHBOARD);
|
||||
startUsagePrimer();
|
||||
|
||||
leaseRecoveryTimer = setInterval(() => {
|
||||
const failover = getGlobalFailoverInfo();
|
||||
|
||||
Reference in New Issue
Block a user