rnxsim 0.1.407 → 0.1.409

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.
Files changed (70) hide show
  1. package/README.md +11 -10
  2. package/cli/bin.ts +16 -643
  3. package/cli/commands/assert.ts +6 -6
  4. package/cli/commands/box/rnx-command.ts +3 -11
  5. package/cli/commands/control.ts +3 -9
  6. package/cli/commands/daemon-mac-app.ts +5 -11
  7. package/cli/commands/daemon.ts +63 -113
  8. package/cli/drivers/playwright-provisioning.ts +33 -29
  9. package/cli/drivers/playwright-sim-host.ts +501 -0
  10. package/cli/drivers/playwright.ts +35 -522
  11. package/cli/hints.ts +2 -4
  12. package/cli/internal-child.ts +176 -0
  13. package/cli/maestro-js.ts +28 -39
  14. package/cli/main.ts +645 -0
  15. package/cli/outbound-endpoints.ts +8 -0
  16. package/cli/self-invocation.ts +34 -0
  17. package/dist-lib/agent-daemon-client.cjs +35 -25
  18. package/dist-lib/agent-events.cjs +1 -1
  19. package/dist-lib/agent-identity.cjs +1 -1
  20. package/dist-lib/agent-sessions.cjs +35 -25
  21. package/dist-lib/attached-projects.cjs +1 -1
  22. package/dist-lib/auth/shared-session.cjs +1 -1
  23. package/dist-lib/backend-origin.cjs +1 -1
  24. package/dist-lib/beta.cjs +1 -1
  25. package/dist-lib/beta.mjs +1 -1
  26. package/dist-lib/bridge-constants.cjs +1 -1
  27. package/dist-lib/bridge-contract-input.cjs +1 -1
  28. package/dist-lib/bridge-contract-input.mjs +1 -1
  29. package/dist-lib/bridge-contract.cjs +1 -1
  30. package/dist-lib/bridge-contract.mjs +1 -1
  31. package/dist-lib/capture-contract.cjs +2 -2
  32. package/dist-lib/capture-contract.mjs +2 -2
  33. package/dist-lib/cli-constants.cjs +1 -1
  34. package/dist-lib/cloud-contract.cjs +1 -4
  35. package/dist-lib/cloud-contract.mjs +1 -3
  36. package/dist-lib/config.cjs +1 -1
  37. package/dist-lib/detox/index.cjs +1 -1
  38. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  39. package/dist-lib/home-paths.cjs +1 -1
  40. package/dist-lib/host/bridge-host.cjs +25 -15
  41. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  42. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  43. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  44. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  45. package/dist-lib/host/websocket-proxy.cjs +1 -1
  46. package/dist-lib/index.cjs +2 -2
  47. package/dist-lib/jump-to-source-babel.cjs +1 -1
  48. package/dist-lib/menu.cjs +1 -1
  49. package/dist-lib/menu.mjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  51. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  52. package/dist-lib/metro-production-bundle.cjs +1 -1
  53. package/dist-lib/metro-production-bundle.mjs +1 -1
  54. package/dist-lib/metro.cjs +1 -1
  55. package/dist-lib/profiles.cjs +1 -1
  56. package/dist-lib/public-brand.cjs +1 -1
  57. package/dist-lib/react-native-host-modules.cjs +1 -1
  58. package/dist-lib/react-native-host-modules.mjs +1 -1
  59. package/dist-lib/render-mode.cjs +1 -1
  60. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  61. package/dist-lib/sdk.cjs +1 -1
  62. package/dist-lib/sdk.mjs +1 -1
  63. package/dist-lib/skills.cjs +134 -608
  64. package/dist-lib/vite.cjs +1 -1
  65. package/package.json +1 -1
  66. package/src/agent-daemon-client.ts +2 -2
  67. package/src/agent-sessions.ts +29 -24
  68. package/src/capture-contract.ts +3 -0
  69. package/src/cloud-contract.ts +0 -1
  70. package/src/vite-plugin.ts +62 -0
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.407 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.409 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnxsim",
3
- "version": "0.1.407",
3
+ "version": "0.1.409",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for rnx.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -365,12 +365,12 @@ export async function ensureDaemonRunning(
365
365
  'DEV_HOST_REFUSED',
366
366
  )
367
367
  }
368
- const { cmd, prefixArgs } = resolveSootsimInvocation()
368
+ const { executable, prefixArgs } = resolveSootsimInvocation()
369
369
  const args = [...prefixArgs, 'serve', '--quiet']
