@ai-matrx/desktop-protocol 0.1.1 → 0.3.0

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.
package/dist/client.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { BackoffOptions } from '@ai-matrx/realtime';
2
2
  export { RECONNECT_ALARM_ATTEMPTS } from '@ai-matrx/realtime';
3
3
  import { z } from 'zod';
4
- import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-DJbrsZ4u.cjs';
5
- export { i as isDesktopProtocolError } from './registry-DJbrsZ4u.cjs';
4
+ import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-DQ5_mP_5.cjs';
5
+ export { i as isDesktopProtocolError } from './registry-DQ5_mP_5.cjs';
6
6
  import { FrameHeader } from './frame.cjs';
7
7
 
8
8
  /** The subset of the WHATWG WebSocket the client uses — browser, React Native, `ws` and Workers all fit. */
@@ -167,6 +167,18 @@ interface DesktopClient {
167
167
  on<E extends EventName>(name: E, handler: EventHandler<E>): () => void;
168
168
  /** Hand the relay a fresh token without dropping the socket. */
169
169
  reauth(token: string): void;
170
+ /**
171
+ * The app came back to the foreground or the network changed (`visibilitychange` → visible,
172
+ * `pageshow`, `online`). Reconnecting → dial now instead of waiting out the backoff. Open → probe:
173
+ * a ping must be answered within `probeMs` (default 4000) or the socket is treated as dead and
174
+ * redialed at once. A phone resumed from sleep often holds a socket the OS already killed, and two
175
+ * silent heartbeats would cost 50 s. Resource streams reattach with `since_seq` as usual.
176
+ */
177
+ wake(options?: WakeOptions): void;
178
+ }
179
+ interface WakeOptions {
180
+ /** How long an open socket has to answer the probe ping. Default 4000. */
181
+ probeMs?: number;
170
182
  }
171
183
 
172
184
  /**
@@ -178,4 +190,20 @@ interface DesktopClient {
178
190
 
179
191
  declare function createDesktopClient(options: DesktopClientOptions): DesktopClient;
180
192
 
181
- export { type ClientTimers, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WebSocketFactory, type WebSocketLike, createDesktopClient };
193
+ /**
194
+ * Wire a client's wake() to the browser events that mean "the person is back": the page becomes
195
+ * visible again (iOS Safari suspends a background tab's sockets), a back/forward-cache restore
196
+ * (`pageshow` with persisted), and the network returning (`online`). Framework-free; the React
197
+ * binding is `useDesktopWake` in `./react`.
198
+ */
199
+
200
+ /** The subset of window/document the binding listens on — injectable for tests and non-DOM hosts. */
201
+ interface WakeTargets {
202
+ window: Pick<Window, "addEventListener" | "removeEventListener">;
203
+ document: Pick<Document, "addEventListener" | "removeEventListener" | "visibilityState">;
204
+ }
205
+ declare function bindDesktopWake(client: Pick<DesktopClient, "wake">, options?: WakeOptions & {
206
+ targets?: WakeTargets;
207
+ }): () => void;
208
+
209
+ export { type ClientTimers, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WakeOptions, type WakeTargets, type WebSocketFactory, type WebSocketLike, bindDesktopWake, createDesktopClient };
package/dist/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { BackoffOptions } from '@ai-matrx/realtime';
2
2
  export { RECONNECT_ALARM_ATTEMPTS } from '@ai-matrx/realtime';
3
3
  import { z } from 'zod';
4
- import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-DJbrsZ4u.js';
5
- export { i as isDesktopProtocolError } from './registry-DJbrsZ4u.js';
4
+ import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-DQ5_mP_5.js';
5
+ export { i as isDesktopProtocolError } from './registry-DQ5_mP_5.js';
6
6
  import { FrameHeader } from './frame.js';
7
7
 
8
8
  /** The subset of the WHATWG WebSocket the client uses — browser, React Native, `ws` and Workers all fit. */
