docs: add PR template + node presence beacon
This commit is contained in:
committed by
Peter Steinberger
parent
476bbd2915
commit
672700f2b3
@@ -205,3 +205,71 @@ git worktree remove /tmp/issue-99
|
|||||||
6. **Parallel is OK** — run many Codex processes at once for batch work
|
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
|
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
|
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
|
||||||
|
|||||||
@@ -936,6 +936,70 @@ export async function startGatewayServer(
|
|||||||
? bridgeHost
|
? bridgeHost
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const nodePresenceTimers = new Map<string, ReturnType<typeof setInterval>>();
|
||||||
|
|
||||||
|
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) {
|
if (bridgeEnabled && bridgePort > 0 && bridgeHost) {
|
||||||
try {
|
try {
|
||||||
const started = await startNodeBridgeServer({
|
const started = await startNodeBridgeServer({
|
||||||
@@ -946,38 +1010,8 @@ export async function startGatewayServer(
|
|||||||
canvasHostHost: canvasHostHostForBridge,
|
canvasHostHost: canvasHostHostForBridge,
|
||||||
onRequest: (nodeId, req) => handleBridgeRequest(nodeId, req),
|
onRequest: (nodeId, req) => handleBridgeRequest(nodeId, req),
|
||||||
onAuthenticated: async (node) => {
|
onAuthenticated: async (node) => {
|
||||||
const host = node.displayName?.trim() || node.nodeId;
|
beaconNodePresence(node, "node-connected");
|
||||||
const ip = node.remoteIp?.trim();
|
startNodePresenceTimer(node);
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cfg = await loadVoiceWakeConfig();
|
const cfg = await loadVoiceWakeConfig();
|
||||||
@@ -992,38 +1026,8 @@ export async function startGatewayServer(
|
|||||||
},
|
},
|
||||||
onDisconnected: (node) => {
|
onDisconnected: (node) => {
|
||||||
bridgeUnsubscribeAll(node.nodeId);
|
bridgeUnsubscribeAll(node.nodeId);
|
||||||
const host = node.displayName?.trim() || node.nodeId;
|
stopNodePresenceTimer(node.nodeId);
|
||||||
const ip = node.remoteIp?.trim();
|
beaconNodePresence(node, "node-disconnected");
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
onEvent: handleBridgeEvent,
|
onEvent: handleBridgeEvent,
|
||||||
onPairRequested: (request) => {
|
onPairRequested: (request) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user