This repository has no description
0

Configure Feed

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

covibe: take the patched omp + client wrapper from the covibe flake

The collab patch lived here and was applied twice (2configs/covibe.nix with the
collab-web SPA, tools/covibe without it), and the client wrapper hardcoded the
four covibe.lassul.us addresses in shell. covibe owns all of that now:

- 2configs/covibe.nix keeps only the deployment: ompPackage and webRoot come
from the module defaults (covibe.packages.${system}.omp and its SPA).
- tools/covibe passes the addresses to covibe-client.override; the CLI's own
COVIBE_* env fallbacks replace the shell script, and `covibe` in a project
directory still starts a session with arguments going to omp.
- covibe.inputs.llm-agents.follows = "llm-agents" keeps one omp revision, so the
patched omp derivation is unchanged (4nxafz2k…-omp-17.1.4.drv): no new build.
- 2configs/omp-collab-autostart.patch deleted (now covibe/nix/).

+25 -237
+6 -3
flake.lock
··· 82 82 "covibe": { 83 83 "inputs": { 84 84 "flake-utils": "flake-utils", 85 + "llm-agents": [ 86 + "llm-agents" 87 + ], 85 88 "nixpkgs": [ 86 89 "nixpkgs" 87 90 ] 88 91 }, 89 92 "locked": { 90 - "lastModified": 1785146837, 91 - "narHash": "sha256-C1E2rNoK3Dh0DFs+qz9imMkrK3f3vFsmuZiYCqh7R+k=", 93 + "lastModified": 1785148849, 94 + "narHash": "sha256-Q8UpBJdAM7KYM5BOuvmZtUcM5b/dgivwa+Sm2TYyZTI=", 92 95 "owner": "lassulus", 93 96 "repo": "covibe", 94 - "rev": "3be3345e862ebc2828e2fdcd0eefb495fda9c4f3", 97 + "rev": "1902e49d62aab3c1c65743d241c81485c32352c3", 95 98 "type": "github" 96 99 }, 97 100 "original": {
+2
flake.nix
··· 84 84 85 85 covibe.url = "github:lassulus/covibe"; 86 86 covibe.inputs.nixpkgs.follows = "nixpkgs"; 87 + # One omp revision (and one build) for covibe's patched omp and llm.omp. 88 + covibe.inputs.llm-agents.follows = "llm-agents"; 87 89 88 90 }; 89 91
-27
2configs/covibe.nix
··· 8 8 self, 9 9 ... 10 10 }: 11 - let 12 - # Patched omp: env-driven headless collab autostart, plus a self-hosted 13 - # collab-web SPA (base path /c/, external analytics stripped) installed to 14 - # share/collab-web so covibe can serve the browser client itself — nothing 15 - # loads from my.omp.sh. 16 - ompPatched = (self.legacyPackages.${pkgs.stdenv.hostPlatform.system}.llm.omp).overrideAttrs (o: { 17 - patches = (o.patches or [ ]) ++ [ ./omp-collab-autostart.patch ]; 18 - postBuild = (o.postBuild or "") + '' 19 - echo "Building self-hosted collab-web (base /c/)..." 20 - sed -i '/um\.can\.ac/d' packages/collab-web/index.html 21 - ( 22 - cd packages/collab-web 23 - bun build ./index.html --outdir dist --minify \ 24 - --entry-naming '[hash].[ext]' --chunk-naming '[hash].[ext]' --asset-naming '[hash].[ext]' \ 25 - --public-path /c/ 26 - mv dist/*.html dist/index.html 27 - cp -R public/. dist/ 28 - ) 29 - ''; 30 - postInstall = (o.postInstall or "") + '' 31 - mkdir -p $out/share 32 - cp -R packages/collab-web/dist $out/share/collab-web 33 - ''; 34 - }); 35 - in 36 11 { 37 12 # covibe: co-vibing dashboard. Launches omp sessions in zellij as the 38 13 # pairprogramming user, shows live sessions + collab QR codes, and exposes a ··· 66 41 services.covibe = { 67 42 enable = true; 68 43 user = "pairprogramming"; 69 - ompPackage = ompPatched; 70 - webRoot = "${ompPatched}/share/collab-web"; 71 44 relayHost = domain; 72 45 dashboard = { 73 46 addr = "127.0.0.1:${toString port}";
-173
2configs/omp-collab-autostart.patch
··· 1 - diff --git a/packages/coding-agent/src/collab/host.ts b/packages/coding-agent/src/collab/host.ts 2 - index 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 - +} 146 - diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts 147 - index 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
+17 -34
tools/covibe/flake-module.nix
··· 1 - { self, inputs, ... }: 1 + { inputs, ... }: 2 2 { 3 3 perSystem = 4 - { pkgs, system, ... }: 5 - let 6 - # Client omp carrying covibe's env-driven headless collab autostart 7 - # (OMP_COLLAB_*) -- the exact patch the neoprism covibe service uses, so a 8 - # session hosts natively against the covibe relay. `covibe session` mints 9 - # the room and passes OMP_COLLAB_* to this omp. 10 - ompCollab = self.legacyPackages.${system}.llm.omp.overrideAttrs (o: { 11 - patches = (o.patches or [ ]) ++ [ ../../2configs/omp-collab-autostart.patch ]; 12 - }); 13 - covibe = inputs.covibe.packages.${system}.covibe; 14 - in 4 + { system, ... }: 15 5 { 16 - # Launch omp as a co-vibing session from any machine: `covibe session 17 - # --dashboard` announces the session over the dashboard's keyless REST API 6 + # Launch omp as a co-vibing session from any machine: `covibe` in a project 7 + # directory announces the session over the dashboard's keyless REST API 18 8 # (name + collab links, heartbeat + pane push), so it shows up in the 19 9 # covibe.lassul.us overview just like a session started locally on 20 10 # neoprism, and is joinable / browser-viewable via the collab relay. The 21 11 # dashboard GCs the announcement once the session exits. 12 + # 13 + # The package, the patched omp and the wrapping all come from the covibe 14 + # flake; only the deployment addresses live here. Arguments go to omp, so 15 + # `covibe --continue` resumes in the current directory. 22 16 packages.covibe = 23 - (pkgs.writeShellApplication { 24 - name = "covibe"; 25 - runtimeInputs = [ pkgs.coreutils ]; 26 - text = '' 27 - dashboard="''${COVIBE_DASHBOARD:-https://covibe.lassul.us}" 28 - relay="''${COVIBE_LOCAL_RELAY:-wss://covibe.lassul.us}" 29 - web="''${COVIBE_WEB_CLIENT:-https://covibe.lassul.us/c}" 30 - host="''${COVIBE_RELAY_HOST:-covibe.lassul.us}" 31 - name="''${COVIBE_NAME:-$(basename "$PWD")}" 32 - 33 - exec ${covibe}/bin/covibe session \ 34 - --dashboard "$dashboard" \ 35 - --relay-host "$host" \ 36 - --web-client "$web" \ 37 - --local-relay "$relay" \ 38 - --omp ${ompCollab}/bin/omp \ 39 - --name "$name" \ 40 - --dir "$PWD" \ 41 - -- "$@" 42 - ''; 17 + (inputs.covibe.packages.${system}.covibe-client.override { 18 + dashboard = "https://covibe.lassul.us"; 19 + relayHost = "covibe.lassul.us"; 20 + webClient = "https://covibe.lassul.us/c"; 21 + localRelay = "wss://covibe.lassul.us"; 22 + defaultArgs = [ 23 + "session" 24 + "--" 25 + ]; 43 26 }).overrideAttrs 44 27 { passthru.usage = builtins.readFile ./usage.kdl; }; 45 28 };