- 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 <noreply@anthropic.com>
74 lines
2.0 KiB
TypeScript
74 lines
2.0 KiB
TypeScript
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";
|
|
|
|
export interface RelayMessage {
|
|
id: string;
|
|
from: Role;
|
|
to: Role;
|
|
kind: MessageKind;
|
|
body: string;
|
|
ts: string;
|
|
}
|
|
|
|
const KNOWN_ROLES = new Set<string>(["pm", "dev-a", "dev-b", "dev-c", "dev-d", "dev-e", "dev-f"]);
|
|
|
|
export function isRole(s: string): s is Role {
|
|
return KNOWN_ROLES.has(s);
|
|
}
|
|
|
|
export class RelayQueue {
|
|
private readonly queues = new Map<Role, RelayMessage[]>([
|
|
["pm", []],
|
|
["dev-a", []],
|
|
["dev-b", []],
|
|
["dev-c", []],
|
|
["dev-d", []],
|
|
["dev-e", []],
|
|
["dev-f", []],
|
|
]);
|
|
|
|
post(from: Role, to: Role, kind: MessageKind, body: string): RelayMessage {
|
|
const msg: RelayMessage = {
|
|
id: randomUUID(),
|
|
from,
|
|
to,
|
|
kind,
|
|
body,
|
|
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;
|
|
}
|
|
|
|
read(forRole: Role): RelayMessage[] {
|
|
const inbox = this.queues.get(forRole)!;
|
|
const messages = [...inbox];
|
|
inbox.length = 0;
|
|
return messages;
|
|
}
|
|
|
|
pending(forRole: Role): { count: number; kinds: MessageKind[] } {
|
|
const inbox = this.queues.get(forRole)!;
|
|
return {
|
|
count: inbox.length,
|
|
kinds: inbox.map((m) => m.kind),
|
|
};
|
|
}
|
|
}
|