fix: require gateway client id

# Conflicts:
#	apps/macos/Sources/Clawdbot/GatewayChannel.swift
#	docs/concepts/typebox.md
#	docs/gateway/index.md
#	src/commands/onboard-non-interactive.gateway-auth.test.ts
#	src/commands/onboard-non-interactive.lan-auto-token.test.ts
#	src/gateway/call.ts
#	src/gateway/client.ts
#	src/gateway/gateway.wizard.e2e.test.ts
#	src/gateway/probe.ts
#	src/gateway/protocol/schema.ts
#	src/gateway/server.auth.test.ts
#	src/gateway/server.health.test.ts
#	src/gateway/server.ts
#	src/gateway/test-helpers.ts
#	src/tui/gateway-chat.ts
This commit is contained in:
Peter Steinberger
2026-01-12 04:55:31 +00:00
parent d26518687a
commit 8e1cdf3a1f
4 changed files with 13 additions and 9 deletions

View File

@@ -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`: humanfriendly host name
- `ip`: besteffort 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 doesnt expire.
Presence entries are stored in a single inmemory 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 caseinsensitive.
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 connectionderived entry is missing `instanceId` (duplicates are expected)

View File

@@ -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).

View File

@@ -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;

View File

@@ -543,6 +543,8 @@ export async function connectReq(
version: string;
platform: string;
mode: string;
deviceFamily?: string;
modelIdentifier?: string;
instanceId?: string;
};
},