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.
- package/README.md +11 -10
- package/cli/bin.ts +16 -643
- package/cli/commands/assert.ts +6 -6
- package/cli/commands/box/rnx-command.ts +3 -11
- package/cli/commands/control.ts +3 -9
- package/cli/commands/daemon-mac-app.ts +5 -11
- package/cli/commands/daemon.ts +63 -113
- package/cli/drivers/playwright-provisioning.ts +33 -29
- package/cli/drivers/playwright-sim-host.ts +501 -0
- package/cli/drivers/playwright.ts +35 -522
- package/cli/hints.ts +2 -4
- package/cli/internal-child.ts +176 -0
- package/cli/maestro-js.ts +28 -39
- package/cli/main.ts +645 -0
- package/cli/outbound-endpoints.ts +8 -0
- package/cli/self-invocation.ts +34 -0
- package/dist-lib/agent-daemon-client.cjs +35 -25
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +35 -25
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +2 -2
- package/dist-lib/capture-contract.mjs +2 -2
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -4
- package/dist-lib/cloud-contract.mjs +1 -3
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +25 -15
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +2 -2
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +134 -608
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/src/agent-daemon-client.ts +2 -2
- package/src/agent-sessions.ts +29 -24
- package/src/capture-contract.ts +3 -0
- package/src/cloud-contract.ts +0 -1
- 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
|
-
//
|
|
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
|
|
79
|
-
//
|
|
80
|
-
//
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
}
|