返回 AiToEarn
live-resume.mjs
1 #!/usr/bin/env node
2 /**
3 * Recover the next agent action from the durable live-session journal.
4 */
5
6 import { createLiveSessionStore } from './live-session-store.mjs';
7
8 function manualApplyReplyCommand(eventOrId = 'EVENT_ID') {
9 const id = typeof eventOrId === 'string' ? eventOrId : eventOrId?.id || 'EVENT_ID';
10 return `live-poll.mjs --reply ${id} done --data '<json>'`;
11 }
12
13 export function manualApplyResumeHint(event = {}) {
14 const summary = event.manualApplySummary || summarizeManualApplyEvent(event);
15 const parts = [];
16 if (summary.pageUrl) parts.push(`page ${summary.pageUrl}`);
17 if (summary.chunk) parts.push(`chunk ${summary.chunk.index}/${summary.chunk.total}`);
18 if (Number.isFinite(summary.opCount)) parts.push(`${summary.opCount} op(s)`);
19 if (Number.isFinite(summary.entryCount)) parts.push(`${summary.entryCount} entr${summary.entryCount === 1 ? 'y' : 'ies'}`);
20 if (summary.files?.length) parts.push(`likely files: ${summary.files.join(', ')}`);
21 const scope = parts.length ? ` (${parts.join(', ')})` : '';
22 return `Manual Apply pending${scope}. If you have not already leased it, run live-poll.mjs. Apply the source edits from the manual_edit_apply batch, then reply with ${manualApplyReplyCommand(event.id)}. Polling only leases this work item; it does not commit source edits. Do not run live-commit-manual-edits.mjs for this leased event. Do not poll again before replying.`;
23 }
24
25 function summarizeManualApplyEvent(event = {}) {
26 const entries = Array.isArray(event.batch?.entries) ? event.batch.entries : [];
27 const opCount = entries.reduce((sum, entry) => sum + (Array.isArray(entry.ops) ? entry.ops.length : 0), 0);
28 return {
29 pageUrl: event.pageUrl || null,
30 chunk: event.chunk || null,
31 entryCount: entries.length,
32 opCount,
33 files: collectManualApplyFiles(event.batch),
34 };
35 }
36
37 function collectManualApplyFiles(batch) {
38 const files = [];
39 for (const entry of batch?.entries || []) {
40 for (const op of entry.ops || []) files.push(op.sourceHint?.file);
41 }
42 for (const candidate of batch?.candidates || []) {
43 files.push(candidate.sourceHint?.relativeFile, candidate.sourceHint?.file);
44 for (const item of candidate.textMatches || []) files.push(item.file);
45 for (const item of candidate.objectKeyMatches || []) files.push(item.file);
46 for (const item of candidate.locatorMatches || []) files.push(item.file);
47 for (const item of candidate.contextTextMatches || []) files.push(item.file);
48 }
49 return [...new Set(files.filter((file) => typeof file === 'string' && file.length > 0))].sort();
50 }
51
52 function parseArgs(argv) {
53 const out = { id: null };
54 for (let i = 0; i < argv.length; i++) {
55 const arg = argv[i];
56 if (arg === '--id') out.id = argv[++i];
57 else if (arg.startsWith('--id=')) out.id = arg.slice('--id='.length);
58 else if (arg === '--help' || arg === '-h') out.help = true;
59 }
60 return out;
61 }
62
63 export async function resumeCli() {
64 const args = parseArgs(process.argv.slice(2));
65 if (args.help) {
66 console.log(`Usage: node live-resume.mjs [--id SESSION_ID]\n\nPrint the active durable session checkpoint and the next safe agent action.`);
67 return;
68 }
69
70 const store = createLiveSessionStore({ cwd: process.cwd(), sessionId: args.id || undefined });
71 const snapshot = args.id ? store.getSnapshot(args.id) : store.listActiveSessions()[0] || null;
72 if (!snapshot) {
73 console.log(JSON.stringify({ active: false, nextAction: 'No active durable live session found.' }, null, 2));
74 return;
75 }
76
77 const pending = snapshot.pendingEvent || null;
78 const nextAction = pending
79 ? pending.type === 'manual_edit_apply'
80 ? manualApplyResumeHint(pending)
81 : `Run live-poll.mjs, handle ${pending.type} ${pending.id}, then acknowledge with live-poll.mjs --reply ${pending.id} done.`
82 : snapshot.phase === 'carbonize_required'
83 ? `Finish carbonize cleanup${snapshot.sourceFile ? ` in ${snapshot.sourceFile}` : ''}, then run live-complete.mjs --id ${snapshot.id}.`
84 : snapshot.phase === 'accept_requested'
85 ? `Run live-complete.mjs --id ${snapshot.id} after verifying the accepted variant is written.`
86 : `Inspect ${snapshot.id}; no pending agent event is currently queued.`;
87
88 console.log(JSON.stringify({ active: true, snapshot, pendingEvent: pending, nextAction }, null, 2));
89 }
90
91 const _running = process.argv[1];
92 if (_running?.endsWith('live-resume.mjs') || _running?.endsWith('live-resume.mjs/')) {
93 resumeCli();
94 }
95
95 lines Plain Text