rnxsim 0.1.408 → 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 (69) 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 +1 -1
  32. package/dist-lib/capture-contract.mjs +1 -1
  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 +1 -1
  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/cloud-contract.ts +0 -1
  69. package/src/vite-plugin.ts +62 -0
@@ -0,0 +1,176 @@
1
+ // hidden child-process entry points.
2
+ //
3
+ // the CLI needs separate JS processes for a few jobs: a detached browser host
4
+ // that outlives `rnx open`, playwright's own installer, and a blocking fetch
5
+ // worker for maestro's synchronous `http` API. each used to run as
6
+ // `process.execPath -e '<source>'`, which cannot work once the CLI ships as a
7
+ // compiled binary: there process.execPath is rnx, and rnx has no -e. a
8
+ // shell-installed user has no node and no bun either, so there is nothing else
9
+ // to reach for. the binary is the JS runtime, and it runs these itself.
10
+ //
11
+ // spawn them through rnxSelfInvocation() so the npm and standalone shapes take
12
+ // the same path. bin.ts dispatches here straight from argv, before any CLI
13
+ // startup work, which keeps a per-request worker (sync-http) cheap.
14
+
15
+ import { existsSync, writeFileSync } from 'node:fs'
16
+ import { createRequire } from 'node:module'
17
+ import * as vm from 'node:vm'
18
+
19
+ export const RNX_INTERNAL_COMMAND = '__internal'
20
+
21
+ export const RNX_INTERNAL_CHILDREN = {
22
+ playwrightProbe: 'playwright-probe',
23
+ playwrightInstall: 'playwright-install',
24
+ playwrightHost: 'playwright-host',
25
+ syncHttp: 'sync-http',
26
+ } as const
27
+
28
+ /** require() rooted at the working directory, which is where a user's
29
+ * node_modules lives. the compiled binary carries none of its own. */
30
+ function requireFromCwd(): NodeJS.Require {
31
+ return createRequire(`${process.cwd()}/`)
32
+ }
33
+
34
+ function isRecord(value: unknown): value is Record<string, unknown> {
35
+ return typeof value === 'object' && value !== null
36
+ }
37
+
38
+ /** what a one-shot child hands back to the caller that is blocked reading it. */
39
+ interface ChildReply {
40
+ stdout: string
41
+ code: number
42
+ }
43
+
44
+ export async function runInternalChild(args: string[]): Promise<void> {
45
+ const [name, ...rest] = args
46
+ let reply: ChildReply | undefined
47
+ try {
48
+ switch (name) {
49
+ case RNX_INTERNAL_CHILDREN.playwrightProbe:
50
+ reply = playwrightProbe()
51
+ break
52
+ case RNX_INTERNAL_CHILDREN.playwrightInstall:
53
+ playwrightInstall(rest)
54
+ break
55
+ case RNX_INTERNAL_CHILDREN.playwrightHost:
56
+ await playwrightHost()
57
+ break
58
+ case RNX_INTERNAL_CHILDREN.syncHttp:
59
+ reply = await syncHttp()
60
+ break
61
+ default:
62
+ throw new Error(`unknown internal child: ${name ?? '(none)'}`)
63
+ }
64
+ } catch (error) {
65
+ writeFileSync(2, `${error instanceof Error ? error.stack : String(error)}\n`)
66
+ process.exit(1)
67
+ }
68
+
69
+ // the two one-shot children answer a caller that is blocked in spawnSync
70
+ // until this process ends, so end it on the reply rather than whenever the
71
+ // runtime decides nothing is left open. that is only safe because the write
72
+ // goes through the file descriptor: through process.stdout a pipe write
73
+ // completes asynchronously on macos, and exiting would truncate the json
74
+ // being parsed on the other side.
75
+ if (reply) {
76
+ writeFileSync(1, reply.stdout)
77
+ process.exit(reply.code)
78
+ }
79
+
80
+ // playwright's installer and the browser host are both still working when
81
+ // they return here. they own the process from now on, and it ends when their
82
+ // own work does.
83
+ }
84
+
85
+ /** report where the resolved playwright package expects its chromium and
86
+ * whether that file is there. a separate process so the caller's environment
87
+ * snapshot — PLAYWRIGHT_BROWSERS_PATH above all — is the only browser cache
88
+ * this ever sees. */
89
+ function playwrightProbe(): ChildReply {
90
+ const modulePath = process.env.SOOTSIM_PW_MODULE
91
+ if (!modulePath) throw new Error('SOOTSIM_PW_MODULE is not set')
92
+ const playwright: unknown = requireFromCwd()(modulePath)
93
+ const chromium = isRecord(playwright) ? playwright.chromium : null
94
+ if (!isRecord(chromium) || typeof chromium.executablePath !== 'function') {
95
+ throw new Error('resolved package does not expose browser "chromium"')
96
+ }
97
+ const executablePath: unknown = chromium.executablePath()
98
+ if (typeof executablePath !== 'string') {
99
+ throw new Error('browser "chromium" reported no executable path')
100
+ }
101
+ return {
102
+ stdout: JSON.stringify({ executablePath, exists: existsSync(executablePath) }),
103
+ code: 0,
104
+ }
105
+ }
106
+
107
+ /** run playwright's own CLI entry, the only thing allowed to install the
108
+ * browser revision its API named. argv is shaped the way that CLI's argument
109
+ * parser expects to receive it from node. it owns this process from here:
110
+ * it sets the exit code and may exit outright. */
111
+ function playwrightInstall(args: string[]): void {
112
+ const [cliPath, ...cliArgs] = args
113
+ if (!cliPath) {
114
+ throw new Error('internal playwright-install needs the package CLI path')
115
+ }
116
+ process.argv = [process.argv[0], cliPath, ...cliArgs]
117
+ requireFromCwd()(cliPath)
118
+ }
119
+
120
+ /** the detached browser host. its source is CJS that resolves playwright from
121
+ * an absolute path the driver hands it in the environment, launches chrome,
122
+ * and keeps this process alive until the browser goes away. */
123
+ async function playwrightHost(): Promise<void> {
124
+ const { PLAYWRIGHT_SIM_HOST } = await import('./drivers/playwright-sim-host')
125
+ const run: unknown = vm.runInThisContext(
126
+ `(function(require){${PLAYWRIGHT_SIM_HOST}\n})`,
127
+ { filename: 'rnx-playwright-host.js' },
128
+ )
129
+ if (typeof run !== 'function') throw new Error('browser host source is not runnable')
130
+ run(requireFromCwd())
131
+ }
132
+
133
+ /** one blocking fetch. maestro's flow `http` API is synchronous and node has
134
+ * no synchronous fetch, so the caller round-trips each request through a
135
+ * spawnSync of this: JSON request on stdin, JSON response on stdout. a failed
136
+ * request is still a reply — the flow engine reads the error out of the same
137
+ * json — so only the exit code separates the two. */
138
+ async function syncHttp(): Promise<ChildReply> {
139
+ const chunks: Buffer[] = []
140
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk))
141
+ try {
142
+ const request: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))
143
+ if (!isRecord(request) || typeof request.url !== 'string') {
144
+ throw new Error('sync-http needs a JSON request with a url')
145
+ }
146
+ const response = await fetch(request.url, {
147
+ method: typeof request.method === 'string' ? request.method : undefined,
148
+ headers: isRecord(request.headers) ? readStringMap(request.headers) : undefined,
149
+ body: typeof request.body === 'string' ? request.body : undefined,
150
+ })
151
+ const body = await response.text()
152
+ const headers: Record<string, string> = {}
153
+ for (const [key, value] of response.headers.entries()) {
154
+ headers[key] = headers[key] ? `${headers[key]},${value}` : value
155
+ }
156
+ return {
157
+ stdout: JSON.stringify({ ok: response.ok, status: response.status, body, headers }),
158
+ code: 0,
159
+ }
160
+ } catch (error) {
161
+ return {
162
+ stdout: JSON.stringify({
163
+ __error: error instanceof Error ? error.message : String(error),
164
+ }),
165
+ code: 1,
166
+ }
167
+ }
168
+ }
169
+
170
+ function readStringMap(value: Record<string, unknown>): Record<string, string> {
171
+ const result: Record<string, string> = {}
172
+ for (const [key, entry] of Object.entries(value)) {
173
+ if (typeof entry === 'string') result[key] = entry
174
+ }
175
+ return result
176
+ }
package/cli/maestro-js.ts CHANGED
@@ -27,37 +27,11 @@
27
27
 
