fix(presence): hide cli sessions; use numeric mac build
This commit is contained in:
@@ -70,7 +70,10 @@ final class PresenceReporter {
|
||||
private static func appVersionString() -> String {
|
||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "dev"
|
||||
if let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String {
|
||||
return "\(version) (\(build))"
|
||||
let trimmed = build.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !trimmed.isEmpty, trimmed != version {
|
||||
return "\(version) (\(trimmed))"
|
||||
}
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ This is meant to answer: “Which clients are currently connected?”
|
||||
|
||||
Implementation: `src/gateway/server.ts` (WS `hello` handling uses `hello.client.instanceId` when provided; otherwise falls back to `connId`).
|
||||
|
||||
#### Why one-off CLI commands do not show up
|
||||
|
||||
The CLI connects to the Gateway to execute one-off commands (health/status/send/agent/etc.). These are not “nodes” and would spam the Instances list, so the Gateway does not create presence entries for clients with `client.mode === "cli"`.
|
||||
|
||||
### 3) `system-event` beacons (client-reported presence)
|
||||
|
||||
Clients can publish richer periodic beacons via the `system-event` method. The mac app uses this to report:
|
||||
@@ -112,4 +116,3 @@ The store refreshes periodically and also applies `presence` WS events.
|
||||
- confirm clients send a stable `instanceId` in `hello`
|
||||
- confirm beaconing uses the same `instanceId`
|
||||
- check whether the connection-derived entry is missing `instanceId` (then it will be keyed by `connId` and duplicates are expected on reconnect)
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ BUNDLE_ID="${BUNDLE_ID:-com.steipete.clawdis.debug}"
|
||||
PKG_VERSION="$(cd "$ROOT_DIR" && node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")"
|
||||
BUILD_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
GIT_COMMIT=$(cd "$ROOT_DIR" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
GIT_BUILD_NUMBER=$(cd "$ROOT_DIR" && git rev-list --count HEAD 2>/dev/null || echo "0")
|
||||
APP_VERSION="${APP_VERSION:-$PKG_VERSION}"
|
||||
APP_BUILD="${APP_BUILD:-$PKG_VERSION}"
|
||||
APP_BUILD="${APP_BUILD:-$GIT_BUILD_NUMBER}"
|
||||
BUILD_CONFIG="${BUILD_CONFIG:-debug}"
|
||||
SPARKLE_PUBLIC_ED_KEY="${SPARKLE_PUBLIC_ED_KEY:-AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=}"
|
||||
SPARKLE_FEED_URL="${SPARKLE_FEED_URL:-https://raw.githubusercontent.com/steipete/clawdis/main/appcast.xml}"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import { type AddressInfo, createServer } from "node:net";
|
||||
import os from "node:os";
|
||||
@@ -792,6 +793,47 @@ describe("gateway server", () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("cli connections are not tracked as instances", async () => {
|
||||
const { server, ws } = await startServerWithClient();
|
||||
const cliId = `cli-${randomUUID()}`;
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "hello",
|
||||
minProtocol: 1,
|
||||
maxProtocol: 1,
|
||||
client: {
|
||||
name: "cli",
|
||||
version: "dev",
|
||||
platform: "test",
|
||||
mode: "cli",
|
||||
instanceId: cliId,
|
||||
},
|
||||
caps: [],
|
||||
}),
|
||||
);
|
||||
await onceMessage(ws, (o) => o.type === "hello-ok");
|
||||
|
||||
const presenceP = onceMessage(
|
||||
ws,
|
||||
(o) => o.type === "res" && o.id === "cli-presence",
|
||||
4000,
|
||||
);
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: "req",
|
||||
id: "cli-presence",
|
||||
method: "system-presence",
|
||||
}),
|
||||
);
|
||||
|
||||
const presenceRes = await presenceP;
|
||||
const entries = presenceRes.payload as Array<Record<string, unknown>>;
|
||||
expect(entries.some((e) => e.instanceId === cliId)).toBe(false);
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("refuses to start when port already bound", async () => {
|
||||
const { server: blocker, port } = await occupyPort();
|
||||
await expect(startGatewayServer(port)).rejects.toBeInstanceOf(
|
||||
|
||||
@@ -58,6 +58,7 @@ type Client = {
|
||||
socket: WebSocket;
|
||||
hello: Hello;
|
||||
connId: string;
|
||||
presenceKey?: string;
|
||||
};
|
||||
|
||||
const METHODS = [
|
||||
@@ -545,10 +546,9 @@ export async function startGatewayServer(
|
||||
`webchat disconnected code=${code} reason=${reason?.toString() || "n/a"} conn=${connId}`,
|
||||
);
|
||||
}
|
||||
if (client) {
|
||||
if (client?.presenceKey) {
|
||||
// mark presence as disconnected
|
||||
const key = client.hello.client.instanceId || connId;
|
||||
upsertPresence(key, {
|
||||
upsertPresence(client.presenceKey, {
|
||||
reason: "disconnect",
|
||||
});
|
||||
presenceVersion += 1;
|
||||
@@ -639,8 +639,11 @@ export async function startGatewayServer(
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldTrackPresence = hello.client.mode !== "cli";
|
||||
// synthesize presence entry for this connection (client fingerprint)
|
||||
const presenceKey = hello.client.instanceId || connId;
|
||||
const presenceKey = shouldTrackPresence
|
||||
? hello.client.instanceId || connId
|
||||
: undefined;
|
||||
logWs("in", "hello", {
|
||||
connId,
|
||||
client: hello.client.name,
|
||||
@@ -655,15 +658,17 @@ export async function startGatewayServer(
|
||||
`webchat connected conn=${connId} remote=${remoteAddr ?? "?"} client=${describeHello(hello)}`,
|
||||
);
|
||||
}
|
||||
upsertPresence(presenceKey, {
|
||||
host: hello.client.name || os.hostname(),
|
||||
ip: isLoopbackAddress(remoteAddr) ? undefined : remoteAddr,
|
||||
version: hello.client.version,
|
||||
mode: hello.client.mode,
|
||||
instanceId: hello.client.instanceId,
|
||||
reason: "connect",
|
||||
});
|
||||
presenceVersion += 1;
|
||||
if (presenceKey) {
|
||||
upsertPresence(presenceKey, {
|
||||
host: hello.client.name || os.hostname(),
|
||||
ip: isLoopbackAddress(remoteAddr) ? undefined : remoteAddr,
|
||||
version: hello.client.version,
|
||||
mode: hello.client.mode,
|
||||
instanceId: hello.client.instanceId,
|
||||
reason: "connect",
|
||||
});
|
||||
presenceVersion += 1;
|
||||
}
|
||||
const snapshot = buildSnapshot();
|
||||
if (healthCache) {
|
||||
snapshot.health = healthCache;
|
||||
@@ -692,7 +697,7 @@ export async function startGatewayServer(
|
||||
clearTimeout(handshakeTimer);
|
||||
// Add the client only after the hello response is ready so no tick/presence
|
||||
// events reach it before the handshake completes.
|
||||
client = { socket, hello, connId };
|
||||
client = { socket, hello, connId, presenceKey };
|
||||
logWs("out", "hello-ok", {
|
||||
connId,
|
||||
methods: METHODS.length,
|
||||
|
||||
Reference in New Issue
Block a user