@rynx-ai/daemon 0.1.11-beta.50 → 0.1.11-beta.52

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/plugin-channel-lark",
3
- "version": "0.1.11-beta.50",
3
+ "version": "0.1.11-beta.52",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -17,7 +17,6 @@ const DEVTOOLS_ACTIVE_PORT_FILE = "DevToolsActivePort";
17
17
  const MAX_PROFILE_METADATA_BYTES = 4 * 1_024;
18
18
  const MAX_PROCESS_COMMAND_BYTES = 64 * 1_024;
19
19
  const MAX_ACTIVE_PORT_BYTES = 1_024;
20
- const MAX_CDP_MESSAGE_BYTES = 4 * 1_024 * 1_024;
21
20
  const DEFAULT_STARTUP_TIMEOUT_MS = 15_000;
22
21
  const DEFAULT_CDP_COMMAND_TIMEOUT_MS = 10_000;
23
22
  const DEFAULT_TERMINATION_GRACE_MS = 2_500;
@@ -771,7 +770,18 @@ class HeadlessBrowserHandle {
771
770
  this.dropAttachedSession(event.sessionId);
772
771
  return;
773
772
  }
774
- this.markUnavailable("Headless Browser could not continue an intercepted request");
773
+ if (!this.host.connection.connected) {
774
+ this.markUnavailable("The native CDP observer disconnected");
775
+ return;
776
+ }
777
+ this.diagnostic("request.continue_failed", { ...identity, error: String(error) });
778
+ // A failed request must not strand the Page or invalidate unrelated Pages.
779
+ void this.host.connection.command("Fetch.failRequest", {
780
+ requestId: event.params.requestId,
781
+ errorReason: "Failed",
782
+ }, event.sessionId).catch((failure) => {
783
+ this.diagnostic("request.fail_failed", { ...identity, error: String(failure) });
784
+ });
775
785
  });
776
786
  return;
777
787
  }
@@ -1044,13 +1054,14 @@ class HeadlessBrowserHandle {
1044
1054
  }
1045
1055
  catch (error) {
1046
1056
  if (!isMissingTargetError(error) && !isTransientPageTransitionError(error)) {
1047
- this.markUnavailable("Headless Browser could not fence a duplicate Page session");
1048
- throw this.mapOperationError(error);
1057
+ if (!this.host.connection.connected)
1058
+ this.markUnavailable("The native CDP observer disconnected");
1059
+ this.diagnostic("session.detach_failed", { cdpSessionId: sessionId, error: String(error) });
1060
+ // Keep the live session tracked so its paused requests can still be handled.
1061
+ return;
1049
1062
  }
1050
1063
  }
1051
- finally {
1052
- this.dropAttachedSession(sessionId);
1053
- }
1064
+ this.dropAttachedSession(sessionId);
1054
1065
  }
