diff --git a/docs/concepts/presence.md b/docs/concepts/presence.md index 5e1e776e5..a9c6e2003 100644 --- a/docs/concepts/presence.md +++ b/docs/concepts/presence.md @@ -18,12 +18,12 @@ provide quick operator visibility. Presence entries are structured objects with fields like: -- `instanceId` (optional but strongly recommended): stable client identity +- `instanceId` (optional but strongly recommended): stable client identity (usually `connect.client.instanceId`) - `host`: human‑friendly host name - `ip`: best‑effort IP address - `version`: client version string - `deviceFamily` / `modelIdentifier`: hardware hints -- `mode`: `gateway`, `app`, `webchat`, `cli`, `node`, ... +- `mode`: `ui`, `webchat`, `cli`, `backend`, `probe`, `test`, `node`, ... - `lastInputSeconds`: “seconds since last user input” (if known) - `reason`: `self`, `connect`, `node-connected`, `periodic`, ... - `ts`: last update timestamp (ms since epoch) @@ -62,7 +62,7 @@ for that node and refreshes it periodically so it doesn’t expire. Presence entries are stored in a single in‑memory map: - Entries are keyed by a **presence key**. -- The best key is a stable `instanceId` that survives restarts. +- The best key is a stable `instanceId` (from `connect.client.instanceId`) that survives restarts. - Keys are case‑insensitive. If a client reconnects without a stable `instanceId`, it may show up as a @@ -94,6 +94,6 @@ indicator (Active/Idle/Stale) based on the age of the last update. - To see the raw list, call `system-presence` against the Gateway. - If you see duplicates: - - confirm clients send a stable `instanceId` in the handshake + - confirm clients send a stable `client.instanceId` in the handshake - confirm periodic beacons use the same `instanceId` - check whether the connection‑derived entry is missing `instanceId` (duplicates are expected) diff --git a/docs/gateway/index.md b/docs/gateway/index.md index e5d2f58f4..0ac74a0ac 100644 --- a/docs/gateway/index.md +++ b/docs/gateway/index.md @@ -109,7 +109,7 @@ CLAWDBOT_CONFIG_PATH=~/.clawdbot/b.json CLAWDBOT_STATE_DIR=~/.clawdbot-b clawdbo - After handshake: - Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}` - Events: `{type:"event", event, payload, seq?, stateVersion?}` -- Structured presence entries: `{host, ip, version, platform?, deviceFamily?, modelIdentifier?, mode, lastInputSeconds?, ts, reason?, tags?[], instanceId? }`. +- Structured presence entries: `{host, ip, version, platform?, deviceFamily?, modelIdentifier?, mode, lastInputSeconds?, ts, reason?, tags?[], instanceId? }` (for WS clients, `instanceId` comes from `connect.client.instanceId`). - `agent` responses are two-stage: first `res` ack `{runId,status:"accepted"}`, then a final `res` `{runId,status:"ok"|"error",summary}` after the run finishes; streamed output arrives as `event:"agent"`. ## Methods (initial set) @@ -124,7 +124,7 @@ CLAWDBOT_CONFIG_PATH=~/.clawdbot/b.json CLAWDBOT_STATE_DIR=~/.clawdbot-b clawdbo - `node.invoke` — invoke a command on a node (e.g. `canvas.*`, `camera.*`). - `node.pair.*` — pairing lifecycle (`request`, `list`, `approve`, `reject`, `verify`). -See also: [Presence](/concepts/presence) for how presence is produced/deduped and why `instanceId` matters. +See also: [Presence](/concepts/presence) for how presence is produced/deduped and why a stable `client.instanceId` matters. ## Events - `agent` — streamed tool/output events from the agent run (seq-tagged). diff --git a/src/gateway/server.ts b/src/gateway/server.ts index df5c42356..dcee0da48 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -1588,8 +1588,10 @@ export async function startGatewayServer( const authMethod = authResult.method ?? "none"; const shouldTrackPresence = !isGatewayCliClient(connectParams.client); + const clientId = connectParams.client.id; + const instanceId = connectParams.client.instanceId; const presenceKey = shouldTrackPresence - ? connectParams.client.instanceId || connId + ? (instanceId ?? connId) : undefined; logWs("in", "connect", { @@ -1598,7 +1600,7 @@ export async function startGatewayServer( clientDisplayName: connectParams.client.displayName, version: connectParams.client.version, mode: connectParams.client.mode, - instanceId: connectParams.client.instanceId, + clientId, platform: connectParams.client.platform, auth: authMethod, }); @@ -1621,7 +1623,7 @@ export async function startGatewayServer( deviceFamily: connectParams.client.deviceFamily, modelIdentifier: connectParams.client.modelIdentifier, mode: connectParams.client.mode, - instanceId: connectParams.client.instanceId, + instanceId, reason: "connect", }); presenceVersion += 1; diff --git a/src/gateway/test-helpers.ts b/src/gateway/test-helpers.ts index 8e351e8e2..3183793e7 100644 --- a/src/gateway/test-helpers.ts +++ b/src/gateway/test-helpers.ts @@ -543,6 +543,8 @@ export async function connectReq( version: string; platform: string; mode: string; + deviceFamily?: string; + modelIdentifier?: string; instanceId?: string; }; },