370
370
  if (port !== DEFAULT_SOOTSIM_BRIDGE_PORT) {
371
371
  args.push('--port', String(port))
372
372
  }
373
- const child = spawn(cmd, args, {
373
+ const child = spawn(executable, args, {
374
374
  detached: true,
375
375
  stdio: 'ignore',
376
376
  env: process.env,
@@ -11,6 +11,8 @@ import { randomUUID } from 'node:crypto'
11
11
  import fs, { constants as fsConstants } from 'node:fs'
12
12
  import path from 'node:path'
13
13
  import readline from 'node:readline'
14
+ import { rnxSelfInvocation, type RnxSelfInvocation } from '../cli/self-invocation.ts'
15
+ import { IS_STANDALONE } from '../cli/standalone.ts'
14
16
  import { parseAgentEventLine, type AgentEvent } from './agent-events.ts'
15
17
  import { encodeAgentPromptEnvelope, type AgentPromptEnvelope } from './agent-prompt.ts'
16
18
  import {
@@ -65,21 +67,27 @@ export function pidIsAlive(pid: number | undefined, sessionId?: string): boolean
65
67
 
66
68
  // --- invocation resolution ---
67
69
 
68
- /** figure out how to re-invoke the sootsim CLI to run `agent-wrapper`.
70
+ /** locate the rnx CLI so this process can run a subcommand of it. callers
71
+ * range from the CLI itself to electron main and the vite-plugin daemon.
69
72
  * priority:
70
73
  * 1. RNX_BIN env var (explicit override)
71
- * 2. electron prod: bundled binary under process.resourcesPath/bin/
72
- * 3. workspace build artifacts (dist-bin/<binary> or dist-cli/bin.js)
74
+ * 2. this process already IS the CLI — `rnxSelfInvocation` owns that answer
75
+ * 3. electron prod: bundled binary under process.resourcesPath/bin/
76
+ * 4. workspace build artifacts (dist-bin/<binary> or dist-cli/bin.js)
73
77
  * — shared between electron dev, the vite-plugin-hosted daemon, and
74
78
  * any other node process that isn't launched with the CLI entry
75
79
  * script directly (e.g. argv[1] = .../node_modules/.bin/vite).
76
- * 4. CLI dev (argv[1] points at the entry script): re-use argv[0] + argv[1]
77
- * 5. CLI standalone: argv[0] is the compiled binary, no prefix needed
80
+ * 5. CLI dev (argv[1] points at the entry script): re-use argv[0] + argv[1]
78
81
  */
79
- export function resolveSootsimInvocation(): { cmd: string; prefixArgs: string[] } {
82
+ export function resolveSootsimInvocation(): RnxSelfInvocation {
80
83
  if (process.env.RNX_BIN) {
81
- return { cmd: process.env.RNX_BIN, prefixArgs: [] }
84
+ return { executable: process.env.RNX_BIN, prefixArgs: [] }
82
85
  }
86
+ // the compiled standalone binary cannot be found through argv: bun reports
87
+ // argv[0] as the literal string "bun" and argv[1] as a path inside its
88
+ // virtual filesystem, so both fall through to spawning a `bun` that a
89
+ // shell-installed user does not have.
90
+ if (IS_STANDALONE) return rnxSelfInvocation()
83
91
  // electron prod: packaged binary lives under Resources/bin/.
84
92
  if (process.versions.electron) {
85
93
  const resourcesPath = (process as NodeJS.Process & { resourcesPath?: string })
@@ -90,7 +98,7 @@ export function resolveSootsimInvocation(): { cmd: string; prefixArgs: string[]
90
98
  path.join(resourcesPath, 'bin', `sootsim-${process.platform}-${process.arch}`),
91
99
  ]
92
100
  for (const c of candidates) {
93
- if (fs.existsSync(c)) return { cmd: c, prefixArgs: [] }
101
+ if (fs.existsSync(c)) return { executable: c, prefixArgs: [] }
94
102
  }
95
103
  }
96
104
  }
@@ -108,23 +116,20 @@ export function resolveSootsimInvocation(): { cmd: string; prefixArgs: string[]
108
116
  // script — `.js`/`.ts`/`.mjs` etc. launching vite gives us argv[1] =
109
117
  // `…/node_modules/.bin/vite` (no extension), which would make us try
110
118
  // to run agent-wrapper under the vite shim and silently fail.
111
- const argv0 = process.argv[0]
112
119
  const argv1 = process.argv[1]
113
120
  if (argv1 && /\.(ts|tsx|mjs|cjs|js)$/.test(argv1)) {
114
- return { cmd: argv0, prefixArgs: [argv1] }
121
+ return { executable: process.argv[0], prefixArgs: [argv1] }
115
122
  }
116
- if (!argv1 || argv1.includes('/.bin/')) {
117
- // bin-shim case falls through to the error below instead of silently
118
- // running `node` without args.
119
- throw new Error(
120
- 'rnx CLI not found. set RNX_BIN to the path of the rnx binary, ' +
121
- 'or build the workspace CLI via `bun run --cwd packages/sootsim build:cli`.',
122
- )
123
- }
124
- return { cmd: argv0, prefixArgs: [] }
123
+ // anything else — a bin shim such as `…/node_modules/.bin/vite`, or no
124
+ // argv[1] at all names no rnx entry script, so say so instead of
125
+ // spawning the host runtime with no arguments.
126
+ throw new Error(
127
+ 'rnx CLI not found. set RNX_BIN to the path of the rnx binary, ' +
128
+ 'or build the workspace CLI via `bun run --cwd packages/sootsim build:cli`.',
129
+ )
125
130
  }
126
131
 
127
- function tryWorkspaceSootsim(): { cmd: string; prefixArgs: string[] } | null {
132
+ function tryWorkspaceSootsim(): RnxSelfInvocation | null {
128
133
  try {
129
134
  const sootsimDir = resolveSootsimPackageDir()
130
135
  if (!sootsimDir) return null
@@ -132,7 +137,7 @@ function tryWorkspaceSootsim(): { cmd: string; prefixArgs: string[] } | null {
132
137
  process.platform === 'win32' ? 'windows' : process.platform
133
138
  }-${process.arch}${process.platform === 'win32' ? '.exe' : ''}`
134
139
  const distBinary = path.join(sootsimDir, 'dist-bin', binaryName)
135
- if (fs.existsSync(distBinary)) return { cmd: distBinary, prefixArgs: [] }
140
+ if (fs.existsSync(distBinary)) return { executable: distBinary, prefixArgs: [] }
136
141
  const distBin = path.join(sootsimDir, 'dist-cli', 'bin.js')
137
142
  if (fs.existsSync(distBin)) {
138
143
  try {
@@ -148,7 +153,7 @@ function tryWorkspaceSootsim(): { cmd: string; prefixArgs: string[] } | null {
148
153
  }
149
154
  }
150
155
  } catch {}
151
- return { cmd: process.execPath, prefixArgs: [distBin] }
156
+ return { executable: process.execPath, prefixArgs: [distBin] }
152
157
  }
153
158
  return null
154
159
  } catch {
@@ -368,7 +373,7 @@ export async function startSession(opts: StartSessionOpts): Promise<StartSession
368
373
  fs.chmodSync(transcriptDir, 0o700)
369
374
  } catch {}
370
375
 
371
- const { cmd, prefixArgs } = resolveSootsimInvocation()
376
+ const { executable, prefixArgs } = resolveSootsimInvocation()
372
377
  const wrapperArgs = [
373
378
  ...prefixArgs,
374
379
  'agent-wrapper',
@@ -394,7 +399,7 @@ export async function startSession(opts: StartSessionOpts): Promise<StartSession
394
399
  wrapperArgs.push('--claude-session-uuid', claudeSessionUuid)
395
400
  }
396
401
 
397
- const child = spawn(cmd, wrapperArgs, {
402
+ const child = spawn(executable, wrapperArgs, {
398
403
  detached: true,
399
404
  stdio: 'ignore',
400
405
  env: {
@@ -137,6 +137,8 @@ export interface RnxCaptureTree {
137
137
  viewport: { width: number; height: number }
138
138
  rootNodeId: number
139
139
  nodes: RnxCaptureNode[]
140
+ /** reconciler commit shared by semantic, layout, and visual evidence */
141
+ commitRevision?: number
140
142
  }
141
143
 
142
144
  export interface RnxCaptureVisualEvidence {
@@ -511,6 +513,7 @@ export function isRnxScreenCapture(value: unknown): value is RnxScreenCapture {
511
513
  captureInteger(tree.viewport.height) &&
512
514
  tree.viewport.height > 0 &&
513
515
  captureInteger(tree.rootNodeId) &&
516
+ (tree.commitRevision === undefined || captureInteger(tree.commitRevision)) &&
514
517
  Array.isArray(tree.nodes) &&
515
518
  tree.nodes.every(captureNode) &&
516
519
  (visual === undefined ||
@@ -1,7 +1,6 @@
1
1
  import type { BridgeCommandType } from './bridge-contract'
2
2
 
3
3
  export const RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024
4
- export const RNX_CLOUD_DESIGN_PARALLEL_JOB_LIMIT = 16
5
4
  export const RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES = 256
6
5
  export const RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024
7
6
 
@@ -466,9 +466,71 @@ export function engineShellWorkerPlugins(): Plugin[] {
466
466
  keyboardControllerBindingsRedirect(),
467
467
  rnghUpstreamInternalResolvePlugin(),
468
468
  workletsBabelTransform(() => true),
469
+ workerBootChunkPlugin(ENGINE_WORKER_BOOT_CLOSURES),
469
470
  ]
470
471
  }
471
472
 
473
+ // a worker cannot modulepreload, so every static level under its
474
+ // implementation root and every dynamic import on its boot path is a serial
475
+ // round trip before the worker is ready. this plugin bundles each declared
476
+ // worker into one file and fails the build when that file grows past its byte
477
+ // cap, so a worker feature cannot silently add startup bytes.
478
+ //
479
+ // the entries stay export-free and import-free so webkit keeps the one-way
480
+ // dynamic boundary (see shell-worker-entry.ts); with every chunk inlined no
481
+ // other file can import the worker script, so nothing can evaluate it twice.
482
+ // inlined dynamic roots still evaluate at their `import()` (the reanimated
483
+ // passthrough must install before upstream reanimated, the worklet runtime
484
+ // must exist before shell-in-worker): with code splitting off rolldown wraps
485
+ // every dynamic-import target in an init function that runs on first import.
486
+ // shell-worker.ts throws at init if a bundle ever hoists one of them.
487
+ export type WorkerBootClosure = {
488
+ // substring of the worker entry module id this applies to
489
+ entry: string
490
+ // byte cap on the rendered worker file; raise it in the same change that
491
+ // adds worker code, never silently
492
+ maxBytes: number
493
+ }
494
+
495
+ const ENGINE_WORKER_BOOT_CLOSURES: WorkerBootClosure[] = [
496
+ { entry: 'src/render-worker/shell-worker-entry.ts', maxBytes: 3_480_000 },
497
+ { entry: 'src/render-worker/compositor-worker-entry.ts', maxBytes: 1_255_000 },
498
+ ]
499
+
500
+ export function workerBootChunkPlugin(closures: WorkerBootClosure[]): Plugin {
501
+ let closure: WorkerBootClosure | null = null
502
+ return {
503
+ name: 'sootsim-worker-boot-chunk',
504
+ apply: 'build',
505
+ options(inputOptions) {
506
+ const input = inputOptions.input
507
+ closure =
508
+ typeof input === 'string'
509
+ ? (closures.find((candidate) => input.includes(candidate.entry)) ?? null)
510
+ : null
511
+ },
512
+ outputOptions(options) {
513
+ if (!closure) return null
514
+ return { ...options, inlineDynamicImports: true }
515
+ },
516
+ generateBundle(_options, bundle) {
517
+ if (!closure) return
518
+ const chunks = Object.values(bundle).filter((output) => output.type === 'chunk')
519
+ if (chunks.length !== 1 || !chunks[0].isEntry) {
520
+ this.error(
521
+ `worker ${closure.entry}: expected one inlined chunk, got ${chunks.map((chunk) => chunk.fileName).join(', ')}`,
522
+ )
523
+ }
524
+ const bytes = Buffer.byteLength(chunks[0].code)
525
+ if (bytes > closure.maxBytes) {
526
+ this.error(
527
+ `worker ${closure.entry}: ${chunks[0].fileName} is ${bytes} bytes, over the ${closure.maxBytes} byte cap; raise the cap in the same change that grows the worker`,
528
+ )
529
+ }
530
+ },
531
+ }
532
+ }
533
+
472
534
  // react-native-keyboard-controller is pure-JS for everything except its
473
535
  // native-event seam (`bindings.ts` / `bindings.native.ts`) and RN platform
474
536
  // `findNodeHandle` resolution. let upstream's components, hooks, animated