@@ -167,6 +167,18 @@ interface DesktopClient {
167
167
  on<E extends EventName>(name: E, handler: EventHandler<E>): () => void;
168
168
  /** Hand the relay a fresh token without dropping the socket. */
169
169
  reauth(token: string): void;
170
+ /**
171
+ * The app came back to the foreground or the network changed (`visibilitychange` → visible,
172
+ * `pageshow`, `online`). Reconnecting → dial now instead of waiting out the backoff. Open → probe:
173
+ * a ping must be answered within `probeMs` (default 4000) or the socket is treated as dead and
174
+ * redialed at once. A phone resumed from sleep often holds a socket the OS already killed, and two
175
+ * silent heartbeats would cost 50 s. Resource streams reattach with `since_seq` as usual.
176
+ */
177
+ wake(options?: WakeOptions): void;
178
+ }
179
+ interface WakeOptions {
180
+ /** How long an open socket has to answer the probe ping. Default 4000. */
181
+ probeMs?: number;
170
182
  }
171
183
 
172
184
  /**
@@ -178,4 +190,20 @@ interface DesktopClient {
178
190
 
179
191
  declare function createDesktopClient(options: DesktopClientOptions): DesktopClient;
180
192
 
181
- export { type ClientTimers, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WebSocketFactory, type WebSocketLike, createDesktopClient };
193
+ /**
194
+ * Wire a client's wake() to the browser events that mean "the person is back": the page becomes
195
+ * visible again (iOS Safari suspends a background tab's sockets), a back/forward-cache restore
196
+ * (`pageshow` with persisted), and the network returning (`online`). Framework-free; the React
197
+ * binding is `useDesktopWake` in `./react`.
198
+ */
199
+
200
+ /** The subset of window/document the binding listens on — injectable for tests and non-DOM hosts. */
201
+ interface WakeTargets {
202
+ window: Pick<Window, "addEventListener" | "removeEventListener">;
203
+ document: Pick<Document, "addEventListener" | "removeEventListener" | "visibilityState">;
204
+ }
205
+ declare function bindDesktopWake(client: Pick<DesktopClient, "wake">, options?: WakeOptions & {
206
+ targets?: WakeTargets;
207
+ }): () => void;
208
+
209
+ export { type ClientTimers, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WakeOptions, type WakeTargets, type WebSocketFactory, type WebSocketLike, bindDesktopWake, createDesktopClient };
package/dist/client.js CHANGED
@@ -95,6 +95,7 @@ import { z as z2 } from "zod";
95
95
  import { z } from "zod";
96
96
  var PROTOCOL_MAJOR = 1;
97
97
  var PROTOCOL_MINOR = 0;
98
+ var PROTOCOL_VERSION = `${PROTOCOL_MAJOR}.${PROTOCOL_MINOR}`;
98
99
  var ProtocolVersion = z.string().regex(/^[1-9][0-9]{0,2}\.[0-9]{1,3}$/);
99
100
  var RequestId = z.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
100
101
  var ResourceId = z.string().regex(/^(pty|watch)_[A-Za-z0-9]{16,40}$/);
@@ -366,12 +367,9 @@ var FsEntry = z6.strictObject({
366
367
  kind: FsKind,
367
368
  size: nonNegInt,
368
369
  mtime: z6.number().min(0),
369
- // seconds, float — daemon parity
370
- mtime_ms: epochMs,
370
+ // seconds, float
371
371
  mode: z6.int().min(0).max(4095),
372
- target: AbsPath.nullable(),
373
- // symlink target, never followed
374
- hidden: z6.boolean()
372
+ target: AbsPath.nullable()
375
373
  });
