From 672700f2b31eb49c22d89c3a33910b10ef539142 Mon Sep 17 00:00:00 2001 From: Mariano Belinky Date: Sun, 4 Jan 2026 03:19:02 +0100 Subject: [PATCH] docs: add PR template + node presence beacon --- skills/coding-agent/SKILL.md | 68 ++++++++++++++++++ src/gateway/server.ts | 132 ++++++++++++++++++----------------- 2 files changed, 136 insertions(+), 64 deletions(-) diff --git a/skills/coding-agent/SKILL.md b/skills/coding-agent/SKILL.md index 05ba08734..46bd20d39 100644 --- a/skills/coding-agent/SKILL.md +++ b/skills/coding-agent/SKILL.md @@ -205,3 +205,71 @@ git worktree remove /tmp/issue-99 6. **Parallel is OK** — run many Codex processes at once for batch work 7. **NEVER start Codex in ~/clawd/** — it'll read your soul docs and get weird ideas about the org chart! Use the target project dir or /tmp for blank slate chats 8. **NEVER checkout branches in ~/Projects/clawdis/** — that's the LIVE Clawdis instance! Clone to /tmp or use git worktree for PR reviews + +--- + +## PR Template (The Razor Standard) + +When submitting PRs to external repos, use this format for quality & maintainer-friendliness: + +```markdown +## Original Prompt +[Exact request/problem statement] + +## What this does +[High-level description] + +**Features:** +- [Key feature 1] +- [Key feature 2] + +**Example usage:** +```bash +# Example +command example +``` +``` + +## Feature intent (maintainer-friendly) +[Why useful, how it fits, workflows it enables] + +## Prompt history (timestamped) +- YYYY-MM-DD HH:MM UTC: [Step 1] +- YYYY-MM-DD HH:MM UTC: [Step 2] + +## How I tested +**Manual verification:** +1. [Test step] - Output: `[result]` +2. [Test step] - Result: [result] + +**Files tested:** +- [Detail] +- [Edge cases] + +## Session logs (implementation) +- [What was researched] +- [What was discovered] +- [Time spent] + +## Implementation details +**New files:** +- `path/file.ts` - [description] + +**Modified files:** +- `path/file.ts` - [change] + +**Technical notes:** +- [Detail 1] +- [Detail 2] + +--- +*Submitted by Razor 🥷 - Mariano's AI agent* +``` + +**Key principles:** +1. Human-written description (no AI slop) +2. Feature intent for maintainers +3. Timestamped prompt history +4. Session logs if using Codex/agent + +**Example:** https://github.com/steipete/bird/pull/22 diff --git a/src/gateway/server.ts b/src/gateway/server.ts index ae3f76ae3..4f0b75d3b 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -936,6 +936,70 @@ export async function startGatewayServer( ? bridgeHost : undefined; + const nodePresenceTimers = new Map>(); + + const stopNodePresenceTimer = (nodeId: string) => { + const timer = nodePresenceTimers.get(nodeId); + if (timer) { + clearInterval(timer); + } + nodePresenceTimers.delete(nodeId); + }; + + const beaconNodePresence = (node: { + nodeId: string; + displayName?: string; + remoteIp?: string; + version?: string; + platform?: string; + deviceFamily?: string; + modelIdentifier?: string; + }, reason: string) => { + const host = node.displayName?.trim() || node.nodeId; + const rawIp = node.remoteIp?.trim(); + const ip = rawIp && !isLoopbackAddress(rawIp) ? rawIp : undefined; + const version = node.version?.trim() || "unknown"; + const platform = node.platform?.trim() || undefined; + const deviceFamily = node.deviceFamily?.trim() || undefined; + const modelIdentifier = node.modelIdentifier?.trim() || undefined; + const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason ${reason}`; + upsertPresence(node.nodeId, { + host, + ip, + version, + platform, + deviceFamily, + modelIdentifier, + mode: "remote", + reason, + lastInputSeconds: 0, + instanceId: node.nodeId, + text, + }); + presenceVersion += 1; + broadcast( + "presence", + { presence: listSystemPresence() }, + { + dropIfSlow: true, + stateVersion: { + presence: presenceVersion, + health: healthVersion, + }, + }, + ); + }; + + const startNodePresenceTimer = (node: { nodeId: string }) => { + stopNodePresenceTimer(node.nodeId); + nodePresenceTimers.set( + node.nodeId, + setInterval(() => { + beaconNodePresence(node, "periodic"); + }, 180_000), + ); + }; + if (bridgeEnabled && bridgePort > 0 && bridgeHost) { try { const started = await startNodeBridgeServer({ @@ -946,38 +1010,8 @@ export async function startGatewayServer( canvasHostHost: canvasHostHostForBridge, onRequest: (nodeId, req) => handleBridgeRequest(nodeId, req), onAuthenticated: async (node) => { - const host = node.displayName?.trim() || node.nodeId; - const ip = node.remoteIp?.trim(); - const version = node.version?.trim() || "unknown"; - const platform = node.platform?.trim() || undefined; - const deviceFamily = node.deviceFamily?.trim() || undefined; - const modelIdentifier = node.modelIdentifier?.trim() || undefined; - const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason node-connected`; - upsertPresence(node.nodeId, { - host, - ip, - version, - platform, - deviceFamily, - modelIdentifier, - mode: "remote", - reason: "node-connected", - lastInputSeconds: 0, - instanceId: node.nodeId, - text, - }); - presenceVersion += 1; - broadcast( - "presence", - { presence: listSystemPresence() }, - { - dropIfSlow: true, - stateVersion: { - presence: presenceVersion, - health: healthVersion, - }, - }, - ); + beaconNodePresence(node, "node-connected"); + startNodePresenceTimer(node); try { const cfg = await loadVoiceWakeConfig(); @@ -992,38 +1026,8 @@ export async function startGatewayServer( }, onDisconnected: (node) => { bridgeUnsubscribeAll(node.nodeId); - const host = node.displayName?.trim() || node.nodeId; - const ip = node.remoteIp?.trim(); - const version = node.version?.trim() || "unknown"; - const platform = node.platform?.trim() || undefined; - const deviceFamily = node.deviceFamily?.trim() || undefined; - const modelIdentifier = node.modelIdentifier?.trim() || undefined; - const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason node-disconnected`; - upsertPresence(node.nodeId, { - host, - ip, - version, - platform, - deviceFamily, - modelIdentifier, - mode: "remote", - reason: "node-disconnected", - lastInputSeconds: 0, - instanceId: node.nodeId, - text, - }); - presenceVersion += 1; - broadcast( - "presence", - { presence: listSystemPresence() }, - { - dropIfSlow: true, - stateVersion: { - presence: presenceVersion, - health: healthVersion, - }, - }, - ); + stopNodePresenceTimer(node.nodeId); + beaconNodePresence(node, "node-disconnected"); }, onEvent: handleBridgeEvent, onPairRequested: (request) => {