@cydm/happy-elves 0.1.0-beta.75 → 0.1.0-beta.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ import { handleAccount } from "./account.js";
2
2
  import { handleCollect } from "./collect.js";
3
3
  import { handleConfig } from "./config.js";
4
4
  import { handleDaemon } from "./daemon.js";
5
+ import { handleDiagnostics } from "./diagnostics.js";
5
6
  import { handleGateway } from "./gateway.js";
6
7
  import { handleLoop } from "./loop.js";
7
8
  import { handleMachine } from "./machine.js";
@@ -22,6 +23,7 @@ const domainHandlers = [
22
23
  handleToken,
23
24
  handleLoop,
24
25
  handleDaemon,
26
+ handleDiagnostics,
25
27
  handleRelay,
26
28
  handleMachine,
27
29
  handleMemory,
@@ -0,0 +1,2 @@
1
+ import type { CommandInput } from "./command.js";
2
+ export declare function handleDiagnostics({ domain, action, flags }: CommandInput): Promise<boolean>;
@@ -0,0 +1,71 @@
1
+ import { UiProbeClient } from "../../../../packages/client/dist/index.js";
2
+ import { CliError, ok, parseDurationMs, readConfig, requireString, wantsJson } from "./lib/index.js";
3
+ const DEFAULT_CAPTURE_TIMEOUT_MS = 30_000;
4
+ const DEFAULT_WATCH_DURATION_MS = 10 * 60_000;
5
+ const MAX_WATCH_DURATION_MS = 10 * 60_000;
6
+ function durationFlag(flags, key, fallbackMs, maximumMs) {
7
+ const durationMs = parseDurationMs(flags[key], fallbackMs);
8
+ if (!Number.isFinite(durationMs) || durationMs <= 0 || durationMs > maximumMs) {
9
+ throw new CliError(`Invalid --${key}: expected a positive duration no greater than 10m`, "INVALID_ARGUMENT");
10
+ }
11
+ return durationMs;
12
+ }
13
+ function printTelemetry(telemetry) {
14
+ console.log(JSON.stringify(telemetry));
15
+ }
16
+ export async function handleDiagnostics({ domain, action, flags }) {
17
+ if (domain !== "diagnostics")
18
+ return false;
19
+ const config = await readConfig(flags);
20
+ const client = new UiProbeClient(config);
21
+ try {
22
+ if (action === "surfaces") {
23
+ const surfaces = await client.listSurfaces();
24
+ if (wantsJson(flags))
25
+ ok("diagnostics.surfaces", { surfaces });
26
+ else if (surfaces.length === 0)
27
+ console.log("No Device is sharing live diagnostics.");
28
+ else {
29
+ for (const surface of surfaces) {
30
+ console.log(`${surface.deviceId} ${surface.surfaceId} ${surface.kind} expires ${new Date(surface.expiresAt).toISOString()}`);
31
+ }
32
+ }
33
+ return true;
34
+ }
35
+ if (action === "capture") {
36
+ const deviceId = requireString(flags, "device");
37
+ const surfaceId = requireString(flags, "surface");
38
+ const timeoutMs = durationFlag(flags, "timeout", DEFAULT_CAPTURE_TIMEOUT_MS, MAX_WATCH_DURATION_MS);
39
+ const telemetry = await client.capture({ deviceId, surfaceId, timeoutMs });
40
+ if (wantsJson(flags))
41
+ ok("diagnostics.capture", telemetry);
42
+ else
43
+ console.log(JSON.stringify(telemetry, null, 2));
44
+ return true;
45
+ }
46
+ if (action === "watch") {
47
+ if (flags.jsonl !== true) {
48
+ throw new CliError("diagnostics watch requires --jsonl", "MISSING_ARGUMENT");
49
+ }
50
+ const deviceId = requireString(flags, "device");
51
+ const surfaceId = requireString(flags, "surface");
52
+ const durationMs = durationFlag(flags, "duration", DEFAULT_WATCH_DURATION_MS, MAX_WATCH_DURATION_MS);
53
+ const abortController = new AbortController();
54
+ const onSigint = () => abortController.abort();
55
+ process.once("SIGINT", onSigint);
56
+ try {
57
+ const watch = await client.watch({ deviceId, surfaceId, durationMs, signal: abortController.signal });
58
+ for await (const telemetry of watch)
59
+ printTelemetry(telemetry);
60
+ }
61
+ finally {
62
+ process.removeListener("SIGINT", onSigint);
63
+ }
64
+ return true;
65
+ }
66
+ throw new CliError(`Unknown diagnostics action: ${action ?? "(missing)"}`, "INVALID_ARGUMENT");
67
+ }
68
+ finally {
69
+ await client.close();
70
+ }
71
+ }
@@ -25,7 +25,9 @@ const valueFlags = new Set([
25
25
  "device-id",
26
26
  "device-name",
27
27
  "display-name",
28
+ "device",
28
29
  "domain",
30
+ "duration",
29
31
  "from",
30
32
  "host",
31
33
  "host-machine",
@@ -69,6 +71,7 @@ const valueFlags = new Set([
69
71
  "runtime-limit",
70
72
  "runtime-session",
71
73
  "shell",
74
+ "surface",
72
75
  "actions",
73
76
  "expires-in",
74
77
  "format",
@@ -122,6 +125,7 @@ const booleanFlags = new Set([
122
125
  "include-archived",
123
126
  "isolated",
124
127
  "json",
128
+ "jsonl",
125
129
  "keep-source",
126
130
  "local",
127
131
  "longterm",
@@ -12,6 +12,15 @@ Show the current Account, Daemon, and latest Session state.
12
12
  doctor: `Usage: happy-elves doctor [--relay <url>] [--json]
13
13
 
14
14
  Check local Device access, Account consistency, service reachability, and Daemon state.
15
+ `,
16
+ diagnostics: `Usage:
17
+ happy-elves diagnostics surfaces --json
18
+ happy-elves diagnostics capture --device <deviceId> --surface <surfaceId> [--timeout 30s] --json
19
+ happy-elves diagnostics watch --device <deviceId> --surface <surfaceId> [--duration 10m] --jsonl
20
+
21
+ Observe privacy-filtered layout, focus, viewport, pointer, and scroll telemetry
22
+ from a Device that explicitly enabled Settings > Diagnostics > Share live diagnostics.
23
+ Probe traffic is end-to-end encrypted and expires after ten minutes.
15
24
  `,
16
25
  account: `Usage:
17
26
  happy-elves account create --relay <url> [--device-id <id>] [--device-name <name>] [--reveal-secrets] --json
@@ -300,6 +309,11 @@ Advanced and compatibility commands:
300
309
  relay status [--relay <url>] [--json]
301
310
  relay doctor [--relay <url>] [--json]
302
311
 
312
+ # Privacy-filtered diagnostics shared by another trusted Device.
313
+ diagnostics surfaces --json
314
+ diagnostics capture --device <deviceId> --surface <surfaceId> --json
315
+ diagnostics watch --device <deviceId> --surface <surfaceId> --jsonl
316
+
303
317
  Usage:
304
318
  happy-elves <domain> <action> [args] [flags]
305
319
  happy-elves <domain> --help
@@ -316,7 +330,7 @@ More:
316
330
  happy-elves --version
317
331
  happy-elves version --json
318
332
  happy-elves <domain> --help
319
- start, status, doctor, collect, config, account, daemon, relay, token, gateway, loop.
333
+ start, status, doctor, collect, config, account, daemon, relay, diagnostics, token, gateway, loop.
320
334
  orchestrator, skill.
321
335
  `;
322
336
  }
@@ -18137,8 +18137,241 @@ function isSessionNameValid(value) {
18137
18137
  }
18138
18138
  }
18139
18139
 
18140
+ // ../../packages/shared/dist/ui-probe.js
18141
+ var encoder2 = new TextEncoder();
18142
+ var opaqueIdSchema = external_exports.string().min(1).max(200);
18143
+ var identifierSchema = external_exports.string().min(1).max(160).regex(/^[A-Za-z0-9._~-]+$/u);
18144
+ var anchorSchema = external_exports.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9._-]*$/u);
18145
+ var markerIdSchema = external_exports.string().regex(/^probe_marker_[A-Za-z0-9_-]{16,115}$/u);
18146
+ var finiteNumberSchema = external_exports.number().finite();
18147
+ var nonNegativeIntegerSchema = external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
18148
+ var positiveSequenceSchema = external_exports.number().int().min(1).max(Number.MAX_SAFE_INTEGER);
18149
+ var encodedCiphertextSchema = external_exports.string().min(1).max(128 * 1024).regex(/^[A-Za-z0-9+/_=-]+$/u);
18150
+ var uiProbeMaxEncryptedEnvelopeBytes = 126 * 1024;
18151
+ var uiProbeEncryptedEnvelopeSchema = external_exports.strictObject({
18152
+ v: external_exports.literal(1),
18153
+ alg: external_exports.literal("A256GCM"),
18154
+ scope: external_exports.string().min(1).max(512),
18155
+ key: external_exports.strictObject({
18156
+ nonce: encodedCiphertextSchema.max(128),
18157
+ ciphertext: encodedCiphertextSchema.max(1024)
18158
+ }),
18159
+ data: external_exports.strictObject({
18160
+ nonce: encodedCiphertextSchema.max(128),
18161
+ ciphertext: encodedCiphertextSchema
18162
+ })
18163
+ }).superRefine((envelope, context) => {
18164
+ if (encoder2.encode(JSON.stringify(envelope)).byteLength > uiProbeMaxEncryptedEnvelopeBytes) {
18165
+ context.addIssue({ code: "custom", message: "UI Probe encrypted payload exceeds the 128 KiB frame budget" });
18166
+ }
18167
+ });
18168
+ var uiProbeCapabilitiesSchema = external_exports.strictObject({
18169
+ versions: external_exports.tuple([external_exports.literal(1)]),
18170
+ telemetry: external_exports.literal(true),
18171
+ driver: external_exports.boolean()
18172
+ });
18173
+ var uiProbeSurfaceRegistrationSchema = external_exports.strictObject({
18174
+ version: external_exports.literal(1),
18175
+ surfaceId: identifierSchema,
18176
+ instanceId: identifierSchema,
18177
+ kind: external_exports.enum(["browser", "pwa", "desktop-webview"]),
18178
+ capabilities: uiProbeCapabilitiesSchema,
18179
+ expiresAt: nonNegativeIntegerSchema
18180
+ });
18181
+ var uiProbeSurfaceSchema = uiProbeSurfaceRegistrationSchema.extend({
18182
+ deviceId: opaqueIdSchema,
18183
+ deviceName: external_exports.string().min(1).max(160),
18184
+ connectedAt: nonNegativeIntegerSchema
18185
+ });
18186
+ var uiProbeSessionSchema = external_exports.strictObject({
18187
+ version: external_exports.literal(1),
18188
+ probeId: identifierSchema,
18189
+ observerDeviceId: opaqueIdSchema,
18190
+ observerDeviceName: external_exports.string().min(1).max(160),
18191
+ targetDeviceId: opaqueIdSchema,
18192
+ surfaceId: identifierSchema,
18193
+ instanceId: identifierSchema,
18194
+ openedAt: nonNegativeIntegerSchema,
18195
+ expiresAt: nonNegativeIntegerSchema
18196
+ });
18197
+ var uiProbeCloseReasonSchema = external_exports.enum([
18198
+ "requested",
18199
+ "expired",
18200
+ "observer-disconnected",
18201
+ "surface-disconnected",
18202
+ "surface-replaced",
18203
+ "auth-revoked",
18204
+ "backpressure",
18205
+ "rate-limited",
18206
+ "relay-shutdown"
18207
+ ]);
18208
+ var uiProbeOpenSemanticsSchema = external_exports.strictObject({
18209
+ version: external_exports.literal(1),
18210
+ requestId: opaqueIdSchema,
18211
+ targetDeviceId: opaqueIdSchema,
18212
+ surfaceId: identifierSchema,
18213
+ instanceId: identifierSchema
18214
+ });
18215
+ var uiProbeElementRefSchema = external_exports.strictObject({
18216
+ anchor: anchorSchema.optional(),
18217
+ tag: external_exports.string().min(1).max(48).regex(/^[a-z][a-z0-9-]*$/u),
18218
+ role: external_exports.string().min(1).max(48).regex(/^[a-z][a-z0-9-]*$/u).optional(),
18219
+ inputType: external_exports.string().min(1).max(32).regex(/^[a-z][a-z0-9-]*$/u).optional(),
18220
+ disabled: external_exports.boolean().optional(),
18221
+ readOnly: external_exports.boolean().optional()
18222
+ });
18223
+ var uiProbeRectSchema = external_exports.strictObject({
18224
+ x: finiteNumberSchema,
18225
+ y: finiteNumberSchema,
18226
+ width: finiteNumberSchema.min(0),
18227
+ height: finiteNumberSchema.min(0)
18228
+ });
18229
+ var uiProbeSampleSchema = external_exports.discriminatedUnion("kind", [
18230
+ external_exports.strictObject({
18231
+ kind: external_exports.literal("viewport"),
18232
+ at: nonNegativeIntegerSchema,
18233
+ layout: external_exports.strictObject({ width: finiteNumberSchema.min(0), height: finiteNumberSchema.min(0) }),
18234
+ visual: external_exports.strictObject({
18235
+ width: finiteNumberSchema.min(0),
18236
+ height: finiteNumberSchema.min(0),
18237
+ offsetLeft: finiteNumberSchema,
18238
+ offsetTop: finiteNumberSchema,
18239
+ scale: finiteNumberSchema.min(0)
18240
+ }).nullable()
18241
+ }),
18242
+ external_exports.strictObject({
18243
+ kind: external_exports.literal("focus"),
18244
+ at: nonNegativeIntegerSchema,
18245
+ event: external_exports.enum(["focus", "blur", "focusin", "focusout"]),
18246
+ active: uiProbeElementRefSchema.nullable(),
18247
+ isTrusted: external_exports.boolean()
18248
+ }),
18249
+ external_exports.strictObject({
18250
+ kind: external_exports.literal("pointer"),
18251
+ at: nonNegativeIntegerSchema,
18252
+ event: external_exports.enum(["pointerdown", "pointerup", "pointercancel", "touchstart", "touchend", "touchcancel"]),
18253
+ x: finiteNumberSchema,
18254
+ y: finiteNumberSchema,
18255
+ isTrusted: external_exports.boolean(),
18256
+ target: uiProbeElementRefSchema.nullable()
18257
+ }),
18258
+ external_exports.strictObject({
18259
+ kind: external_exports.literal("scroll"),
18260
+ at: nonNegativeIntegerSchema,
18261
+ target: uiProbeElementRefSchema.nullable(),
18262
+ scrollLeft: finiteNumberSchema,
18263
+ scrollTop: finiteNumberSchema,
18264
+ clientWidth: finiteNumberSchema.min(0),
18265
+ clientHeight: finiteNumberSchema.min(0),
18266
+ scrollWidth: finiteNumberSchema.min(0),
18267
+ scrollHeight: finiteNumberSchema.min(0)
18268
+ }),
18269
+ external_exports.strictObject({
18270
+ kind: external_exports.literal("anchors"),
18271
+ at: nonNegativeIntegerSchema,
18272
+ anchors: external_exports.array(external_exports.strictObject({
18273
+ anchor: anchorSchema,
18274
+ tag: external_exports.string().min(1).max(48).regex(/^[a-z][a-z0-9-]*$/u),
18275
+ role: external_exports.string().min(1).max(48).regex(/^[a-z][a-z0-9-]*$/u).optional(),
18276
+ inputType: external_exports.string().min(1).max(32).regex(/^[a-z][a-z0-9-]*$/u).optional(),
18277
+ disabled: external_exports.boolean().optional(),
18278
+ readOnly: external_exports.boolean().optional(),
18279
+ rect: uiProbeRectSchema,
18280
+ style: external_exports.strictObject({
18281
+ position: external_exports.enum(["static", "relative", "absolute", "fixed", "sticky"]),
18282
+ overflowX: external_exports.enum(["visible", "hidden", "clip", "scroll", "auto"]),
18283
+ overflowY: external_exports.enum(["visible", "hidden", "clip", "scroll", "auto"]),
18284
+ touchAction: external_exports.string().min(1).max(64),
18285
+ pointerEvents: external_exports.enum(["auto", "none"]),
18286
+ zIndex: external_exports.string().min(1).max(32),
18287
+ fontSize: external_exports.string().min(1).max(32),
18288
+ transform: external_exports.string().min(1).max(256)
18289
+ })
18290
+ })).max(64)
18291
+ }),
18292
+ external_exports.strictObject({
18293
+ kind: external_exports.literal("layout-shift"),
18294
+ at: nonNegativeIntegerSchema,
18295
+ value: finiteNumberSchema.min(0),
18296
+ hadRecentInput: external_exports.boolean()
18297
+ }),
18298
+ external_exports.strictObject({
18299
+ kind: external_exports.literal("lifecycle"),
18300
+ at: nonNegativeIntegerSchema,
18301
+ event: external_exports.enum(["load", "pageshow", "pagehide", "visible", "hidden", "online", "offline"]),
18302
+ persisted: external_exports.boolean().optional()
18303
+ }),
18304
+ external_exports.strictObject({
18305
+ kind: external_exports.literal("error"),
18306
+ at: nonNegativeIntegerSchema,
18307
+ category: external_exports.enum(["console", "unhandled-error", "unhandled-rejection", "layout-observer"]),
18308
+ messageHash: external_exports.string().regex(/^[a-f0-9]{64}$/u)
18309
+ }),
18310
+ external_exports.strictObject({
18311
+ kind: external_exports.literal("marker"),
18312
+ at: nonNegativeIntegerSchema,
18313
+ markerId: markerIdSchema
18314
+ })
18315
+ ]);
18316
+ var uiProbeBindingSchema = {
18317
+ version: external_exports.literal(1),
18318
+ probeId: identifierSchema,
18319
+ observerDeviceId: opaqueIdSchema,
18320
+ targetDeviceId: opaqueIdSchema,
18321
+ surfaceId: identifierSchema,
18322
+ instanceId: identifierSchema,
18323
+ sequence: positiveSequenceSchema
18324
+ };
18325
+ var uiProbeCommandActionSchema = external_exports.discriminatedUnion("action", [
18326
+ external_exports.strictObject({ action: external_exports.literal("snapshot"), anchors: external_exports.array(anchorSchema).max(64).optional() }),
18327
+ external_exports.strictObject({ action: external_exports.literal("setSampling"), intervalMs: external_exports.number().int().min(250).max(2e3) }),
18328
+ external_exports.strictObject({ action: external_exports.literal("mark"), markerId: markerIdSchema }),
18329
+ external_exports.strictObject({ action: external_exports.literal("close") })
18330
+ ]);
18331
+ var uiProbeCommandSchema = external_exports.strictObject({
18332
+ ...uiProbeBindingSchema,
18333
+ command: uiProbeCommandActionSchema
18334
+ });
18335
+ var uiProbeTelemetrySchema = external_exports.strictObject({
18336
+ ...uiProbeBindingSchema,
18337
+ sampledAt: nonNegativeIntegerSchema,
18338
+ fullSnapshot: external_exports.boolean(),
18339
+ dropped: nonNegativeIntegerSchema,
18340
+ samples: external_exports.array(uiProbeSampleSchema).max(64)
18341
+ });
18342
+
18140
18343
  // ../../packages/shared/dist/protocol.js
18141
18344
  var controllerMessageSchema = external_exports.discriminatedUnion("type", [
18345
+ external_exports.object({
18346
+ type: external_exports.literal("controller:uiProbeList"),
18347
+ requestId: external_exports.string().min(1).max(200)
18348
+ }),
18349
+ external_exports.object({
18350
+ type: external_exports.literal("controller:uiProbeOpen"),
18351
+ requestId: external_exports.string().min(1).max(200),
18352
+ targetDeviceId: external_exports.string().min(1).max(200),
18353
+ surfaceId: external_exports.string().min(1).max(160).regex(/^[A-Za-z0-9._~-]+$/u),
18354
+ instanceId: external_exports.string().min(1).max(160).regex(/^[A-Za-z0-9._~-]+$/u),
18355
+ semanticDigest: external_exports.string().max(200).regex(/^ui-probe-v1:[A-Za-z0-9_-]+$/u)
18356
+ }),
18357
+ external_exports.object({
18358
+ type: external_exports.literal("controller:uiProbeCommand"),
18359
+ requestId: external_exports.string().min(1).max(200),
18360
+ probeId: external_exports.string().min(1).max(160).regex(/^[A-Za-z0-9._~-]+$/u),
18361
+ sequence: external_exports.number().int().positive(),
18362
+ encryptedPayload: uiProbeEncryptedEnvelopeSchema
18363
+ }),
18364
+ external_exports.object({
18365
+ type: external_exports.literal("controller:uiProbeTelemetry"),
18366
+ probeId: external_exports.string().min(1).max(160).regex(/^[A-Za-z0-9._~-]+$/u),
18367
+ sequence: external_exports.number().int().positive(),
18368
+ encryptedPayload: uiProbeEncryptedEnvelopeSchema
18369
+ }),
18370
+ external_exports.object({
18371
+ type: external_exports.literal("controller:uiProbeClose"),
18372
+ requestId: external_exports.string().min(1).max(200),
18373
+ probeId: external_exports.string().min(1).max(160).regex(/^[A-Za-z0-9._~-]+$/u)
18374
+ }),
18142
18375
  external_exports.object({
18143
18376
  type: external_exports.literal("controller:createSession"),
18144
18377
  requestId: external_exports.string().min(1),
@@ -18598,11 +18831,14 @@ var clientMessageSchema = external_exports.union([
18598
18831
  clientType: external_exports.enum(["controller", "machine"]),
18599
18832
  token: external_exports.string().min(1),
18600
18833
  machineId: external_exports.string().min(1).optional(),
18834
+ purpose: external_exports.enum(["app", "probe"]).optional(),
18835
+ uiProbeSurface: uiProbeSurfaceRegistrationSchema.optional(),
18601
18836
  capabilities: external_exports.object({
18602
18837
  daemonLifecycleEventsV1: external_exports.boolean().optional(),
18603
18838
  daemonAutostartV1: external_exports.boolean().optional(),
18604
18839
  resourcePreferenceInvalidationV1: external_exports.boolean().optional(),
18605
- daemonProjectsPreferenceV1: external_exports.boolean().optional()
18840
+ daemonProjectsPreferenceV1: external_exports.boolean().optional(),
18841
+ uiProbe: uiProbeCapabilitiesSchema.optional()
18606
18842
  }).optional()
18607
18843
  }),
18608
18844
  controllerMessageSchema,
@@ -18850,9 +19086,39 @@ var serverMessageSchema = external_exports.union([
18850
19086
  sessionStarsV1: external_exports.boolean().optional(),
18851
19087
  daemonAuthorizedDeviceBootstrapV1: external_exports.boolean().optional(),
18852
19088
  browserBootstrapInviteV1: external_exports.boolean().optional(),
18853
- daemonAutostartV1: external_exports.boolean().optional()
19089
+ daemonAutostartV1: external_exports.boolean().optional(),
19090
+ uiProbe: uiProbeCapabilitiesSchema.optional()
18854
19091
  }).optional()
18855
19092
  }),
19093
+ external_exports.object({
19094
+ type: external_exports.literal("server:uiProbeSurfaces"),
19095
+ requestId: external_exports.string().min(1),
19096
+ surfaces: external_exports.array(uiProbeSurfaceSchema)
19097
+ }),
19098
+ external_exports.object({
19099
+ type: external_exports.literal("server:uiProbeOpened"),
19100
+ requestId: external_exports.string().min(1),
19101
+ session: uiProbeSessionSchema
19102
+ }),
19103
+ external_exports.object({
19104
+ type: external_exports.literal("server:uiProbeCommand"),
19105
+ requestId: external_exports.string().min(1),
19106
+ session: uiProbeSessionSchema,
19107
+ sequence: external_exports.number().int().positive(),
19108
+ encryptedPayload: uiProbeEncryptedEnvelopeSchema
19109
+ }),
19110
+ external_exports.object({
19111
+ type: external_exports.literal("server:uiProbeTelemetry"),
19112
+ session: uiProbeSessionSchema,
19113
+ sequence: external_exports.number().int().positive(),
19114
+ encryptedPayload: uiProbeEncryptedEnvelopeSchema
19115
+ }),
19116
+ external_exports.object({
19117
+ type: external_exports.literal("server:uiProbeClosed"),
19118
+ requestId: external_exports.string().min(1).optional(),
19119
+ probeId: external_exports.string().min(1),
19120
+ reason: uiProbeCloseReasonSchema
19121
+ }),
18856
19122
  external_exports.object({
18857
19123
  type: external_exports.literal("server:snapshot"),
18858
19124
  machines: external_exports.array(machineSnapshotSchema),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.75",
3
+ "version": "0.1.0-beta.76",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -57,6 +57,7 @@ export type PendingCommandContext = {
57
57
  export declare function machineKey(accountId: string, machineId: string): string;
58
58
  export declare function createConnectionState(): {
59
59
  controllersByAccount: Map<string, Set<ControllerConnection>>;
60
+ probeControllersByAccount: Map<string, Set<ControllerConnection>>;
60
61
  machinesByKey: Map<string, MachineConnection>;
61
62
  pendingCommandRequests: Map<string, PendingCommandContext>;
62
63
  };
@@ -5,6 +5,7 @@ export function machineKey(accountId, machineId) {
5
5
  export function createConnectionState() {
6
6
  return {
7
7
  controllersByAccount: new Map(),
8
+ probeControllersByAccount: new Map(),
8
9
  machinesByKey: new Map(),
9
10
  pendingCommandRequests: new Map(),
10
11
  };
@@ -100,6 +100,7 @@ export declare function createRelayContext(options: CreateRelayContextOptions):
100
100
  sessionId: string;
101
101
  turnId: string;
102
102
  }) => PendingCommandContext | undefined;
103
+ probeControllersByAccount: Map<string, Set<ControllerConnection>>;
103
104
  isFullControllerToken: typeof isFullControllerToken;
104
105
  machineCapabilities: (accountId: string, machineId: string) => MachineSnapshot["capabilities"] | undefined;
105
106
  machineSnapshot: (row: MachineRow) => MachineSnapshot;
@@ -9,7 +9,7 @@ import { clearAcceptedRun as clearConnectionAcceptedRun, clearAcceptedRunsForSes
9
9
  import { bearerToken, encodePairingCode, tokenDigest } from "./security.js";
10
10
  const REWRITE_HEAD_BASIS_PREFIX = "rewrite:";
11
11
  export function createRelayContext(options) {
12
- const { controllersByAccount, machinesByKey, pendingCommandRequests } = createConnectionState();
12
+ const { controllersByAccount, probeControllersByAccount, machinesByKey, pendingCommandRequests } = createConnectionState();
13
13
  const now = options.now ?? Date.now;
14
14
  function connectedMachine(accountId, machineId) {
15
15
  return machinesByKey.get(machineKey(accountId, machineId));
@@ -86,17 +86,18 @@ export function createRelayContext(options) {
86
86
  assertScopedAction(tokenScope(record), action);
87
87
  }
88
88
  function closeRevokedControllerConnections(accountId, deviceId) {
89
- const controllers = controllersByAccount.get(accountId);
90
- if (!controllers)
91
- return;
92
- for (const connection of controllers) {
93
- if (connection.deviceId === deviceId) {
94
- send(connection.socket, {
95
- type: "server:error",
96
- code: "AUTH_TOKEN_REVOKED",
97
- message: "Device has been revoked",
98
- });
99
- connection.socket.close(1008, "Device has been revoked");
89
+ for (const controllers of [controllersByAccount.get(accountId), probeControllersByAccount.get(accountId)]) {
90
+ if (!controllers)
91
+ continue;
92
+ for (const connection of controllers) {
93
+ if (connection.deviceId === deviceId) {
94
+ send(connection.socket, {
95
+ type: "server:error",
96
+ code: "AUTH_TOKEN_REVOKED",
97
+ message: "Device has been revoked",
98
+ });
99
+ connection.socket.close(1008, "Device has been revoked");
100
+ }
100
101
  }
101
102
  }
102
103
  }
@@ -1021,6 +1022,7 @@ export function createRelayContext(options) {
1021
1022
  isCommandPending,
1022
1023
  isSessionCommandPending,
1023
1024
  pendingRunCommand,
1025
+ probeControllersByAccount,
1024
1026
  isFullControllerToken,
1025
1027
  machineCapabilities,
1026
1028
  machineSnapshot,
@@ -1,4 +1,4 @@
1
- import type { MachineSnapshot, SessionSnapshot } from "../../../packages/shared/dist/index.js";
1
+ import type { MachineSnapshot, SessionSnapshot, UiProbeCapabilities, UiProbeSurfaceRegistration } from "../../../packages/shared/dist/index.js";
2
2
  export type TokenKind = "controller" | "machine";
3
3
  export type ScopedTokenScope = {
4
4
  name?: string;
@@ -120,6 +120,8 @@ export type PairingRow = {
120
120
  };
121
121
  export type WebSocketLike = {
122
122
  readyState: number;
123
+ /** Bytes queued by the transport for this exact recipient socket. */
124
+ bufferedAmount?: number;
123
125
  send(data: string): void;
124
126
  close(code?: number, reason?: string): void;
125
127
  on(event: "message", cb: (data: Buffer) => void): void;
@@ -131,12 +133,16 @@ export type ControllerConnection = {
131
133
  accountId: string;
132
134
  token: string;
133
135
  deviceId: string | null;
136
+ deviceName: string;
137
+ purpose: "app" | "probe";
138
+ uiProbeSurface?: UiProbeSurfaceRegistration;
134
139
  scope: ScopedTokenScope | null;
135
140
  clientCapabilities?: {
136
141
  daemonLifecycleEventsV1?: boolean;
137
142
  daemonAutostartV1?: boolean;
138
143
  resourcePreferenceInvalidationV1?: boolean;
139
144
  daemonProjectsPreferenceV1?: boolean;
145
+ uiProbe?: UiProbeCapabilities;
140
146
  };
141
147
  activeRunTurns?: Set<string>;
142
148
  expiresAt: number | null;
@@ -0,0 +1,35 @@
1
+ import { type ClientMessage, type ServerMessage, type UiProbeCloseReason } from "../../../packages/shared/dist/index.js";
2
+ import type { ControllerConnection } from "./types.js";
3
+ export declare const UI_PROBE_TTL_MS: number;
4
+ export declare const UI_PROBE_REGISTRATION_CLOCK_SKEW_MS = 60000;
5
+ export declare const UI_PROBE_MAX_ACTIVE_PER_ACCOUNT = 5;
6
+ export declare const UI_PROBE_MAX_CONTROL_MESSAGES_PER_SECOND = 20;
7
+ export declare const UI_PROBE_MAX_RECIPIENT_BUFFER_BYTES: number;
8
+ export type UiProbeClientMessage = Extract<ClientMessage, {
9
+ type: "controller:uiProbeList" | "controller:uiProbeOpen" | "controller:uiProbeCommand" | "controller:uiProbeTelemetry" | "controller:uiProbeClose";
10
+ }>;
11
+ type AuthError = {
12
+ code: string;
13
+ message: string;
14
+ };
15
+ type TimerHandle = ReturnType<typeof setTimeout> | number;
16
+ type UiProbeRouterOptions = {
17
+ now(): number;
18
+ send(connection: ControllerConnection, message: ServerMessage): boolean;
19
+ authError(connection: ControllerConnection): AuthError | null;
20
+ setTimer?(callback: () => void, delayMs: number): TimerHandle;
21
+ clearTimer?(timer: TimerHandle): void;
22
+ };
23
+ export declare function createUiProbeRouter(options: UiProbeRouterOptions): {
24
+ closeAll: (reason?: UiProbeCloseReason) => void;
25
+ disconnect: (connection: ControllerConnection) => void;
26
+ handle: (connection: ControllerConnection, message: UiProbeClientMessage) => void;
27
+ register: (connection: ControllerConnection) => {
28
+ replaced?: ControllerConnection;
29
+ };
30
+ };
31
+ export declare class UiProbeRegistrationError extends Error {
32
+ readonly code: string;
33
+ constructor(code: string, message: string);
34
+ }
35
+ export {};