live-complete.mjs

 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}