376
374
  var FsListParams = z6.strictObject({
377
375
  path: AbsPath,
@@ -379,10 +377,12 @@ var FsListParams = z6.strictObject({
379
377
  depth: z6.int().min(1).max(32).default(1),
380
378
  pattern: z6.string().max(256).optional(),
381
379
  // glob on name
382
- limit: z6.int().min(1).max(5e3).default(500),
380
+ limit: z6.int().min(1).max(5e3).default(1e3),
383
381
  page_token: z6.string().max(256).optional(),
384
- show_hidden: z6.boolean().default(false),
385
- sort: z6.enum(["name", "mtime", "size", "kind"]).default("kind")
382
+ // Desktop additions; their defaults ARE the daemon's behaviour: dotfiles listed, siblings in
383
+ // binary (byte) order of name, depth-first.
384
+ show_hidden: z6.boolean().default(true),
385
+ sort: z6.enum(["name", "mtime", "size", "kind"]).default("name")
386
386
  });
387
387
  var FsListResult = z6.strictObject({
388
388
  entries: z6.array(FsEntry),
@@ -466,7 +466,7 @@ var FsSearchPathsParams = z6.strictObject({
466
466
  hidden: z6.boolean().default(false),
467
467
  max_results: z6.int().min(1).max(5e3).default(100)
468
468
  });
469
- var FsSearchPathsResult = z6.strictObject({ paths: z6.array(AbsPath), truncated: z6.boolean() });
469
+ var FsSearchPathsResult = z6.strictObject({ paths: z6.array(z6.string().min(3).max(4096).regex(/^\.\/[^\\\u0000]+$/)) });
470
470
  var FsSearchContentParams = z6.strictObject({
471
471
  query: z6.string().min(1).max(1024),
472
472
  cwd: AbsPath,
@@ -479,13 +479,14 @@ var FsSearchContentParams = z6.strictObject({
479
479
  var FsSearchContentResult = z6.strictObject({
480
480
  results: z6.array(
481
481
  z6.strictObject({
482
- path: AbsPath,
482
+ path: z6.string().min(1).max(4096).regex(/^[^/\\\u0000][^\\\u0000]*$/),
483
483
  lines: z6.string(),
484
484
  line_number: z6.int().min(1),
485
- submatches: z6.array(z6.strictObject({ match: z6.string(), start: nonNegInt, end: nonNegInt }))
485
+ submatches: z6.array(
486
+ z6.strictObject({ match: z6.strictObject({ text: z6.string() }), start: nonNegInt, end: nonNegInt })
487
+ )
486
488
  })
487
- ),
488
- truncated: z6.boolean()
489
+ )
489
490
  });
490
491
  var FsChange = z6.strictObject({
491
492
  type: z6.enum(["created", "modified", "deleted", "moved"]),
@@ -732,7 +733,9 @@ function createDesktopClient(options) {
732
733
  let helloId = null;
733
734
  let reconnectTimer = null;
734
735
  let heartbeatTimer = null;
736
+ let probeTimer = null;
735
737
  let lastHeardAt = 0;
738
+ let heardCount = 0;
736
739
  let closedByUser = false;
737
740
  const newId = (prefix) => `${prefix}${(++nextId).toString(36)}`;
738
741
  function setState(patch) {
@@ -787,6 +790,7 @@ function createDesktopClient(options) {
787
790
  ws.onmessage = (event) => {
788
791
  if (generation !== socketGeneration) return;
789
792
  lastHeardAt = timers.now();
793
+ heardCount++;
790
794
  onMessage(event.data);
791
795
  };
792
796
  ws.onerror = () => {
@@ -828,6 +832,10 @@ function createDesktopClient(options) {
828
832
  timers.clearTimeout(heartbeatTimer);
829
833
  heartbeatTimer = null;
830
834
  }
835
+ if (probeTimer !== null) {
836
+ timers.clearTimeout(probeTimer);
837
+ probeTimer = null;
838
+ }
831
839
  const terminal = code === null ? void 0 : TERMINAL_CLOSE.get(code);
832
840
  const lost = error ?? (terminal ? new DesktopProtocolError(terminal[0], terminal[1]) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
833
841
  for (const [id, p] of pending) {
@@ -1204,15 +1212,16 @@ function createDesktopClient(options) {
1204
1212
  });
1205
1213
  result.catch(() => void 0);
1206
1214
  done.catch(() => void 0);
1215
+ const attach = op === "session.attach" ? params : null;
1207
1216
  const s = {
1208
1217
  op,
1209
1218
  handlers,
1210
1219
  options: opts,
1211
1220
  id: newId("s"),
1212
1221
  sid: null,
1213
- resourceId: null,
1214
- lastSeq: 0,
1215
- expectSeq: 1,
1222
+ resourceId: attach ? attach.resource_id : null,
1223
+ lastSeq: attach?.since_seq ?? 0,
1224
+ expectSeq: attach ? attach.since_seq === void 0 ? null : attach.since_seq + 1 : 1,
1216
1225
  windowBytes: opts.windowBytes ?? DEFAULT_WINDOW_BYTES,
1217
1226
  consumedUncredited: 0,
1218
1227
  sendWindow: DEFAULT_WINDOW_BYTES,
@@ -1301,6 +1310,34 @@ function createDesktopClient(options) {
1301
1310
  function reauth(token) {
1302
1311
  sendJson({ type: "relay", op: "reauth", token });
1303
1312
  }
1313
+ function wake(opts = {}) {
1314
+ if (closedByUser || state.status === "closed") return;
1315
+ if (state.status === "idle" || state.status === "reconnecting") {
1316
+ connect();
1317
+ return;
1318
+ }
1319
+ if (state.status === "connecting" || !socket || probeTimer !== null) return;
1320
+ const generation = socketGeneration;
1321
+ const heardBefore = heardCount;
1322
+ try {
1323
+ socket.send(PING_TEXT);
1324
+ } catch {
1325
+ }
1326
+ probeTimer = timers.setTimeout(() => {
1327
+ probeTimer = null;
1328
+ if (generation !== socketGeneration || !socket) return;
1329
+ if (heardCount > heardBefore) return;
1330
+ const silentMs = timers.now() - lastHeardAt;
1331
+ diagnose({ kind: "heartbeat_timeout", silentMs });
1332
+ const dead = socket;
1333
+ onSocketGone(generation, null, new DesktopProtocolError("TIMEOUT", "Connection went silent", { retryable: true }));
1334
+ try {
1335
+ dead.close(CloseCode.NORMAL, "wake probe timeout");
1336
+ } catch {
1337
+ }
1338
+ if (state.status === "reconnecting") connect();
1339
+ }, opts.probeMs ?? 4e3);
1340
+ }
1304
1341
  return {
1305
1342
  connect,
1306
1343
  close,
@@ -1315,12 +1352,35 @@ function createDesktopClient(options) {
1315
1352
  request,
1316
1353
  stream,
1317
1354
  on,
1318
- reauth
1355
+ reauth,
1356
+ wake
1357
+ };
1358
+ }
1359
+
1360
+ // src/client/wake.ts
1361
+ function bindDesktopWake(client, options = {}) {
1362
+ const targets = options.targets ?? (typeof window !== "undefined" && typeof document !== "undefined" ? { window, document } : null);
1363
+ if (!targets) return () => void 0;
1364
+ const wakeOptions = options.probeMs === void 0 ? {} : { probeMs: options.probeMs };
1365
+ const onVisibility = () => {
1366
+ if (targets.document.visibilityState === "visible") client.wake(wakeOptions);
1367
+ };
1368
+ const onWake = () => client.wake(wakeOptions);
1369
+ targets.document.addEventListener("visibilitychange", onVisibility);
1370
+ targets.window.addEventListener("pageshow", onWake);
1371
+ targets.window.addEventListener("online", onWake);
1372
+ targets.window.addEventListener("focus", onWake);
1373
+ return () => {
1374
+ targets.document.removeEventListener("visibilitychange", onVisibility);
1375
+ targets.window.removeEventListener("pageshow", onWake);
1376
+ targets.window.removeEventListener("online", onWake);
1377
+ targets.window.removeEventListener("focus", onWake);
1319
1378
  };
1320
1379
  }
1321
1380
  export {
1322
1381
  DesktopProtocolError,
1323
1382
  RECONNECT_ALARM_ATTEMPTS,
1383
+ bindDesktopWake,
1324
1384
  createDesktopClient,
1325
1385
  isDesktopProtocolError
1326
1386
  };