This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / 2configs / omp-collab-autostart.patch
7.4 kB 173 lines
1diff --git a/packages/coding-agent/src/collab/host.ts b/packages/coding-agent/src/collab/host.ts 2index 5d9d103..722b01b 100644 3--- a/packages/coding-agent/src/collab/host.ts 4+++ b/packages/coding-agent/src/collab/host.ts 5@@ -21,6 +21,7 @@ import type { 6 AgentEvent as WireAgentEvent, 7 SessionEntry as WireSessionEntry, 8 } from "@oh-my-pi/pi-wire"; 9+import { ROOM_KEY_BYTES, WRITE_TOKEN_BYTES } from "@oh-my-pi/pi-wire"; 10 import type { InteractiveModeContext } from "../modes/types"; 11 import { AgentLifecycleManager } from "../registry/agent-lifecycle"; 12 import { type AgentRef, AgentRegistry } from "../registry/agent-registry"; 13@@ -207,10 +208,14 @@ export class CollabHost { 14 } 15 } 16 17- async start(relayUrl: string, webUrl = ""): Promise<void> { 18- const rawKey = generateRoomKey(); 19- const writeToken = generateWriteToken(); 20- const roomId = generateRoomId(); 21+ async start( 22+ relayUrl: string, 23+ webUrl = "", 24+ preset?: { roomId: string; key: Uint8Array; writeToken: Uint8Array }, 25+ ): Promise<void> { 26+ const rawKey = preset?.key ?? generateRoomKey(); 27+ const writeToken = preset?.writeToken ?? generateWriteToken(); 28+ const roomId = preset?.roomId ?? generateRoomId(); 29 this.#writeToken = writeToken; 30 this.#link = formatCollabLink(relayUrl, roomId, rawKey, writeToken); 31 this.#webLink = formatCollabWebLink(relayUrl, roomId, rawKey, writeToken, webUrl); 32@@ -289,6 +294,9 @@ export class CollabHost { 33 /** Broadcast a goodbye, detach all taps, and close the socket. */ 34 async stop(reason: string): Promise<void> { 35 if (this.#stopped) return; 36+ // Only the slash commands stop with this reason; record it so the covibe 37+ // supervisor treats it as intent and does not resurrect the room. 38+ if (reason === USER_STOP_REASON) userStoppedHosting = true; 39 this.#socket?.send({ t: "bye", reason }); 40 await this.#teardown(); 41 } 42@@ -689,3 +697,103 @@ export class CollabHost { 43 this.#ctx.ui.requestRender(); 44 } 45 } 46+ 47+/** 48+ * covibe integration: host headlessly on a caller-owned room. 49+ * 50+ * OMP_COLLAB_RELAY/_ROOM/_KEY (key = base64url of key‖writeToken) let the 51+ * orchestrator own a stable, enumerable link instead of minting or scraping one. 52+ * Hosting is supervised because a host that goes away never comes back on its 53+ * own: CollabSocket treats 4001/4004/4009/4029 as fatal (no reconnect), and the 54+ * host also stops itself on a session switch — which happens seconds into a 55+ * fresh session, when the first prompt creates the real session id. A one-shot 56+ * start therefore loses the room for the rest of a multi-hour session. Since the 57+ * room id and key are fixed by the orchestrator, re-hosting restores the very 58+ * same link, so already-shared links and QR codes keep working. 59+ */ 60+ 61+/** Reason the /collab stop and /leave commands pass to {@link CollabHost.stop}. */ 62+const USER_STOP_REASON = "host stopped"; 63+const AUTOSTART_POLL_MS = 2_000; 64+const AUTOSTART_BACKOFF_BASE_MS = 1_000; 65+const AUTOSTART_BACKOFF_MAX_MS = 30_000; 66+ 67+/** Set once the user explicitly stops hosting, disabling re-hosting. */ 68+let userStoppedHosting = false; 69+ 70+export interface CollabAutostartConfig { 71+ relayUrl: string; 72+ webUrl: string; 73+ preset?: { roomId: string; key: Uint8Array; writeToken: Uint8Array }; 74+} 75+ 76+/** 77+ * Read the autostart env and remove it from process.env. Consuming it matters: 78+ * children inherit the environment, so a nested omp (or a worker daemon) would 79+ * otherwise try to host the same room and the relay rejects one of them with 80+ * 4009 — if the child wins the race, the real session is locked out. 81+ */ 82+export function readCollabAutostartEnv(): CollabAutostartConfig | undefined { 83+ const relayUrl = process.env.OMP_COLLAB_RELAY; 84+ const webUrl = process.env.OMP_COLLAB_WEB ?? ""; 85+ const roomId = process.env.OMP_COLLAB_ROOM; 86+ const keyB64 = process.env.OMP_COLLAB_KEY; 87+ delete process.env.OMP_COLLAB_RELAY; 88+ delete process.env.OMP_COLLAB_WEB; 89+ delete process.env.OMP_COLLAB_ROOM; 90+ delete process.env.OMP_COLLAB_KEY; 91+ if (!relayUrl) return undefined; 92+ let preset: CollabAutostartConfig["preset"]; 93+ if (roomId && keyB64) { 94+ const secret = new Uint8Array(Buffer.from(keyB64, "base64url")); 95+ if (secret.byteLength === ROOM_KEY_BYTES + WRITE_TOKEN_BYTES) { 96+ preset = { 97+ roomId, 98+ key: secret.subarray(0, ROOM_KEY_BYTES), 99+ writeToken: secret.subarray(ROOM_KEY_BYTES), 100+ }; 101+ } else { 102+ logger.warn("collab autostart: OMP_COLLAB_KEY has unexpected length", { 103+ bytes: secret.byteLength, 104+ want: ROOM_KEY_BYTES + WRITE_TOKEN_BYTES, 105+ }); 106+ } 107+ } 108+ return { relayUrl, webUrl, preset }; 109+} 110+ 111+/** 112+ * Host on the configured room and keep hosting for the life of the process: 113+ * whenever the host goes away without the user asking, start a new one on the 114+ * same room. No-ops when no relay is configured. 115+ */ 116+export async function superviseAutostartCollab(ctx: InteractiveModeContext): Promise<void> { 117+ const cfg = readCollabAutostartEnv(); 118+ if (!cfg) return; 119+ let attempt = 0; 120+ for (;;) { 121+ if (userStoppedHosting) return; 122+ // Joining someone else's session takes precedence over hosting ours. 123+ if (!ctx.collabHost && !ctx.collabGuest) { 124+ try { 125+ const host = new CollabHost(ctx); 126+ await host.start(cfg.relayUrl, cfg.webUrl, cfg.preset); 127+ ctx.collabHost = host; 128+ attempt = 0; 129+ logger.info("collab autostart: hosting", { room: cfg.preset?.roomId, relay: cfg.relayUrl }); 130+ } catch (err) { 131+ attempt++; 132+ const delay = Math.min(AUTOSTART_BACKOFF_MAX_MS, AUTOSTART_BACKOFF_BASE_MS * 2 ** (attempt - 1)); 133+ logger.warn("collab autostart: host start failed, retrying", { 134+ error: String(err), 135+ attempt, 136+ delayMs: delay, 137+ room: cfg.preset?.roomId, 138+ }); 139+ await Bun.sleep(delay); 140+ continue; 141+ } 142+ } 143+ await Bun.sleep(AUTOSTART_POLL_MS); 144+ } 145+} 146diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts 147index 9a444a4..d38f4a2 100644 148--- a/packages/coding-agent/src/modes/interactive-mode.ts 149+++ b/packages/coding-agent/src/modes/interactive-mode.ts 150@@ -55,6 +55,7 @@ import chalk from "chalk"; 151 import { reset as resetCapabilities } from "../capability"; 152 import type { CollabGuestLink } from "../collab/guest"; 153 import type { CollabHost } from "../collab/host"; 154+import { superviseAutostartCollab } from "../collab/host"; 155 import { KeybindingsManager } from "../config/keybindings"; 156 import { formatModelString, type ResolvedModelRoleValue } from "../config/model-resolver"; 157 import { applyProviderGlobalsFromSettings } from "../config/provider-globals"; 158@@ -1137,6 +1138,15 @@ export class InteractiveMode implements InteractiveModeContext { 159 this.statusLine.watchBranch(() => { 160 this.ui.requestRender(); 161 }); 162+ 163+ // covibe: headless collab hosting from env (OMP_COLLAB_*). Supervised and 164+ // fire-and-forget: the TUI never waits on the relay, and an involuntary 165+ // stop (relay drop, freed room, or the session-switch self-stop that fires 166+ // when the first prompt creates the real session id) re-hosts the same 167+ // room, so links and QR codes already shared stay valid. 168+ void superviseAutostartCollab(this).catch((err: unknown) => 169+ logger.warn(`collab autostart supervisor failed: ${String(err)}`), 170+ ); 171 } 172 173 /** Reload the title-generation system prompt override for the provided working