1055
1066
  async dismissJavaScriptDialog(sessionId, targetId) {
1056
1067
  try {
@@ -1225,7 +1236,13 @@ class HeadlessBrowserSurfaceSource {
1225
1236
  return result;
1226
1237
  }
1227
1238
  acceptScreencastFrame(params) {
1228
- const screencastSessionId = surfaceFrameSessionId(params.sessionId);
1239
+ let screencastSessionId;
1240
+ try {
1241
+ screencastSessionId = surfaceFrameSessionId(params.sessionId);
1242
+ }
1243
+ catch {
1244
+ return;
1245
+ }
1229
1246
  const acknowledge = onceAsync(async () => {
1230
1247
  await this.source.connection.command("Page.screencastFrameAck", { sessionId: screencastSessionId }, this.source.pageSessionId);
1231
1248
  });
@@ -1236,7 +1253,6 @@ class HeadlessBrowserSurfaceSource {
1236
1253
  void acknowledge().catch(() => undefined);
1237
1254
  return;
1238
1255
  }
1239
- this.ensureCurrent();
1240
1256
  const receivedAt = Date.now();
1241
1257
  this.lastScreencastFrameAt = receivedAt;
1242
1258
  // Throttle immediate deliveries, not replacements behind a slow viewer.
@@ -1247,6 +1263,7 @@ class HeadlessBrowserSurfaceSource {
1247
1263
  return;
1248
1264
  }
1249
1265
  try {
1266
+ this.ensureCurrent();
1250
1267
  this.updateViewportFromScreencastMetadata(params.metadata);
1251
1268
  const timestampMilliseconds = surfaceFrameTimestamp(params.metadata, receivedAt);
1252
1269
  this.lastAcceptedFrameAt = receivedAt;
@@ -1255,6 +1272,9 @@ class HeadlessBrowserSurfaceSource {
1255
1272
  // never extend across network RTT back into Chrome capture.
1256
1273
  this.pushFrameIfChanged(decodeSurfaceImage(params.data, this.format), timestampMilliseconds, async () => undefined);
1257
1274
  }
1275
+ catch {
1276
+ // Discard a bad frame without disconnecting the shared Browser observer.
1277
+ }
1258
1278
  finally {
1259
1279
  void acknowledge().catch(() => undefined);
1260
1280
  }
@@ -1837,7 +1857,6 @@ class CdpConnection {
1837
1857
  static async connect(endpoint, commandTimeoutMs) {
1838
1858
  validateCdpEndpoint(endpoint);
1839
1859
  const socket = new WebSocket(endpoint, {
1840
- maxPayload: MAX_CDP_MESSAGE_BYTES,
1841
1860
  perMessageDeflate: false,
1842
1861
  });
1843
1862
  await new Promise((resolveOpen, rejectOpen) => {
@@ -1901,7 +1920,7 @@ class CdpConnection {
1901
1920
  }
1902
1921
  onMessage(data, isBinary) {
1903
1922
  const bytes = rawDataBytes(data);
1904
- if (isBinary || bytes.byteLength > MAX_CDP_MESSAGE_BYTES) {
1923
+ if (isBinary) {
1905
1924
  this.socket.terminate();
1906
1925
  this.disconnect("Native CDP sent an invalid message");
1907
1926
  return;
@@ -9,7 +9,7 @@ export async function createPageAgentBrowserConnection(endpoint, isCurrent) {
9
9
  let used = false, closed = false, authority = "";
10
10
  let client, upstream;
11
11
  const server = createServer((_request, response) => { response.writeHead(404).end(); });
12
- const sockets = new WebSocketServer({ noServer: true, maxPayload: limit });
12
+ const sockets = new WebSocketServer({ noServer: true });
13
13
  const disconnect = () => { client?.terminate(); upstream?.terminate(); };
14
14
  server.on("upgrade", (request, socket, head) => {
15
15
  if (closed || used || !isCurrent() || request.url !== noncePath || request.headers.origin !== undefined || request.headers.host !== authority) {
@@ -19,7 +19,7 @@ export async function createPageAgentBrowserConnection(endpoint, isCurrent) {
19
19
  used = true;
20
20
  sockets.handleUpgrade(request, socket, head, (downstream) => {
21
21
  client = downstream;
22
- const native = new WebSocket(endpoint, { maxPayload: limit, handshakeTimeout: 5_000 });
22
+ const native = new WebSocket(endpoint, { handshakeTimeout: 5_000 });
23
23
  upstream = native;
24
24
  const early = [];
25
25
  let buffered = 0;
@@ -28,7 +28,7 @@ export async function createPageAgentBrowserConnection(endpoint, isCurrent) {
28
28
  native.on("close", disconnect);
29
29
  native.on("error", disconnect);
30
30
  downstream.on("message", (bytes, binary) => {
31
- if (binary || !isCurrent() || native.bufferedAmount > limit) {
31
+ if (binary || !isCurrent()) {
32
32
  disconnect();
33
33
  return;
34
34
  }
@@ -45,7 +45,7 @@ export async function createPageAgentBrowserConnection(endpoint, isCurrent) {
45
45
  native.on("open", () => { for (const bytes of early)
46
46
  native.send(bytes, { binary: false }); early.length = 0; buffered = 0; });
47
47
  native.on("message", (bytes, binary) => {
48
- if (binary || !isCurrent() || downstream.readyState !== WebSocket.OPEN || downstream.bufferedAmount > limit) {
48
+ if (binary || !isCurrent() || downstream.readyState !== WebSocket.OPEN) {
49
49
  disconnect();
50
50
  return;
51
51
  }
@@ -2,7 +2,6 @@ import { randomBytes } from "node:crypto";
2
2
  import { createServer } from "node:http";
3
3
  import WebSocket, { WebSocketServer } from "ws";
4
4
  const CHILD_TYPES = new Set(["iframe", "worker", "service_worker", "shared_worker"]);
5
- const MAX_BYTES = 8 * 1024 * 1024;
6
5
  const TARGET_METHODS = new Set(["Target.getTargets", "Target.getTargetInfo", "Target.setDiscoverTargets", "Target.setAutoAttach", "Target.attachToTarget", "Target.detachFromTarget", "Target.activateTarget"]);
7
6
  const BROWSER_METHODS = new Set(["Browser.getVersion", "Browser.getWindowForTarget"]);
8
7
  /** A capability for one existing native target, never the whole Chrome process. */
@@ -15,7 +14,7 @@ export async function createPageCdpGateway(upstreamEndpoint, targetId, isCurrent
15
14
  // The daemon hands the nonce URL directly to its helper. Never publish that
16
15
  // capability on an unauthenticated discovery endpoint.
17
16
  const server = createServer((_request, response) => { response.writeHead(404).end(); });
18
- const sockets = new WebSocketServer({ noServer: true, maxPayload: MAX_BYTES });
17
+ const sockets = new WebSocketServer({ noServer: true });
19
18
  server.on("upgrade", (request, socket, head) => {
20
19
  if (stopped || !isCurrent() || request.url !== path || downstream ||
21
20
  request.headers.origin !== undefined || request.headers.host !== new URL(endpoint).host) {
@@ -26,7 +25,7 @@ export async function createPageCdpGateway(upstreamEndpoint, targetId, isCurrent
26
25
  });
27
26
  sockets.on("connection", (client) => {
28
27
  downstream = client;
29
- const native = new WebSocket(upstreamEndpoint, { maxPayload: MAX_BYTES, handshakeTimeout: 5_000 });
28
+ const native = new WebSocket(upstreamEndpoint, { handshakeTimeout: 5_000 });
30
29
  upstream = native;
31
30
  const sessions = new Set();
32
31
  const children = new Map();
@@ -12,7 +12,9 @@ export declare class SqliteSessionLogStore implements SessionLogStore {
12
12
  append(sessionId: string, items: SessionItem[]): Promise<SessionItem[]>;
13
13
  list(sessionId: string, opts?: {
14
14
  afterId?: string;
15
+ beforeId?: string;
15
16
  limit?: number;
17
+ order?: "asc" | "desc";
16
18
  }): Promise<SessionItem[]>;
17
19
  get(sessionId: string, itemId: string): Promise<SessionItem | undefined>;
18
20
  snapshot(sessionId: string): Promise<SessionItem[]>;
@@ -93,23 +93,34 @@ export class SqliteSessionLogStore {
93
93
  }
94
94
  async list(sessionId, opts = {}) {
95
95
  const conn = db();
96
- let afterPosition = -1;
97
- if (opts.afterId) {
96
+ const order = opts.order ?? "asc";
97
+ const cursorId = opts.afterId ?? opts.beforeId;
98
+ let cursorPosition;
99
+ if (cursorId) {
98
100
  const cursor = conn
99
- .prepare("SELECT position FROM session_items WHERE id = ?")
100
- .get(opts.afterId);
101
+ .prepare("SELECT position FROM session_items WHERE session_id = ? AND id = ?")
102
+ .get(sessionId, cursorId);
101
103
  // Unknown cursor ⇒ empty page (rather than silently returning everything).
102
104
  if (!cursor)
103
105
  return [];
104
- afterPosition = cursor.position;
106
+ cursorPosition = cursor.position;
105
107
  }
106
108
  const limit = opts.limit ?? -1; // SQLite treats LIMIT -1 as "no limit"
107
- const rows = conn
108
- .prepare(`SELECT * FROM session_items
109
- WHERE session_id = ? AND position > ?
110
- ORDER BY position ASC
111
- LIMIT ?`)
112
- .all(sessionId, afterPosition, limit);
109
+ const descending = order === "desc";
110
+ const before = opts.beforeId !== undefined;
111
+ const comparison = before
112
+ ? (descending ? ">" : "<")
113
+ : (descending ? "<" : ">");
114
+ const positionBound = cursorPosition ?? (descending ? Number.MAX_SAFE_INTEGER : -1);
115
+ const sqlOrder = before
116
+ ? (descending ? "ASC" : "DESC")
117
+ : (descending ? "DESC" : "ASC");
118
+ const rows = conn.prepare(`SELECT * FROM session_items
119
+ WHERE session_id = ? AND position ${cursorPosition === undefined ? (descending ? "<=" : ">=") : comparison} ?
120
+ ORDER BY position ${sqlOrder}
121
+ LIMIT ?`).all(sessionId, positionBound, limit);
122
+ if (before)
123
+ rows.reverse();
113
124
  return rows.map(toItem);
114
125
  }
115
126
  async get(sessionId, itemId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/daemon",
3
- "version": "0.1.11-beta.50",
3
+ "version": "0.1.11-beta.52",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -52,18 +52,18 @@
52
52
  "tar": "^7.5.19",
53
53
  "undici": "^7.28.0",
54
54
  "ws": "^8.21.0",
55
- "@rynx-ai/core": "0.1.11-beta.50",
56
- "@rynx-ai/emulator": "0.1.11-beta.50",
57
- "@rynx-ai/plugin-runner": "0.1.11-beta.50",
58
- "@rynx-ai/plugin-sdk": "0.1.11-beta.50",
59
- "@rynx-ai/protocol": "0.1.11-beta.50",
60
- "@rynx-ai/remote-runtime-client": "0.1.11-beta.50",
61
- "@rynx-ai/server": "0.1.11-beta.50"
55
+ "@rynx-ai/emulator": "0.1.11-beta.52",
56
+ "@rynx-ai/core": "0.1.11-beta.52",
57
+ "@rynx-ai/plugin-runner": "0.1.11-beta.52",
58
+ "@rynx-ai/plugin-sdk": "0.1.11-beta.52",
59
+ "@rynx-ai/protocol": "0.1.11-beta.52",
60
+ "@rynx-ai/remote-runtime-client": "0.1.11-beta.52",
61
+ "@rynx-ai/server": "0.1.11-beta.52"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/ws": "^8.18.1",
65
- "@rynx-ai/browser-cdp": "0.1.11-beta.50",
66
- "@rynx-ai/plugin-channel-lark": "0.1.11-beta.50"
65
+ "@rynx-ai/browser-cdp": "0.1.11-beta.52",
66
+ "@rynx-ai/plugin-channel-lark": "0.1.11-beta.52"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "rm -rf dist bundled-plugins && tsc -p tsconfig.json && chmod +x dist/index-daemon.js && node scripts/prepare-agent-browser.mjs && node ../../scripts/stage-bundled-plugins.mjs",