28
28
  import { spawnSync } from 'child_process'
29
29
  import * as vm from 'vm'
30
+ import { RNX_INTERNAL_CHILDREN, RNX_INTERNAL_COMMAND } from './internal-child'
31
+ import { rnxSelfInvocation } from './self-invocation'
30
32
 
31
33
  const HTTP_TIMEOUT_MS = 300_000 // upstream: okhttp read/write/call timeout 5min
32
34
 
33
- // child process source for synchronous http from inside the vm context.
34
- // maestro's `http` API is synchronous (scripts do `var res = http.post(...)`),
35
- // and node has no sync fetch — so the request round-trips through a blocking
36
- // spawnSync of this script. stdin: JSON request; stdout: JSON response.
37
- const HTTP_CHILD_SRC = `
38
- let input = '';
39
- process.stdin.on('data', (c) => { input += c });
40
- process.stdin.on('end', async () => {
41
- try {
42
- const req = JSON.parse(input);
43
- const res = await fetch(req.url, {
44
- method: req.method,
45
- headers: req.headers,
46
- body: req.body === undefined || req.body === null ? undefined : req.body,
47
- });
48
- const body = await res.text();
49
- const headers = {};
50
- for (const [k, v] of res.headers.entries()) {
51
- headers[k] = headers[k] ? headers[k] + ',' + v : v;
52
- }
53
- process.stdout.write(JSON.stringify({ ok: res.ok, status: res.status, body, headers }));
54
- } catch (err) {
55
- process.stdout.write(JSON.stringify({ __error: (err && err.message) || String(err) }));
56
- process.exitCode = 1;
57
- }
58
- });
59
- `
60
-
61
35
  interface HttpParams {
62
36
  body?: string
63
37
  headers?: Record<string, string>
@@ -73,24 +47,39 @@ function executeSyncHttp(
73
47
  if (params?.multipartForm) {
74
48
  throw new Error('http: multipartForm is not supported by rnx yet')
75
49
  }
76
- // the http child must be hermetic: when the runner runs under `bun` (or any
50
+ // maestro's `http` API is synchronous (flows do `var res = http.post(...)`)
51
+ // and there is no synchronous fetch, so each request round-trips through a
52
+ // blocking spawn of this CLI's own fetch worker: JSON in on stdin, JSON out
53
+ // on stdout.
54
+ //
55
+ // the child must be hermetic: when the runner runs under `bun` (or any
77
56
  // process with a debug inspector), the inherited BUN_INSPECT*/NODE_OPTIONS
78
- // env makes the spawned `bun -e` attach to the parent's inspector/IPC socket
79
- // and exit 1 with no output instead of running our fetch. scrub those so the
80
- // child is a clean fetch worker regardless of how the parent was launched.
57
+ // env makes the child attach to the parent's inspector/IPC socket and exit 1
58
+ // with no output instead of running our fetch. scrub those so the child is a
59
+ // clean fetch worker regardless of how the parent was launched.
81
60
  const childEnv = { ...process.env }
82
61
  for (const key of Object.keys(childEnv)) {
83
62
  if (key.startsWith('BUN_INSPECT') || key === 'NODE_OPTIONS') {
84
63
  delete childEnv[key]
85
64
  }
86
65
  }
87
- const child = spawnSync(process.execPath, ['-e', HTTP_CHILD_SRC], {
88
- input: JSON.stringify({ url, method, headers: params?.headers, body: params?.body }),
89
- encoding: 'utf8',
90
- timeout: HTTP_TIMEOUT_MS,
91
- maxBuffer: 64 * 1024 * 1024,
92
- env: childEnv,
93
- })
66
+ const self = rnxSelfInvocation()
67
+ const child = spawnSync(
68
+ self.executable,
69
+ [...self.prefixArgs, RNX_INTERNAL_COMMAND, RNX_INTERNAL_CHILDREN.syncHttp],
70
+ {
71
+ input: JSON.stringify({
72
+ url,
73
+ method,
74
+ headers: params?.headers,
75
+ body: params?.body,
76
+ }),
77
+ encoding: 'utf8',
78
+ timeout: HTTP_TIMEOUT_MS,
79
+ maxBuffer: 64 * 1024 * 1024,
80
+ env: childEnv,
81
+ },
82
+ )
94
83
  if (child.error) {
95
84
  throw new Error(`http ${method} ${url} failed: ${child.error.message}`)
96
85
  }