@qontinui/ui-bridge-native 0.6.8 → 0.6.10

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.
@@ -55,6 +55,11 @@ var UI_BRIDGE_NATIVE_ROUTES = {
55
55
  path: "/ui-bridge/control/snapshot",
56
56
  description: "Get full bridge snapshot"
57
57
  },
58
+ DISCOVER: {
59
+ method: "GET",
60
+ path: "/ui-bridge/control/discover",
61
+ description: "Discover elements (alias for snapshot; web/runner parity)"
62
+ },
58
63
  PAGE_HEALTH: {
59
64
  method: "POST",
60
65
  path: "/ui-bridge/control/page-health",
@@ -182,6 +187,18 @@ var UI_BRIDGE_NATIVE_ROUTES = {
182
187
  path: "/ui-bridge/control/modal/dismiss/:id",
183
188
  description: "Dismiss a modal by id (testHooks)"
184
189
  },
190
+ // Observability — last-N ring buffers (always mounted; `installed:false`
191
+ // when the console/network patches are off, i.e. no testHooks).
192
+ CONSOLE_ERRORS: {
193
+ method: "GET",
194
+ path: "/ui-bridge/control/console-errors",
195
+ description: "Last-N captured console.error/console.warn entries"
196
+ },
197
+ NETWORK_REQUESTS: {
198
+ method: "GET",
199
+ path: "/ui-bridge/sdk/network-requests",
200
+ description: "Last-N captured fetch/XHR requests with status + duration"
201
+ },
185
202
  // Health
186
203
  HEALTH: {
187
204
  method: "GET",
@@ -941,6 +958,13 @@ function matchElementsByText(registry, text, opts) {
941
958
  scored.sort((a, b) => a.rank - b.rank);
942
959
  return scored.map((s) => s.element);
943
960
  }
961
+ function resolveElementState(element) {
962
+ const state = element.getState();
963
+ if (element.type === "input" && state.value === void 0 && typeof element.props?.value === "string") {
964
+ return { ...state, value: element.props.value };
965
+ }
966
+ return state;
967
+ }
944
968
  async function executeWorkflowStep(step, registry, executor) {
945
969
  const startTime = Date.now();
946
970
  try {
@@ -1177,7 +1201,7 @@ function createServerHandlers(registry, executor, config) {
1177
1201
  type: element.type,
1178
1202
  label: element.label,
1179
1203
  identifier: element.getIdentifier(),
1180
- state: element.getState(),
1204
+ state: resolveElementState(element),
1181
1205
  actions: element.actions,
1182
1206
  customActions: element.customActions ? Object.keys(element.customActions) : void 0,
1183
1207
  registeredHandlers: handlers.length > 0 ? handlers : void 0,
@@ -1191,18 +1215,19 @@ function createServerHandlers(registry, executor, config) {
1191
1215
  if (!element) {
1192
1216
  return error2(`Element not found: ${id}`, "ELEMENT_NOT_FOUND");
1193
1217
  }
1194
- return success2({ state: element.getState() });
1218
+ return success2({ state: resolveElementState(element) });
1195
1219
  },
1196
1220
  executeAction: async (ctx) => {
1197
- const { id } = ctx.params;
1198
- const body = ctx.body;
1199
- if (!body?.action) {
1221
+ const rawBody = ctx.body ?? {};
1222
+ const envelope = rawBody.request && typeof rawBody.request === "object" ? rawBody.request : rawBody;
1223
+ const id = ctx.params.id ?? rawBody.id ?? "";
1224
+ if (!envelope?.action) {
1200
1225
  return error2("Action is required", "INVALID_REQUEST");
1201
1226
  }
1202
1227
  const response = await executor.executeAction(id, {
1203
- action: body.action,
1204
- params: body.params,
1205
- waitOptions: body.waitOptions
1228
+ action: envelope.action,
1229
+ params: envelope.params,
1230
+ waitOptions: envelope.waitOptions
1206
1231
  });
1207
1232
  if (!response.success) {
1208
1233
  return error2(response.error || "Action failed", "ACTION_FAILED");
@@ -1714,7 +1739,8 @@ function createServerHandlers(registry, executor, config) {
1714
1739
  return success2({
1715
1740
  entries: [],
1716
1741
  count: 0,
1717
- bufferSize: 0
1742
+ bufferSize: 0,
1743
+ installed: false
1718
1744
  });
1719
1745
  }
1720
1746
  const entries = buf.entries({
@@ -1724,7 +1750,8 @@ function createServerHandlers(registry, executor, config) {
1724
1750
  return success2({
1725
1751
  entries,
1726
1752
  count: entries.length,
1727
- bufferSize: buf.size()
1753
+ bufferSize: buf.size(),
1754
+ installed: buf.isInstalled()
1728
1755
  });
1729
1756
  },
1730
1757
  getNetworkRequests: async (ctx) => {
@@ -1735,7 +1762,8 @@ function createServerHandlers(registry, executor, config) {
1735
1762
  return success2({
1736
1763
  entries: [],
1737
1764
  count: 0,
1738
- bufferSize: 0
1765
+ bufferSize: 0,
1766
+ installed: false
1739
1767
  });
1740
1768
  }
1741
1769
  const entries = buf.entries({
@@ -1745,7 +1773,8 @@ function createServerHandlers(registry, executor, config) {
1745
1773
  return success2({
1746
1774
  entries,
1747
1775
  count: entries.length,
1748
- bufferSize: buf.size()
1776
+ bufferSize: buf.size(),
1777
+ installed: buf.isInstalled()
1749
1778
  });
1750
1779
  },
1751
1780
  // Test hooks — modal stack manipulation
@@ -1837,7 +1866,7 @@ function createServerHandlers(registry, executor, config) {
1837
1866
  };
1838
1867
  if (config?.appInfo) {
1839
1868
  response.uiBridge = {
1840
- version: "0.3.0",
1869
+ version: "0.6.10",
1841
1870
  ...config.appInfo,
1842
1871
  capabilities: ["elements", "components", "actions", "design"],
1843
1872
  elementCount: stats.elements,
@@ -2164,7 +2193,10 @@ var WS_ROUTES = [
2164
2193
  // same handler at both paths to keep agents portable across platforms.
2165
2194
  { pattern: "ai/find", handler: "find", httpMethods: ["POST"] },
2166
2195
  { pattern: "control/snapshot", handler: "getSnapshot", httpMethods: ["GET"] },
2167
- { pattern: "control/discover", handler: "getSnapshot" },
2196
+ // Discovery alias — web/runner parity with `POST /control/discover` (a
2197
+ // legacy alias for snapshot/find). Mounted on HTTP (GET+POST) so mobile
2198
+ // doesn't 404 where web returns 200; delegates to the snapshot handler.
2199
+ { pattern: "control/discover", handler: "getSnapshot", httpMethods: ["GET", "POST"] },
2168
2200
  // Page health — accepts both GET and POST so the canonical skill invocation
2169
2201
  // (`POST .../control/page-health` with an empty body) and ad-hoc curls both
2170
2202
  // work without a body-shape gotcha.
@@ -2258,19 +2290,26 @@ var WS_ROUTES = [
2258
2290
  httpMethods: ["POST"],
2259
2291
  requiresTestHooks: true
2260
2292
  },
2261
- // Observability — last-N console errors + network requests. testHooks-gated
2262
- // because patching `console.error` / `fetch` is invasive in production.
2293
+ // Observability — last-N console errors + network requests.
2294
+ //
2295
+ // Always mounted (not testHooks-gated) so they return a schema-valid 200
2296
+ // on every build instead of a 404 that agents can't distinguish from a
2297
+ // missing route. The INVASIVE part — monkey-patching `console.error` /
2298
+ // `fetch` / `XMLHttpRequest` — stays gated behind `testHooks` (see
2299
+ // NativeUIBridgeServer.start → buffer.install()). When the patches aren't
2300
+ // installed the handlers report `installed: false` + empty entries, so
2301
+ // production builds expose the contract without observing real users'
2302
+ // console/network traffic. Set `features.testHooks` (typically on
2303
+ // `__DEV__`) to populate the buffers with live data.
2263
2304
  {
2264
2305
  pattern: "control/console-errors",
2265
2306
  handler: "getConsoleErrors",
2266
- httpMethods: ["GET"],
2267
- requiresTestHooks: true
2307
+ httpMethods: ["GET"]
2268
2308
  },
2269
2309
  {
2270
2310
  pattern: "sdk/network-requests",
2271
2311
  handler: "getNetworkRequests",
2272
- httpMethods: ["GET"],
2273
- requiresTestHooks: true
2312
+ httpMethods: ["GET"]
2274
2313
  }
2275
2314
  ];
2276
2315
  var HANDLER_DESCRIPTIONS = {
@@ -2317,8 +2356,8 @@ var HANDLER_DESCRIPTIONS = {
2317
2356
  typeInto: "App-agnostic: type into the input matching a label: body { label, text, clear?, visibleOnly? }. (selector form is web-only)",
2318
2357
  readValue: "STUB \u2014 selector-based; NOT_SUPPORTED on native (no DOM). Use /control/element/:id/state to read a value.",
2319
2358
  findByText: "App-agnostic: list elements matching free-text (no press): body { text, visibleOnly? }",
2320
- getConsoleErrors: "TEST HOOK \u2014 last-N console.error/console.warn entries; ?since=<ms>&limit=<n>",
2321
- getNetworkRequests: "TEST HOOK \u2014 last-N fetch/XHR entries with status + duration; ?since=<ms>&limit=<n>",
2359
+ getConsoleErrors: "Last-N console.error/console.warn entries; ?since=<ms>&limit=<n>. `installed:false` when capture is off (no testHooks) \u2014 entries empty but schema-valid.",
2360
+ getNetworkRequests: "Last-N fetch/XHR entries with status + duration; ?since=<ms>&limit=<n>. `installed:false` when capture is off (no testHooks) \u2014 entries empty but schema-valid.",
2322
2361
  pushModal: "TEST HOOK \u2014 push a modal onto the snapshot.modalStack via the registry ModalDetector",
2323
2362
  dismissModal: "TEST HOOK \u2014 dismiss a modal by id from the snapshot.modalStack via the registry ModalDetector",
2324
2363
  keepAwake: "TEST HOOK \u2014 keep the device screen awake via the injected keepAwakeProvider: body { enabled: boolean, durationMs?: number }"
@@ -3953,6 +3992,46 @@ var WebSocketConnection = class {
3953
3992
  }
3954
3993
  };
3955
3994
 
3956
- export { ConsoleErrorBuffer, NativeUIBridgeServer, NetworkRequestBuffer, OPCODE_BINARY, OPCODE_CLOSE, OPCODE_CONTINUATION, OPCODE_PING, OPCODE_PONG, OPCODE_TEXT, UI_BRIDGE_NATIVE_ROUTES, WebSocketConnection, WebSocketEventBridge, buildUpgradeResponse, computeAcceptKey, createDesignHandlers, createNativeServer, createServerHandlers, decodeFrames, decodeTextPayload, encodeCloseFrame, encodePingFrame, encodePongFrame, encodeTextFrame, isBatchRequest, isSubscribe, isUnsubscribe };
3995
+ // src/server/bind-retry.ts
3996
+ function isAddrInUseError(err) {
3997
+ const message = err instanceof Error ? err.message : typeof err === "string" ? err : String(err);
3998
+ const code = err?.code;
3999
+ return message.includes("EADDRINUSE") || message.toLowerCase().includes("address already in use") || code === "EADDRINUSE";
4000
+ }
4001
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4002
+ async function bindWithRetry(attempt, options = {}) {
4003
+ const {
4004
+ minTotalMs = 3e4,
4005
+ initialDelayMs = 500,
4006
+ maxDelayMs = 5e3,
4007
+ backoffFactor = 2,
4008
+ logger = console,
4009
+ sleep = defaultSleep,
4010
+ now = Date.now
4011
+ } = options;
4012
+ const startedAt = now();
4013
+ let delay = initialDelayMs;
4014
+ let attemptIndex = 0;
4015
+ for (; ; ) {
4016
+ try {
4017
+ await attempt(attemptIndex);
4018
+ return;
4019
+ } catch (err) {
4020
+ const elapsed = now() - startedAt;
4021
+ const budgetExhausted = elapsed + delay >= minTotalMs;
4022
+ if (!isAddrInUseError(err) || budgetExhausted) {
4023
+ throw err;
4024
+ }
4025
+ logger.warn(
4026
+ `[ui-bridge-native] bind failed with EADDRINUSE (attempt ${attemptIndex + 1}); retrying in ${delay}ms (elapsed ${elapsed}ms / budget ${minTotalMs === Infinity ? "\u221E" : minTotalMs}ms)`
4027
+ );
4028
+ await sleep(delay);
4029
+ delay = Math.min(delay * backoffFactor, maxDelayMs);
4030
+ attemptIndex += 1;
4031
+ }
4032
+ }
4033
+ }
4034
+
4035
+ export { ConsoleErrorBuffer, NativeUIBridgeServer, NetworkRequestBuffer, OPCODE_BINARY, OPCODE_CLOSE, OPCODE_CONTINUATION, OPCODE_PING, OPCODE_PONG, OPCODE_TEXT, UI_BRIDGE_NATIVE_ROUTES, WebSocketConnection, WebSocketEventBridge, bindWithRetry, buildUpgradeResponse, computeAcceptKey, createDesignHandlers, createNativeServer, createServerHandlers, decodeFrames, decodeTextPayload, encodeCloseFrame, encodePingFrame, encodePongFrame, encodeTextFrame, isAddrInUseError, isBatchRequest, isSubscribe, isUnsubscribe };
3957
4036
  //# sourceMappingURL=index.mjs.map
3958
4037
  //# sourceMappingURL=index.mjs.map