From 108965ec8498f9616930578afa0a72405c6bab1c Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sun, 31 May 2026 22:51:02 -0400 Subject: [PATCH] tools(relay): durable JSONL message log + pm wrapper + 120-char preview - queue.ts: append every posted message to relay-log.jsonl (full body, survives the consume-once drain + restarts). gitignored. - server.ts: bump the stdout preview from 60 to 120 chars. - tools/relay/pm: absolute-path bash wrapper (read|pending|send) so relay ops work from any cwd without cd or hand-built JSON escaping. - Fold in Dev-C's Phase 6 ARCHITECTURE.md slice as a coordination artifact. Co-Authored-By: Claude Opus 4.8 --- .../v0.7-dev-c-architecture-slice.md | 68 +++++++++++++++++++ tools/relay/.gitignore | 3 + tools/relay/pm | 49 +++++++++++++ tools/relay/queue.ts | 14 ++++ tools/relay/server.ts | 4 +- 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/coordination/v0.7-dev-c-architecture-slice.md create mode 100644 tools/relay/.gitignore create mode 100755 tools/relay/pm diff --git a/docs/superpowers/coordination/v0.7-dev-c-architecture-slice.md b/docs/superpowers/coordination/v0.7-dev-c-architecture-slice.md new file mode 100644 index 0000000..5a7835d --- /dev/null +++ b/docs/superpowers/coordination/v0.7-dev-c-architecture-slice.md @@ -0,0 +1,68 @@ +# Dev-C ARCHITECTURE.md slice — Plan C Phase 6 (`get_vault_status` + sidebar status indicator) + +Ready-to-fold additions for `extension/ARCHITECTURE.md`, scoped to Dev-C's Phase 6 work only. +Phase 3 (`create_vault`/`attach_vault`, setup-SW migration) and Phase 4 (the `vault.ts` → +`vault-shell`/`vault-sidebar`/`vault-list`/`vault-drawer`/`vault-form-wrapper` split) doc updates +are Dev-A's / Dev-B's slices — not included here. + +Merged to origin/main as `397cc78` (Merge Plan C Phase 6). Local source ref: `675452a`. + +--- + +## 1. SW message-protocol row — `get_vault_status` (read-only, popup-only) + +**Where:** the `router/popup-only.ts` bullet in the service-worker module map (around line 270), +and/or wherever the read-only popup messages are enumerated. + +**Add:** + +> - `get_vault_status` (popup-only, read-only) — returns the cached sync summary +> `{ ahead, behind, lastSyncAt, pendingItems }` with **no network call**. `ahead`/`behind`/ +> `lastSyncAt` are read straight off `state.gitHost` (populated by the `sync` handler, which +> records `lastSyncAt = Math.floor(Date.now()/1000)` — unix **seconds** — after a successful +> manifest fetch). `pendingItems` is a live count of active (non-trashed) manifest entries via +> `vault.listItems(manifest).length`. `ahead`/`behind` are structurally always `0` in the +> extension (it writes straight to the host via the Contents REST API; there is no local commit +> graph) and exist for parity with `relicario status`. Handler: `vault.handleGetVaultStatus(state)` +> — synchronous; its `Pick`-typed param both breaks the +> `PopupState` import cycle and structurally forbids it from making a network call. + +## 2. `git-host.ts` cache fields + +**Where:** the `git-host.ts` bullet in the SW module map (around line 299, listing the interface methods). + +**Amend** the interface description to note the cached sync metadata: + +> The `GitHost` interface also carries cached sync metadata — +> `lastSyncAt: number | null` (unix seconds), `ahead: number`, `behind: number` — initialized to +> `null`/`0`/`0` in both `GiteaHost` and `GitHubHost`. The cache rides the gitHost lifecycle: it is +> created on unlock and cleared whenever `state.gitHost` is nulled — on session-timer expiry +> (`index.ts`) **and** on the explicit `lock` message handler (`popup-only.ts`), which now nulls +> `state.gitHost` symmetrically so a lock→unlock cycle can't surface a stale `lastSyncAt`. + +## 3. Sidebar status-indicator UI flow + +**Where:** the `src/vault/` module map (around line 184). Add a `vault-status.ts` entry and a note on +the `vault-sidebar.ts` footer wiring. (If Dev-B's Phase 4 slice has already added the `vault-sidebar.ts` +entry, fold the status note into it rather than duplicating.) + +**Add:** + +> - `vault-status.ts` — sidebar-footer sync indicator renderer. `renderStatusIndicator(el, status)` +> is pure DOM: it renders, by priority, `N pending` / `N ahead` / `N behind`, falling back to +> `in sync`, plus a `last sync ` / `never synced` line. Reuses `shared/glyphs.ts` +> (`GLYPH_PENDING`/`AHEAD`/`BEHIND`/`SYNCED`) and `shared/relative-time.ts`. `VaultStatus` is an +> alias of `GetVaultStatusResponse['data']`, so the renderer's input shape is single-sourced from +> the message contract and can't drift from the SW handler. +> - **Status-indicator flow** (in the `vault-sidebar.ts` entry): the footer holds a +> `#vault-status-slot` plus a manual `↻` refresh button (`GLYPH_REFRESH`). `wireSidebar` calls +> `refreshStatus()` once on mount and again on the button's click — sending `get_vault_status` via +> `ctx.sendMessage` and rendering the result into the slot. There is **no timer polling**: the +> indicator only refreshes on mount + explicit button press, matching the spec's +> no-network-without-user-intent discipline (sync is user-initiated). + +## 4. Living-docs note + +This closes the last `relicario status` CLI/extension parity gap (called out in the extension +restructure spec, `docs/superpowers/specs/2026-05-04-extension-restructure-design.md`). `STATUS.md` +should move the extension-restructure line to shipped as part of the Task 7.1 pass. diff --git a/tools/relay/.gitignore b/tools/relay/.gitignore new file mode 100644 index 0000000..2e46c05 --- /dev/null +++ b/tools/relay/.gitignore @@ -0,0 +1,3 @@ +# Runtime message archive written by queue.ts post() — local relay traffic, +# not source. Regenerated each session; never committed. +relay-log.jsonl diff --git a/tools/relay/pm b/tools/relay/pm new file mode 100755 index 0000000..cc7f895 --- /dev/null +++ b/tools/relay/pm @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# PM relay helper — absolute-path wrapper around call.py so it can be invoked +# from ANY working directory with no `cd` and no JSON-quoting by hand. +# +# Usage: +# tools/relay/pm read # drain PM inbox +# tools/relay/pm pending # pending counts for all roles +# tools/relay/pm send # post_message from pm +# e.g. tools/relay/pm send dev-c directive "## DIRECTIVE ... " +# +# Always works regardless of cwd because it resolves call.py by absolute path. +set -euo pipefail + +RELAY_DIR="/home/alee/Sources/relicario/tools/relay" +CALL="python3 $RELAY_DIR/call.py" + +cmd="${1:-}" +case "$cmd" in + read) + $CALL read_messages '{"for":"pm"}' + ;; + pending) + for r in dev-a dev-b dev-c pm; do + printf '%s: ' "$r" + $CALL list_pending "{\"for\":\"$r\"}" + echo + done + ;; + send) + to="${2:?usage: pm send }" + kind="${3:?usage: pm send }" + body="${4:?usage: pm send }" + # Build JSON with python to handle escaping of the body safely. + python3 - "$to" "$kind" "$body" <<'PY' +import json, sys, urllib.request +to, kind, body = sys.argv[1], sys.argv[2], sys.argv[3] +payload = {"from": "pm", "to": to, "kind": kind, "body": body} +import subprocess +print(subprocess.run( + ["python3", "/home/alee/Sources/relicario/tools/relay/call.py", + "post_message", json.dumps(payload)], + capture_output=True, text=True).stdout, end="") +PY + ;; + *) + echo "usage: pm {read|pending|send }" >&2 + exit 2 + ;; +esac diff --git a/tools/relay/queue.ts b/tools/relay/queue.ts index 992ade9..50b9e93 100644 --- a/tools/relay/queue.ts +++ b/tools/relay/queue.ts @@ -1,4 +1,13 @@ import { randomUUID } from "node:crypto"; +import { appendFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +// Append-only archive of every posted message. The in-memory queues are +// consume-once (read() drains the inbox) and vanish on restart, so this is +// the only durable, full-body record of relay traffic. One JSON object per +// line; never truncated. +const LOG_PATH = join(dirname(fileURLToPath(import.meta.url)), "relay-log.jsonl"); export type Role = "pm" | "dev-a" | "dev-b" | "dev-c" | "dev-d" | "dev-e" | "dev-f"; export type MessageKind = "status" | "question" | "directive" | "free"; @@ -39,6 +48,11 @@ export class RelayQueue { ts: new Date().toISOString(), }; this.queues.get(to)!.push(msg); + try { + appendFileSync(LOG_PATH, JSON.stringify(msg) + "\n"); + } catch { + // Logging is best-effort; never let a disk error drop a message. + } return msg; } diff --git a/tools/relay/server.ts b/tools/relay/server.ts index 9d768f5..e6b45e0 100644 --- a/tools/relay/server.ts +++ b/tools/relay/server.ts @@ -86,8 +86,8 @@ function handleToolCall(name: string, args: Record) { const kind = args.kind as "status" | "question" | "directive" | "free"; const msg = queue.post(args.from, args.to, kind, args.body); const ts = new Date(msg.ts).toTimeString().slice(0, 8); - const preview = args.body.slice(0, 60).replace(/\n/g, " "); - const ellipsis = args.body.length > 60 ? "..." : ""; + const preview = args.body.slice(0, 120).replace(/\n/g, " "); + const ellipsis = args.body.length > 120 ? "..." : ""; process.stdout.write(`[${ts}] ${args.from} → ${args.to} [${kind}] "${preview}${ellipsis}"\n`); return { content: [{ type: "text" as const, text: JSON.stringify({ id: msg.id }) }] }; }