1#!/usr/bin/env node
2/**
3 * Canonical durable completion acknowledgement for Impeccable live sessions.
4 */
5
6import { createLiveSessionStore } from './live-session-store.mjs';
7import { readLiveServerInfo } from './impeccable-paths.mjs';
8
9function parseArgs(argv) {
10 const out = { status: 'complete' };
11 for (let i = 0; i < argv.length; i++) {
12 const arg = argv[i];
13 if (arg === '--id') out.id = argv[++i];
14 else if (arg.startsWith('--id=')) out.id = arg.slice('--id='.length);
15 else if (arg === '--discarded' || arg === '--discard') out.status = 'discarded';
16 else if (arg === '--error') { out.status = 'agent_error'; out.message = argv[++i] || 'unknown error'; }
17 else if (arg.startsWith('--error=')) { out.status = 'agent_error'; out.message = arg.slice('--error='.length); }
18 else if (arg === '--help' || arg === '-h') out.help = true;
19 }
20 return out;
21}
22
23export async function completeCli() {
24 const args = parseArgs(process.argv.slice(2));
25 if (args.help || !args.id) {
26 console.log(`Usage: node live-complete.mjs --id SESSION_ID [--discarded|--error MESSAGE]\n\nAppend the final durable session acknowledgement. Use after accept/discard cleanup is verified.`);
27 process.exit(args.help ? 0 : 1);
28 }
29
30 const serverInfo = readServerInfo();
31 const serverResult = serverInfo ? await completeThroughServer(serverInfo, args) : null;
32 if (serverResult?.ok) {
33 const store = createLiveSessionStore({ cwd: process.cwd(), sessionId: args.id });
34 const snapshot = store.getSnapshot(args.id, { includeCompleted: true });
35 console.log(JSON.stringify({ ok: true, id: args.id, phase: snapshot?.phase || args.status, snapshot }, null, 2));
36 return;
37 }
38
39 const store = createLiveSessionStore({ cwd: process.cwd(), sessionId: args.id });
40 const event = args.status === 'discarded'
41 ? { type: 'discarded', id: args.id }
42 : args.status === 'agent_error'
43 ? { type: 'agent_error', id: args.id, message: args.message || 'unknown error' }
44 : { type: 'complete', id: args.id };
45 const snapshot = store.appendEvent(event);
46 console.log(JSON.stringify({ ok: true, id: args.id, phase: snapshot.phase, snapshot }, null, 2));
47}
48
49function readServerInfo() {
50 return readLiveServerInfo(process.cwd())?.info || null;
51}
52
53async function completeThroughServer(info, args) {
54 const type = args.status === 'discarded'
55 ? 'discarded'
56 : args.status === 'agent_error'
57 ? 'error'
58 : 'complete';
59 try {
60 const res = await fetch(`http://localhost:${info.port}/poll`, {
61 method: 'POST',
62 headers: { 'Content-Type': 'application/json' },
63 body: JSON.stringify({ token: info.token, id: args.id, type, message: args.message }),
64 });
65 if (!res.ok) return null;
66 return await res.json();
67 } catch {
68 return null;
69 }
70}
71
72const _running = process.argv[1];
73if (_running?.endsWith('live-complete.mjs') || _running?.endsWith('live-complete.mjs/')) {
74 completeCli();
75}