rnxsim 0.1.381 → 0.1.383
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 +19 -6
- package/cli/app-fonts.ts +1 -1
- package/cli/bin.ts +118 -26
- package/cli/bridge-diagnostics.ts +1 -1
- package/cli/bridge-flow-runner.ts +9 -9
- package/cli/browser-evals.ts +3 -3
- package/cli/cli-update.ts +261 -0
- package/cli/cloud-client.ts +407 -0
- package/cli/cloud-dispatch.ts +165 -0
- package/cli/cloud-session.ts +156 -0
- package/cli/command-contract.ts +2 -2
- package/cli/command-registry.ts +9 -0
- package/cli/commands/agent-wrapper.ts +4 -4
- package/cli/commands/agent.ts +3 -3
- package/cli/commands/box/checkout-plane.ts +16 -8
- package/cli/commands/box/rnx-command.ts +2 -2
- package/cli/commands/box/search-command.ts +2 -2
- package/cli/commands/box.ts +123 -70
- package/cli/commands/config.ts +3 -2
- package/cli/commands/control.ts +6 -6
- package/cli/commands/cpu-profile.ts +2 -2
- package/cli/commands/daemon.ts +7 -7
- package/cli/commands/debug.ts +2 -2
- package/cli/commands/detox.ts +7 -7
- package/cli/commands/diagnose.ts +1 -1
- package/cli/commands/electron.ts +1 -1
- package/cli/commands/film.ts +1 -1
- package/cli/commands/flow.ts +4 -4
- package/cli/commands/inspect/core.ts +58 -7
- package/cli/commands/inspect/env.ts +5 -3
- package/cli/commands/inspect/shared.ts +2 -1
- package/cli/commands/inspect/wait-event.ts +1 -1
- package/cli/commands/inspect/wait-ready.ts +15 -5
- package/cli/commands/inspect.ts +31 -9
- package/cli/commands/install-desktop.ts +2 -2
- package/cli/commands/maestro.ts +3 -3
- package/cli/commands/permissions.ts +1 -1
- package/cli/commands/platform.ts +91 -0
- package/cli/commands/record.ts +3 -3
- package/cli/commands/report-issue.ts +1 -1
- package/cli/commands/runtime.ts +1 -1
- package/cli/commands/screenshot-mode.ts +1 -1
- package/cli/commands/screenshot.ts +25 -0
- package/cli/commands/serve.ts +1 -1
- package/cli/commands/skills.ts +1 -1
- package/cli/commands/timeline.ts +1 -1
- package/cli/commands/upgrade.ts +8 -34
- package/cli/commands/upload.ts +3 -3
- package/cli/commands/version.ts +14 -3
- package/cli/commands/what-happened.ts +2 -2
- package/cli/current-sim.ts +43 -30
- package/cli/desktop-companion.ts +1 -1
- package/cli/drivers/electron.ts +1 -1
- package/cli/drivers/playwright.ts +3 -3
- package/cli/drivers/types.ts +4 -4
- package/cli/flow-file.ts +3 -3
- package/cli/help-core.ts +18 -11
- package/cli/hidden-runtime-alias.ts +1 -1
- package/cli/hints.ts +2 -2
- package/cli/maestro-js.ts +2 -2
- package/cli/outbound-endpoints.ts +40 -6
- package/cli/parse-args.ts +1 -1
- package/cli/registry.ts +1 -1
- package/cli/resolve-assets.ts +2 -2
- package/cli/secure-file.ts +31 -0
- package/cli/shell-init.ts +98 -0
- package/cli/ws-bridge.ts +18 -8
- package/detox/expectations.ts +1 -1
- package/detox/gestures.ts +3 -3
- package/detox/index.ts +3 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- 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 +20 -8
- package/dist-lib/bridge-contract-input.mjs +20 -8
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +217 -0
- package/dist-lib/capture-contract.mjs +187 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +41 -0
- package/dist-lib/cloud-contract.mjs +13 -0
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +4 -4
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +9 -1
- package/dist-lib/host/bridge-host.cjs +15 -10
- 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 +361 -75
- 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-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 +44 -6
- package/dist-lib/sdk.mjs +44 -6
- package/dist-lib/skills.cjs +436 -155
- package/dist-lib/vite.cjs +1 -1
- package/package.json +20 -4
- package/scripts/dev-server-scanner.ts +3 -3
- package/skills/contrast/SKILL.md +1 -1
- package/skills/rnx-perf/SKILL.md +68 -52
- package/src/app-url.ts +2 -3
- package/src/bridge-contract-input.ts +25 -6
- package/src/bridge-contract.ts +35 -5
- package/src/capture-contract.ts +576 -0
- package/src/cloud-contract.ts +18 -0
- package/src/home-paths.ts +9 -3
- package/src/host/agent-host.ts +1 -1
- package/src/host/bridge-host.ts +13 -11
- package/src/host/plane-host.ts +2 -2
- package/src/index.ts +25 -0
- package/src/native-dev-bundle-url.ts +2 -2
- package/src/screenshots/orchestrate.ts +1 -1
- package/src/screenshots/org-deck.ts +1 -1
- package/src/settings-bundle.ts +1 -1
- package/src/sim-client.ts +4 -0
- package/src/vite-plugin-one.ts +2 -2
- package/src/vite-plugin.ts +1 -1
- package/cli/commands/box/cloud-box.ts +0 -294
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
// public, runtime-neutral screen capture contract. semantic evidence and pixel
|
|
2
|
+
// evidence are separate because neither one proves the other.
|
|
3
|
+
|
|
4
|
+
export const RNX_SCREEN_CAPTURE_VERSION = 1 as const
|
|
5
|
+
|
|
6
|
+
export interface RnxCaptureRect {
|
|
7
|
+
x: number
|
|
8
|
+
y: number
|
|
9
|
+
width: number
|
|
10
|
+
height: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RnxCaptureAccessibilityState {
|
|
14
|
+
disabled: boolean
|
|
15
|
+
selected: boolean
|
|
16
|
+
checked: boolean | 'mixed'
|
|
17
|
+
busy: boolean
|
|
18
|
+
expanded: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RnxCaptureAccessibilityValue {
|
|
22
|
+
min?: number
|
|
23
|
+
max?: number
|
|
24
|
+
now?: number
|
|
25
|
+
text?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RnxCaptureAccessibility {
|
|
29
|
+
accessible: boolean
|
|
30
|
+
label: string | null
|
|
31
|
+
role: string | null
|
|
32
|
+
hint: string | null
|
|
33
|
+
liveRegion: 'none' | 'polite' | 'assertive' | null
|
|
34
|
+
state: RnxCaptureAccessibilityState | null
|
|
35
|
+
value: RnxCaptureAccessibilityValue | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RnxCaptureInteractionState {
|
|
39
|
+
pressed: boolean
|
|
40
|
+
pressable: boolean
|
|
41
|
+
textInput: boolean
|
|
42
|
+
textSelection: { start: number | null; end: number | null } | null
|
|
43
|
+
secureTextEntry: boolean
|
|
44
|
+
placeholder: string | null
|
|
45
|
+
scrollOffset: { x: number; y: number }
|
|
46
|
+
zoomScale: number | null
|
|
47
|
+
nativePinchEnabled: boolean
|
|
48
|
+
contentOffsetProp: { x?: number; y?: number } | null
|
|
49
|
+
decelerationRateProp: unknown
|
|
50
|
+
strokeDashoffsetProp: number | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RnxCaptureStyleTokenBinding {
|
|
54
|
+
token: string
|
|
55
|
+
theme: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface RnxCaptureTextSegment {
|
|
59
|
+
text: string
|
|
60
|
+
color?: string | number
|
|
61
|
+
fontFamily?: string
|
|
62
|
+
fontSize?: number
|
|
63
|
+
fontStyle?: string
|
|
64
|
+
fontWeight?: string | number
|
|
65
|
+
letterSpacing?: number
|
|
66
|
+
textDecorationLine?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface RnxCaptureTextLine {
|
|
70
|
+
text: string
|
|
71
|
+
x: number
|
|
72
|
+
y: number
|
|
73
|
+
width: number
|
|
74
|
+
height: number
|
|
75
|
+
baseline: number
|
|
76
|
+
segments?: RnxCaptureTextSegment[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface RnxCaptureSource {
|
|
80
|
+
location?: string
|
|
81
|
+
file?: string
|
|
82
|
+
componentName?: string
|
|
83
|
+
componentStack?: string[]
|
|
84
|
+
react?: {
|
|
85
|
+
component?: string
|
|
86
|
+
owners?: string[]
|
|
87
|
+
source?: string
|
|
88
|
+
key?: string
|
|
89
|
+
hasRef?: boolean
|
|
90
|
+
hooks?: {
|
|
91
|
+
total: number
|
|
92
|
+
counts: Record<string, number>
|
|
93
|
+
}
|
|
94
|
+
contexts?: string[]
|
|
95
|
+
lastCommit?: {
|
|
96
|
+
ageMs: number
|
|
97
|
+
causes: string[]
|
|
98
|
+
propDiffs?: string[]
|
|
99
|
+
stateChanged?: boolean
|
|
100
|
+
actualDurationMs?: number
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface RnxCaptureNode {
|
|
106
|
+
nodeId: number
|
|
107
|
+
parentNodeId: number | null
|
|
108
|
+
childNodeIds: number[]
|
|
109
|
+
paintIndex: number
|
|
110
|
+
type: string
|
|
111
|
+
id: string | null
|
|
112
|
+
testID: string | null
|
|
113
|
+
text: string | null
|
|
114
|
+
childCount: number
|
|
115
|
+
accessibility: RnxCaptureAccessibility
|
|
116
|
+
interaction: RnxCaptureInteractionState
|
|
117
|
+
layout: {
|
|
118
|
+
local: RnxCaptureRect
|
|
119
|
+
frame: RnxCaptureRect | null
|
|
120
|
+
absolutePosition: { x: number; y: number }
|
|
121
|
+
visibleFrame: RnxCaptureRect | null
|
|
122
|
+
}
|
|
123
|
+
style: {
|
|
124
|
+
resolved: Record<string, unknown>
|
|
125
|
+
authored: Record<string, unknown>
|
|
126
|
+
computed: Record<string, unknown>
|
|
127
|
+
tokenBindings: Record<string, RnxCaptureStyleTokenBinding>
|
|
128
|
+
}
|
|
129
|
+
source: RnxCaptureSource
|
|
130
|
+
props: Record<string, unknown>
|
|
131
|
+
textLines?: RnxCaptureTextLine[]
|
|
132
|
+
/** additional generic inspect fields preserved for tooling adapters */
|
|
133
|
+
inspection: Record<string, unknown>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface RnxCaptureTree {
|
|
137
|
+
viewport: { width: number; height: number }
|
|
138
|
+
rootNodeId: number
|
|
139
|
+
nodes: RnxCaptureNode[]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface RnxCaptureVisualEvidence {
|
|
143
|
+
kind: 'png'
|
|
144
|
+
/** names the runtime that produced these pixels without claiming visibility */
|
|
145
|
+
producer: 'headless-cpu-app-surface' | 'browser-compositor-app-surface'
|
|
146
|
+
dataUri: string
|
|
147
|
+
pixelWidth: number
|
|
148
|
+
pixelHeight: number
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface RnxCaptureRegionRequest {
|
|
152
|
+
key: string
|
|
153
|
+
/** host node whose own subtree is rendered into this transparent atlas region */
|
|
154
|
+
nodeId: number
|
|
155
|
+
frame: RnxCaptureRect
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface RnxRasterRegionTree {
|
|
159
|
+
width: number
|
|
160
|
+
height: number
|
|
161
|
+
nodes: readonly {
|
|
162
|
+
nodeId: number
|
|
163
|
+
parentNodeId: number | null
|
|
164
|
+
type: string
|
|
165
|
+
style: Record<string, unknown>
|
|
166
|
+
layout: RnxCaptureRect
|
|
167
|
+
frame: RnxCaptureRect | null
|
|
168
|
+
absolutePosition: { x: number; y: number }
|
|
169
|
+
visibleFrame?: RnxCaptureRect | null
|
|
170
|
+
}[]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function selectRnxRasterNodeIds(tree: RnxRasterRegionTree): Set<number> {
|
|
174
|
+
const nodesById = new Map(tree.nodes.map((node) => [node.nodeId, node]))
|
|
175
|
+
const unsupported = new Set<number>()
|
|
176
|
+
for (const node of tree.nodes) {
|
|
177
|
+
const visibleFrame = node.visibleFrame ?? node.frame
|
|
178
|
+
if (!visibleFrame || visibleFrame.width <= 0 || visibleFrame.height <= 0) continue
|
|
179
|
+
if (
|
|
180
|
+
node.type === 'masked-view' ||
|
|
181
|
+
node.style._liquidGlass !== undefined ||
|
|
182
|
+
node.style._liquidGlassEdge !== undefined
|
|
183
|
+
) {
|
|
184
|
+
unsupported.add(node.nodeId)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const nodeId of [...unsupported]) {
|
|
189
|
+
let parentNodeId = nodesById.get(nodeId)?.parentNodeId ?? null
|
|
190
|
+
while (parentNodeId !== null) {
|
|
191
|
+
if (unsupported.has(parentNodeId)) {
|
|
192
|
+
unsupported.delete(nodeId)
|
|
193
|
+
break
|
|
194
|
+
}
|
|
195
|
+
parentNodeId = nodesById.get(parentNodeId)?.parentNodeId ?? null
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const screenArea = tree.width * tree.height
|
|
200
|
+
const maxLayerArea = screenArea * 0.25
|
|
201
|
+
const maxAtlasArea = screenArea * 0.5
|
|
202
|
+
const candidates = [...unsupported]
|
|
203
|
+
.map((nodeId) => {
|
|
204
|
+
const node = nodesById.get(nodeId)
|
|
205
|
+
const frame = node?.visibleFrame ?? node?.frame
|
|
206
|
+
return frame ? { nodeId, area: frame.width * frame.height } : null
|
|
207
|
+
})
|
|
208
|
+
.filter(
|
|
209
|
+
(candidate): candidate is { nodeId: number; area: number } =>
|
|
210
|
+
candidate !== null && candidate.area <= maxLayerArea,
|
|
211
|
+
)
|
|
212
|
+
.sort((left, right) => left.area - right.area || left.nodeId - right.nodeId)
|
|
213
|
+
const selected = new Set<number>()
|
|
214
|
+
let atlasArea = 0
|
|
215
|
+
for (const candidate of candidates) {
|
|
216
|
+
if (atlasArea + candidate.area > maxAtlasArea) continue
|
|
217
|
+
selected.add(candidate.nodeId)
|
|
218
|
+
atlasArea += candidate.area
|
|
219
|
+
}
|
|
220
|
+
return selected
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function rnxRasterFrames(
|
|
224
|
+
tree: RnxRasterRegionTree,
|
|
225
|
+
nodeIds: ReadonlySet<number>,
|
|
226
|
+
): Map<number, RnxCaptureRect> {
|
|
227
|
+
const frames = new Map<number, RnxCaptureRect>()
|
|
228
|
+
for (const node of tree.nodes) {
|
|
229
|
+
if (!nodeIds.has(node.nodeId)) continue
|
|
230
|
+
const frame = node.frame ?? {
|
|
231
|
+
x: node.absolutePosition.x,
|
|
232
|
+
y: node.absolutePosition.y,
|
|
233
|
+
width: node.layout.width,
|
|
234
|
+
height: node.layout.height,
|
|
235
|
+
}
|
|
236
|
+
const liquidGlass = node.style._liquidGlass
|
|
237
|
+
const capturePadding =
|
|
238
|
+
liquidGlass &&
|
|
239
|
+
typeof liquidGlass === 'object' &&
|
|
240
|
+
typeof Reflect.get(liquidGlass, 'capturePadding') === 'number'
|
|
241
|
+
? Reflect.get(liquidGlass, 'capturePadding')
|
|
242
|
+
: 0
|
|
243
|
+
const shadowRadius =
|
|
244
|
+
typeof node.style.shadowRadius === 'number' ? node.style.shadowRadius : 0
|
|
245
|
+
const shadowOffset = node.style.shadowOffset
|
|
246
|
+
const shadowOffsetX =
|
|
247
|
+
shadowOffset &&
|
|
248
|
+
typeof shadowOffset === 'object' &&
|
|
249
|
+
typeof Reflect.get(shadowOffset, 'width') === 'number'
|
|
250
|
+
? Reflect.get(shadowOffset, 'width')
|
|
251
|
+
: 0
|
|
252
|
+
const shadowOffsetY =
|
|
253
|
+
shadowOffset &&
|
|
254
|
+
typeof shadowOffset === 'object' &&
|
|
255
|
+
typeof Reflect.get(shadowOffset, 'height') === 'number'
|
|
256
|
+
? Reflect.get(shadowOffset, 'height')
|
|
257
|
+
: 0
|
|
258
|
+
const left = Math.max(
|
|
259
|
+
0,
|
|
260
|
+
frame.x - Math.max(capturePadding, shadowRadius - shadowOffsetX),
|
|
261
|
+
)
|
|
262
|
+
const top = Math.max(
|
|
263
|
+
0,
|
|
264
|
+
frame.y - Math.max(capturePadding, shadowRadius - shadowOffsetY),
|
|
265
|
+
)
|
|
266
|
+
const right = Math.min(
|
|
267
|
+
tree.width,
|
|
268
|
+
frame.x + frame.width + Math.max(capturePadding, shadowRadius + shadowOffsetX),
|
|
269
|
+
)
|
|
270
|
+
const bottom = Math.min(
|
|
271
|
+
tree.height,
|
|
272
|
+
frame.y + frame.height + Math.max(capturePadding, shadowRadius + shadowOffsetY),
|
|
273
|
+
)
|
|
274
|
+
if (right > left && bottom > top) {
|
|
275
|
+
frames.set(node.nodeId, {
|
|
276
|
+
x: left,
|
|
277
|
+
y: top,
|
|
278
|
+
width: right - left,
|
|
279
|
+
height: bottom - top,
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return frames
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function rnxCaptureRasterRegions(
|
|
287
|
+
capture: RnxScreenCapture,
|
|
288
|
+
): RnxCaptureRegionRequest[] {
|
|
289
|
+
const tree: RnxRasterRegionTree = {
|
|
290
|
+
width: capture.tree.viewport.width,
|
|
291
|
+
height: capture.tree.viewport.height,
|
|
292
|
+
nodes: capture.tree.nodes.map((node) => ({
|
|
293
|
+
nodeId: node.nodeId,
|
|
294
|
+
parentNodeId: node.parentNodeId,
|
|
295
|
+
type: node.type,
|
|
296
|
+
style: node.style.resolved,
|
|
297
|
+
layout: node.layout.local,
|
|
298
|
+
frame: node.layout.frame,
|
|
299
|
+
absolutePosition: node.layout.absolutePosition,
|
|
300
|
+
visibleFrame: node.layout.visibleFrame,
|
|
301
|
+
})),
|
|
302
|
+
}
|
|
303
|
+
const nodeIds = selectRnxRasterNodeIds(tree)
|
|
304
|
+
const frames = rnxRasterFrames(tree, nodeIds)
|
|
305
|
+
return [...nodeIds].flatMap((nodeId) => {
|
|
306
|
+
const frame = frames.get(nodeId)
|
|
307
|
+
return frame ? [{ key: String(nodeId), nodeId, frame }] : []
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export interface RnxCaptureAtlasRegion extends RnxCaptureRegionRequest {
|
|
312
|
+
/** source rectangle inside the packed atlas, in logical atlas coordinates */
|
|
313
|
+
source: RnxCaptureRect
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export interface RnxCaptureRegionAtlas {
|
|
317
|
+
kind: 'png-atlas'
|
|
318
|
+
producer: 'headless-cpu-app-surface'
|
|
319
|
+
dataUri: string
|
|
320
|
+
/** logical atlas dimensions; the encoded PNG uses pixelWidth × pixelHeight */
|
|
321
|
+
width: number
|
|
322
|
+
height: number
|
|
323
|
+
pixelWidth: number
|
|
324
|
+
pixelHeight: number
|
|
325
|
+
regions: RnxCaptureAtlasRegion[]
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface RnxCaptureFontRegistration {
|
|
329
|
+
url?: string
|
|
330
|
+
family: string
|
|
331
|
+
sourceLabel?: string
|
|
332
|
+
parsedFamily?: string | null
|
|
333
|
+
parsedPostScript?: string | null
|
|
334
|
+
fullName?: string | null
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export interface RnxScreenCapture {
|
|
338
|
+
contractVersion: typeof RNX_SCREEN_CAPTURE_VERSION
|
|
339
|
+
capturedAt: number
|
|
340
|
+
tree: RnxCaptureTree
|
|
341
|
+
/** app fonts registered in the captured tenant, included only when requested */
|
|
342
|
+
fonts?: RnxCaptureFontRegistration[]
|
|
343
|
+
/** optional full-frame diagnostic evidence, never required by tree consumers */
|
|
344
|
+
visual?: RnxCaptureVisualEvidence
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function captureRecord(value: unknown): value is Record<string, unknown> {
|
|
348
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function captureInteger(value: unknown): value is number {
|
|
352
|
+
return typeof value === 'number' && Number.isSafeInteger(value)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function captureNumber(value: unknown): value is number {
|
|
356
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function captureNullableString(value: unknown): value is string | null {
|
|
360
|
+
return value === null || typeof value === 'string'
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function captureRect(value: unknown): value is RnxCaptureRect {
|
|
364
|
+
return (
|
|
365
|
+
captureRecord(value) &&
|
|
366
|
+
captureNumber(value.x) &&
|
|
367
|
+
captureNumber(value.y) &&
|
|
368
|
+
captureNumber(value.width) &&
|
|
369
|
+
captureNumber(value.height)
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function captureOptionalString(value: unknown): value is string | undefined {
|
|
374
|
+
return value === undefined || typeof value === 'string'
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function captureOptionalNullableString(
|
|
378
|
+
value: unknown,
|
|
379
|
+
): value is string | null | undefined {
|
|
380
|
+
return value === undefined || captureNullableString(value)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function captureFontRegistration(value: unknown): value is RnxCaptureFontRegistration {
|
|
384
|
+
return (
|
|
385
|
+
captureRecord(value) &&
|
|
386
|
+
typeof value.family === 'string' &&
|
|
387
|
+
value.family.length > 0 &&
|
|
388
|
+
captureOptionalString(value.url) &&
|
|
389
|
+
captureOptionalString(value.sourceLabel) &&
|
|
390
|
+
captureOptionalNullableString(value.parsedFamily) &&
|
|
391
|
+
captureOptionalNullableString(value.parsedPostScript) &&
|
|
392
|
+
captureOptionalNullableString(value.fullName)
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function capturePoint(value: unknown): value is { x: number; y: number } {
|
|
397
|
+
return captureRecord(value) && captureNumber(value.x) && captureNumber(value.y)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function captureNode(value: unknown): value is RnxCaptureNode {
|
|
401
|
+
if (
|
|
402
|
+
!captureRecord(value) ||
|
|
403
|
+
!captureInteger(value.nodeId) ||
|
|
404
|
+
!(value.parentNodeId === null || captureInteger(value.parentNodeId)) ||
|
|
405
|
+
!Array.isArray(value.childNodeIds) ||
|
|
406
|
+
!value.childNodeIds.every(captureInteger) ||
|
|
407
|
+
!captureInteger(value.paintIndex) ||
|
|
408
|
+
typeof value.type !== 'string' ||
|
|
409
|
+
!captureNullableString(value.id) ||
|
|
410
|
+
!captureNullableString(value.testID) ||
|
|
411
|
+
!captureNullableString(value.text) ||
|
|
412
|
+
!captureInteger(value.childCount) ||
|
|
413
|
+
!captureRecord(value.accessibility) ||
|
|
414
|
+
!captureRecord(value.interaction) ||
|
|
415
|
+
!captureRecord(value.layout) ||
|
|
416
|
+
!captureRecord(value.style) ||
|
|
417
|
+
!captureRecord(value.source) ||
|
|
418
|
+
!captureRecord(value.props) ||
|
|
419
|
+
!captureRecord(value.inspection)
|
|
420
|
+
) {
|
|
421
|
+
return false
|
|
422
|
+
}
|
|
423
|
+
const accessibility = value.accessibility
|
|
424
|
+
if (
|
|
425
|
+
typeof accessibility.accessible !== 'boolean' ||
|
|
426
|
+
!captureNullableString(accessibility.label) ||
|
|
427
|
+
!captureNullableString(accessibility.role) ||
|
|
428
|
+
!captureNullableString(accessibility.hint) ||
|
|
429
|
+
!(
|
|
430
|
+
accessibility.liveRegion === null ||
|
|
431
|
+
accessibility.liveRegion === 'none' ||
|
|
432
|
+
accessibility.liveRegion === 'polite' ||
|
|
433
|
+
accessibility.liveRegion === 'assertive'
|
|
434
|
+
) ||
|
|
435
|
+
!(accessibility.state === null || captureRecord(accessibility.state)) ||
|
|
436
|
+
!(accessibility.value === null || captureRecord(accessibility.value))
|
|
437
|
+
) {
|
|
438
|
+
return false
|
|
439
|
+
}
|
|
440
|
+
const interaction = value.interaction
|
|
441
|
+
if (
|
|
442
|
+
typeof interaction.pressed !== 'boolean' ||
|
|
443
|
+
typeof interaction.pressable !== 'boolean' ||
|
|
444
|
+
typeof interaction.textInput !== 'boolean' ||
|
|
445
|
+
!(interaction.textSelection === null || captureRecord(interaction.textSelection)) ||
|
|
446
|
+
typeof interaction.secureTextEntry !== 'boolean' ||
|
|
447
|
+
!captureNullableString(interaction.placeholder) ||
|
|
448
|
+
!capturePoint(interaction.scrollOffset) ||
|
|
449
|
+
!(interaction.zoomScale === null || captureNumber(interaction.zoomScale)) ||
|
|
450
|
+
typeof interaction.nativePinchEnabled !== 'boolean' ||
|
|
451
|
+
!(
|
|
452
|
+
interaction.contentOffsetProp === null ||
|
|
453
|
+
captureRecord(interaction.contentOffsetProp)
|
|
454
|
+
) ||
|
|
455
|
+
!(
|
|
456
|
+
interaction.strokeDashoffsetProp === null ||
|
|
457
|
+
captureNumber(interaction.strokeDashoffsetProp)
|
|
458
|
+
)
|
|
459
|
+
) {
|
|
460
|
+
return false
|
|
461
|
+
}
|
|
462
|
+
const layout = value.layout
|
|
463
|
+
if (
|
|
464
|
+
!captureRect(layout.local) ||
|
|
465
|
+
!(layout.frame === null || captureRect(layout.frame)) ||
|
|
466
|
+
!capturePoint(layout.absolutePosition) ||
|
|
467
|
+
!(layout.visibleFrame === null || captureRect(layout.visibleFrame))
|
|
468
|
+
) {
|
|
469
|
+
return false
|
|
470
|
+
}
|
|
471
|
+
const style = value.style
|
|
472
|
+
return (
|
|
473
|
+
captureRecord(style.resolved) &&
|
|
474
|
+
captureRecord(style.authored) &&
|
|
475
|
+
captureRecord(style.computed) &&
|
|
476
|
+
captureRecord(style.tokenBindings) &&
|
|
477
|
+
(value.textLines === undefined || Array.isArray(value.textLines))
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function isRnxScreenCapture(value: unknown): value is RnxScreenCapture {
|
|
482
|
+
if (
|
|
483
|
+
!captureRecord(value) ||
|
|
484
|
+
value.contractVersion !== RNX_SCREEN_CAPTURE_VERSION ||
|
|
485
|
+
!captureInteger(value.capturedAt) ||
|
|
486
|
+
!captureRecord(value.tree) ||
|
|
487
|
+
!(
|
|
488
|
+
value.fonts === undefined ||
|
|
489
|
+
(Array.isArray(value.fonts) &&
|
|
490
|
+
value.fonts.length <= 200 &&
|
|
491
|
+
value.fonts.every(captureFontRegistration))
|
|
492
|
+
) ||
|
|
493
|
+
!(value.visual === undefined || captureRecord(value.visual))
|
|
494
|
+
) {
|
|
495
|
+
return false
|
|
496
|
+
}
|
|
497
|
+
const tree = value.tree
|
|
498
|
+
const visual = value.visual
|
|
499
|
+
return (
|
|
500
|
+
captureRecord(tree.viewport) &&
|
|
501
|
+
captureInteger(tree.viewport.width) &&
|
|
502
|
+
tree.viewport.width > 0 &&
|
|
503
|
+
captureInteger(tree.viewport.height) &&
|
|
504
|
+
tree.viewport.height > 0 &&
|
|
505
|
+
captureInteger(tree.rootNodeId) &&
|
|
506
|
+
Array.isArray(tree.nodes) &&
|
|
507
|
+
tree.nodes.every(captureNode) &&
|
|
508
|
+
(visual === undefined ||
|
|
509
|
+
(visual.kind === 'png' &&
|
|
510
|
+
(visual.producer === 'headless-cpu-app-surface' ||
|
|
511
|
+
visual.producer === 'browser-compositor-app-surface') &&
|
|
512
|
+
typeof visual.dataUri === 'string' &&
|
|
513
|
+
visual.dataUri.startsWith('data:image/png;base64,') &&
|
|
514
|
+
captureInteger(visual.pixelWidth) &&
|
|
515
|
+
visual.pixelWidth > 0 &&
|
|
516
|
+
captureInteger(visual.pixelHeight) &&
|
|
517
|
+
visual.pixelHeight > 0))
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export function isRnxCaptureRegionAtlas(value: unknown): value is RnxCaptureRegionAtlas {
|
|
522
|
+
if (
|
|
523
|
+
!captureRecord(value) ||
|
|
524
|
+
value.kind !== 'png-atlas' ||
|
|
525
|
+
value.producer !== 'headless-cpu-app-surface' ||
|
|
526
|
+
typeof value.dataUri !== 'string' ||
|
|
527
|
+
!value.dataUri.startsWith('data:image/png;base64,') ||
|
|
528
|
+
!captureNumber(value.width) ||
|
|
529
|
+
value.width <= 0 ||
|
|
530
|
+
!captureNumber(value.height) ||
|
|
531
|
+
value.height <= 0 ||
|
|
532
|
+
!captureInteger(value.pixelWidth) ||
|
|
533
|
+
value.pixelWidth <= 0 ||
|
|
534
|
+
!captureInteger(value.pixelHeight) ||
|
|
535
|
+
value.pixelHeight <= 0 ||
|
|
536
|
+
!Array.isArray(value.regions) ||
|
|
537
|
+
value.regions.length === 0 ||
|
|
538
|
+
value.regions.length > 256
|
|
539
|
+
) {
|
|
540
|
+
return false
|
|
541
|
+
}
|
|
542
|
+
const scale = value.pixelWidth / value.width
|
|
543
|
+
if (
|
|
544
|
+
!Number.isInteger(scale) ||
|
|
545
|
+
scale < 1 ||
|
|
546
|
+
scale > 4 ||
|
|
547
|
+
Math.abs(value.height * scale - value.pixelHeight) > 0.000_001
|
|
548
|
+
) {
|
|
549
|
+
return false
|
|
550
|
+
}
|
|
551
|
+
const keys = new Set<string>()
|
|
552
|
+
for (const region of value.regions) {
|
|
553
|
+
if (
|
|
554
|
+
!captureRecord(region) ||
|
|
555
|
+
typeof region.key !== 'string' ||
|
|
556
|
+
region.key.length === 0 ||
|
|
557
|
+
keys.has(region.key) ||
|
|
558
|
+
!captureInteger(region.nodeId) ||
|
|
559
|
+
region.nodeId <= 0 ||
|
|
560
|
+
!captureRect(region.frame) ||
|
|
561
|
+
region.frame.width <= 0 ||
|
|
562
|
+
region.frame.height <= 0 ||
|
|
563
|
+
!captureRect(region.source) ||
|
|
564
|
+
region.source.x < 0 ||
|
|
565
|
+
region.source.y < 0 ||
|
|
566
|
+
region.source.width !== region.frame.width ||
|
|
567
|
+
region.source.height !== region.frame.height ||
|
|
568
|
+
region.source.x + region.source.width > value.width ||
|
|
569
|
+
region.source.y + region.source.height > value.height
|
|
570
|
+
) {
|
|
571
|
+
return false
|
|
572
|
+
}
|
|
573
|
+
keys.add(region.key)
|
|
574
|
+
}
|
|
575
|
+
return true
|
|
576
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024
|
|
2
|
+
export const RNX_CLOUD_DESIGN_PARALLEL_JOB_LIMIT = 16
|
|
3
|
+
export const RNX_CLOUD_DESIGN_MAX_STORAGE_ENTRIES = 256
|
|
4
|
+
export const RNX_CLOUD_DESIGN_MAX_STORAGE_BYTES = 1024 * 1024
|
|
5
|
+
|
|
6
|
+
export interface RnxCloudClaim {
|
|
7
|
+
id: string
|
|
8
|
+
expiresAt: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RnxCloudCreateReceipt {
|
|
12
|
+
simId: string
|
|
13
|
+
artifact: {
|
|
14
|
+
id: `sha256:${string}`
|
|
15
|
+
bytes: number
|
|
16
|
+
}
|
|
17
|
+
claim: RnxCloudClaim
|
|
18
|
+
}
|
package/src/home-paths.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// ├── runtime-upgraded.json one-shot notice written by the daemon's
|
|
19
19
|
// │ background updater, consumed (printed + deleted)
|
|
20
20
|
// │ by the next interactive CLI run
|
|
21
|
+
// ├── cli-update.json cached standalone CLI release check + notice state
|
|
21
22
|
// ├── automatic-cleanup-v1.json completion marker for the one-time safe
|
|
22
23
|
// │ cleanup introduced for legacy unbounded data
|
|
23
24
|
// └── config.json user prefs: update channel, cdn origin override,
|
|
@@ -33,6 +34,7 @@ export const ACTIVE_RUNTIME_FILE = 'active'
|
|
|
33
34
|
export const DAEMON_LOCKFILE = 'daemon.json'
|
|
34
35
|
export const DEV_BRIDGES_DIR = 'dev-bridges'
|
|
35
36
|
export const CONFIG_FILE = 'config.json'
|
|
37
|
+
export const CLI_UPDATE_FILE = 'cli-update.json'
|
|
36
38
|
export const DAEMON_HEARTBEAT_STALE_MS = 30_000
|
|
37
39
|
|
|
38
40
|
export function rnxHomeDir(): string {
|
|
@@ -93,8 +95,8 @@ export function isDevWorkstation(): boolean {
|
|
|
93
95
|
// the persistent launchd / systemd daemon must never auto-install or run on a
|
|
94
96
|
// dev checkout or a dev workstation. either makes the background agent point at
|
|
95
97
|
// moving workspace artifacts / dev env and serve stale assets that shadow the
|
|
96
|
-
// live dev stack. setup and the launchd-spawned `
|
|
97
|
-
// this; a foreground `
|
|
98
|
+
// live dev stack. setup and the launchd-spawned `rnx serve` boot consult
|
|
99
|
+
// this; a foreground `rnx serve` the dev starts
|
|
98
100
|
// by hand is intentional and stays allowed.
|
|
99
101
|
export function shouldSkipPersistentDaemon(): boolean {
|
|
100
102
|
return isSootsimDevCheckout() || isDevWorkstation()
|
|
@@ -176,6 +178,10 @@ export function configFilePath(): string {
|
|
|
176
178
|
return path.join(rnxHomeDir(), CONFIG_FILE)
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
export function cliUpdateFilePath(): string {
|
|
182
|
+
return path.join(rnxHomeDir(), CLI_UPDATE_FILE)
|
|
183
|
+
}
|
|
184
|
+
|
|
179
185
|
// --- shared rnx config ---------------------------------------------------
|
|
180
186
|
//
|
|
181
187
|
// ~/.rnx/config.json is the single user-level config file every rnx
|
|
@@ -291,7 +297,7 @@ export function readOrCreateAnonymousId(): string {
|
|
|
291
297
|
// the only place its "updated to vX" line lands is the daemon's own stderr,
|
|
292
298
|
// which nobody reads. it also drops a one-shot notice file here; the next
|
|
293
299
|
// interactive CLI invocation prints it once and deletes it, so users learn
|
|
294
|
-
// their engine moved without having to run `
|
|
300
|
+
// their engine moved without having to run `rnx version`.
|
|
295
301
|
|
|
296
302
|
export interface RuntimeUpgradeNotice {
|
|
297
303
|
from: string | null
|
package/src/host/agent-host.ts
CHANGED
|
@@ -67,7 +67,7 @@ interface PendingPromptEcho {
|
|
|
67
67
|
export interface AgentHostOptions {
|
|
68
68
|
/** callback so hosts with a Contrast-env context (electron, dev middleware)
|
|
69
69
|
* can inject their own exclude list. defaults to the env-derived list
|
|
70
|
-
* the rest of the repo uses, so `
|
|
70
|
+
* the rest of the repo uses, so `rnx serve` standalone does the
|
|
71
71
|
* right thing. */
|
|
72
72
|
getExcludePorts?: () => number[]
|
|
73
73
|
/** returns the active CLI lease for a sim. used only for prompt routing to
|