Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
108965ec84 |
@@ -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<GitHost,'lastSyncAt'|'ahead'|'behind'>`-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 <relativeTime>` / `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.
|
||||||
3
tools/relay/.gitignore
vendored
Normal file
3
tools/relay/.gitignore
vendored
Normal file
@@ -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
|
||||||
49
tools/relay/pm
Executable file
49
tools/relay/pm
Executable file
@@ -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 <to> <kind> <body> # 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 <to> <kind> <body>}"
|
||||||
|
kind="${3:?usage: pm send <to> <kind> <body>}"
|
||||||
|
body="${4:?usage: pm send <to> <kind> <body>}"
|
||||||
|
# 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 <to> <kind> <body>}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -1,4 +1,13 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
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 Role = "pm" | "dev-a" | "dev-b" | "dev-c" | "dev-d" | "dev-e" | "dev-f";
|
||||||
export type MessageKind = "status" | "question" | "directive" | "free";
|
export type MessageKind = "status" | "question" | "directive" | "free";
|
||||||
@@ -39,6 +48,11 @@ export class RelayQueue {
|
|||||||
ts: new Date().toISOString(),
|
ts: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
this.queues.get(to)!.push(msg);
|
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;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ function handleToolCall(name: string, args: Record<string, string>) {
|
|||||||
const kind = args.kind as "status" | "question" | "directive" | "free";
|
const kind = args.kind as "status" | "question" | "directive" | "free";
|
||||||
const msg = queue.post(args.from, args.to, kind, args.body);
|
const msg = queue.post(args.from, args.to, kind, args.body);
|
||||||
const ts = new Date(msg.ts).toTimeString().slice(0, 8);
|
const ts = new Date(msg.ts).toTimeString().slice(0, 8);
|
||||||
const preview = args.body.slice(0, 60).replace(/\n/g, " ");
|
const preview = args.body.slice(0, 120).replace(/\n/g, " ");
|
||||||
const ellipsis = args.body.length > 60 ? "..." : "";
|
const ellipsis = args.body.length > 120 ? "..." : "";
|
||||||
process.stdout.write(`[${ts}] ${args.from} → ${args.to} [${kind}] "${preview}${ellipsis}"\n`);
|
process.stdout.write(`[${ts}] ${args.from} → ${args.to} [${kind}] "${preview}${ellipsis}"\n`);
|
||||||
return { content: [{ type: "text" as const, text: JSON.stringify({ id: msg.id }) }] };
|
return { content: [{ type: "text" as const, text: JSON.stringify({ id: msg.id }) }] };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user