peachsim 0.1.527
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/LICENSE +22 -0
- package/README.md +640 -0
- package/cli/app-config.ts +82 -0
- package/cli/app-fonts.ts +388 -0
- package/cli/app-project.ts +59 -0
- package/cli/app-splash.ts +171 -0
- package/cli/app-state-reset.ts +26 -0
- package/cli/auth.ts +184 -0
- package/cli/bin.ts +59 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2806 -0
- package/cli/browser-backend.ts +44 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/cli-update.ts +261 -0
- package/cli/cloud-client.ts +1530 -0
- package/cli/cloud-dispatch.ts +206 -0
- package/cli/cloud-session.ts +227 -0
- package/cli/command-contract.ts +221 -0
- package/cli/command-registry.ts +304 -0
- package/cli/commands/agent-wrapper.ts +1048 -0
- package/cli/commands/agent.ts +439 -0
- package/cli/commands/app-fonts.ts +99 -0
- package/cli/commands/assert.ts +542 -0
- package/cli/commands/auth.ts +71 -0
- package/cli/commands/box/bundle-plane.ts +71 -0
- package/cli/commands/box/checkout-plane.ts +256 -0
- package/cli/commands/box/plane-share.ts +208 -0
- package/cli/commands/box/rnx-command.ts +60 -0
- package/cli/commands/box/search-command.ts +157 -0
- package/cli/commands/box.ts +925 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +176 -0
- package/cli/commands/config.ts +34 -0
- package/cli/commands/control.ts +2154 -0
- package/cli/commands/cpu-profile.ts +941 -0
- package/cli/commands/daemon-mac-app.ts +165 -0
- package/cli/commands/daemon.ts +800 -0
- package/cli/commands/debug.ts +751 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +201 -0
- package/cli/commands/detox.ts +481 -0
- package/cli/commands/device.ts +134 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/do-chain.ts +408 -0
- package/cli/commands/electron.ts +94 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1240 -0
- package/cli/commands/inspect/actions.ts +516 -0
- package/cli/commands/inspect/core.ts +2086 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +119 -0
- package/cli/commands/inspect/env.ts +25 -0
- package/cli/commands/inspect/find.ts +169 -0
- package/cli/commands/inspect/get-layout.ts +53 -0
- package/cli/commands/inspect/keyboard.ts +54 -0
- package/cli/commands/inspect/list.ts +175 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +77 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +32 -0
- package/cli/commands/inspect/settling.ts +197 -0
- package/cli/commands/inspect/shared.ts +259 -0
- package/cli/commands/inspect/sleep.ts +15 -0
- package/cli/commands/inspect/tree.ts +23 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +211 -0
- package/cli/commands/inspect/wait-idle.ts +25 -0
- package/cli/commands/inspect/wait-ready.ts +87 -0
- package/cli/commands/inspect/wait-selector.ts +59 -0
- package/cli/commands/inspect/wait.ts +32 -0
- package/cli/commands/inspect.ts +4738 -0
- package/cli/commands/install-cli.ts +275 -0
- package/cli/commands/install-desktop.ts +385 -0
- package/cli/commands/login.ts +357 -0
- package/cli/commands/logout.ts +24 -0
- package/cli/commands/maestro-generate.ts +375 -0
- package/cli/commands/maestro.ts +536 -0
- package/cli/commands/mode.ts +58 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +67 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/platform.ts +410 -0
- package/cli/commands/preview.ts +103 -0
- package/cli/commands/profile.ts +106 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1435 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +89 -0
- package/cli/commands/runtime.ts +348 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +144 -0
- package/cli/commands/screenshot-mode.ts +38 -0
- package/cli/commands/screenshot.ts +492 -0
- package/cli/commands/screenshots-capture.ts +608 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +139 -0
- package/cli/commands/setup.ts +416 -0
- package/cli/commands/shell-boolean-mode.ts +82 -0
- package/cli/commands/skills.ts +468 -0
- package/cli/commands/slides.ts +357 -0
- package/cli/commands/state.ts +94 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +55 -0
- package/cli/commands/test.ts +535 -0
- package/cli/commands/three-mode.ts +771 -0
- package/cli/commands/timeline.ts +121 -0
- package/cli/commands/upgrade.ts +167 -0
- package/cli/commands/upload.ts +1203 -0
- package/cli/commands/version.ts +63 -0
- package/cli/commands/welcome.ts +17 -0
- package/cli/commands/what-happened.ts +328 -0
- package/cli/current-sim.ts +217 -0
- package/cli/desktop-companion.ts +315 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +184 -0
- package/cli/drivers/playwright-sim-host.ts +537 -0
- package/cli/drivers/playwright.ts +209 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-export.ts +55 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +126 -0
- package/cli/flow-session.ts +140 -0
- package/cli/help-core.ts +143 -0
- package/cli/help.ts +44 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +214 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/internal-child.ts +176 -0
- package/cli/maestro-js.ts +323 -0
- package/cli/main.ts +704 -0
- package/cli/open-url.ts +8 -0
- package/cli/outbound-endpoints.ts +830 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +189 -0
- package/cli/privacy.ts +53 -0
- package/cli/prompt.ts +56 -0
- package/cli/recording-access.ts +108 -0
- package/cli/registry.ts +2 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/rnx-in-process.ts +98 -0
- package/cli/run-registry.ts +226 -0
- package/cli/run-rnx-core.ts +148 -0
- package/cli/run-rnx.ts +137 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/secure-file.ts +31 -0
- package/cli/self-invocation.ts +58 -0
- package/cli/send-to-box.ts +109 -0
- package/cli/setup-repository.ts +191 -0
- package/cli/shell-init.ts +98 -0
- package/cli/standalone.ts +4 -0
- package/cli/telemetry.ts +84 -0
- package/cli/ui.ts +68 -0
- package/cli/ws-bridge.ts +1020 -0
- package/detox/attempt-events.cjs +23 -0
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +48 -0
- package/detox/expectations.ts +534 -0
- package/detox/gestures.ts +501 -0
- package/detox/index.ts +2694 -0
- package/detox/jest-preset.cjs +29 -0
- package/detox/jest-setup-after-env.cjs +22 -0
- package/detox/matchers.ts +30 -0
- package/detox/motion-change.cjs +109 -0
- package/detox/motion-change.d.cts +35 -0
- package/detox/navigation-lifecycle.ts +290 -0
- package/detox/navigation.ts +43 -0
- package/detox/proof-frame.cjs +114 -0
- package/detox/record-block-execution.cjs +78 -0
- package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
- package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
- package/detox/screenshots/color-test-rest-norngh.png +0 -0
- package/detox/screenshots/theme-blue-toggle.png +0 -0
- package/detox/screenshots/theme-blue.png +0 -0
- package/detox/screenshots/theme-red-toggle.png +0 -0
- package/detox/screenshots/theme-red.png +0 -0
- package/dist-lib/agent-daemon-client.cjs +502 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-identity.cjs +53 -0
- package/dist-lib/agent-sessions.cjs +759 -0
- package/dist-lib/attached-projects.cjs +526 -0
- package/dist-lib/auth/shared-session.cjs +208 -0
- package/dist-lib/backend-origin.cjs +88 -0
- package/dist-lib/beta.cjs +41 -0
- package/dist-lib/beta.mjs +13 -0
- package/dist-lib/bridge-constants.cjs +161 -0
- package/dist-lib/bridge-contract-input.cjs +451 -0
- package/dist-lib/bridge-contract-input.mjs +414 -0
- package/dist-lib/bridge-contract.cjs +130 -0
- package/dist-lib/bridge-contract.mjs +94 -0
- package/dist-lib/capture-contract.cjs +220 -0
- package/dist-lib/capture-contract.mjs +190 -0
- package/dist-lib/cli-constants.cjs +32 -0
- package/dist-lib/cloud-contract.cjs +194 -0
- package/dist-lib/cloud-contract.mjs +145 -0
- package/dist-lib/cloud.cjs +670 -0
- package/dist-lib/cloud.mjs +639 -0
- package/dist-lib/config.cjs +96 -0
- package/dist-lib/detox/index.cjs +3789 -0
- package/dist-lib/detox/jest-preset.cjs +29 -0
- package/dist-lib/dev-bundle-resolution.cjs +290 -0
- package/dist-lib/home-paths.cjs +638 -0
- package/dist-lib/host/bridge-host.cjs +10903 -0
- package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
- package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
- package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
- package/dist-lib/host/replacement-module-handler.cjs +469 -0
- package/dist-lib/host/websocket-proxy.cjs +298 -0
- package/dist-lib/index.cjs +4268 -0
- package/dist-lib/jump-to-source-babel.cjs +93 -0
- package/dist-lib/jump-to-source-native.cjs +112 -0
- package/dist-lib/menu.cjs +646 -0
- package/dist-lib/menu.mjs +604 -0
- package/dist-lib/metro-fingerprint-registry.cjs +186 -0
- package/dist-lib/metro-fingerprint-registry.mjs +156 -0
- package/dist-lib/metro-production-bundle.cjs +329 -0
- package/dist-lib/metro-production-bundle.mjs +295 -0
- package/dist-lib/metro.cjs +904 -0
- package/dist-lib/profiles.cjs +246 -0
- package/dist-lib/public-brand.cjs +39 -0
- package/dist-lib/react-native-host-modules.cjs +51 -0
- package/dist-lib/react-native-host-modules.mjs +25 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
- package/dist-lib/sdk.cjs +2433 -0
- package/dist-lib/sdk.mjs +2336 -0
- package/dist-lib/skills.cjs +39132 -0
- package/dist-lib/swift-submit.cjs +92 -0
- package/dist-lib/swift-submit.mjs +65 -0
- package/dist-lib/swift.cjs +1646 -0
- package/dist-lib/vite.cjs +1591 -0
- package/package.json +290 -0
- package/scripts/dev-server-scanner.ts +1291 -0
- package/scripts/optional-demo-registry.ts +113 -0
- package/skills/contrast/SKILL.md +165 -0
- package/skills/rnx-debug/SKILL.md +433 -0
- package/skills/rnx-perf/SKILL.md +399 -0
- package/skills/rnx-setup/SKILL.md +172 -0
- package/skills/rnx-test/SKILL.md +321 -0
- package/skills/rnx-visual/SKILL.md +126 -0
- package/src/agent-daemon-client.ts +410 -0
- package/src/agent-events.ts +71 -0
- package/src/agent-identity.ts +23 -0
- package/src/agent-prompt.ts +71 -0
- package/src/agent-sessions.ts +580 -0
- package/src/app-url.ts +39 -0
- package/src/attached-projects.ts +539 -0
- package/src/auth/shared-session.ts +235 -0
- package/src/backend-origin.ts +70 -0
- package/src/beta.ts +19 -0
- package/src/bridge-constants.ts +175 -0
- package/src/bridge-contract-input.ts +502 -0
- package/src/bridge-contract.ts +558 -0
- package/src/browser-cache.ts +16 -0
- package/src/capture-contract.ts +587 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +40 -0
- package/src/cloud-contract.ts +274 -0
- package/src/cloud.ts +762 -0
- package/src/codex-client.ts +234 -0
- package/src/config.ts +132 -0
- package/src/connect.ts +101 -0
- package/src/dev-bundle-resolution.ts +346 -0
- package/src/dev-server-open.ts +198 -0
- package/src/disk-cleanup.ts +540 -0
- package/src/flow-timeout-scale.ts +15 -0
- package/src/home-paths.ts +806 -0
- package/src/host/agent-host.ts +666 -0
- package/src/host/bridge-host.ts +2788 -0
- package/src/host/fetch-proxy-handler.ts +449 -0
- package/src/host/fetch-proxy-overrides.ts +51 -0
- package/src/host/open-url.ts +89 -0
- package/src/host/plane-host.ts +203 -0
- package/src/host/replacement-module-handler.ts +158 -0
- package/src/host/websocket-proxy.ts +315 -0
- package/src/index.ts +71 -0
- package/src/jump-to-source-babel.ts +135 -0
- package/src/jump-to-source-native.ts +102 -0
- package/src/menu.ts +777 -0
- package/src/metro-fingerprint-registry.ts +302 -0
- package/src/metro-plugin.ts +341 -0
- package/src/metro-production-bundle.ts +471 -0
- package/src/native-dev-bundle-url.ts +118 -0
- package/src/native-seam-manifest.ts +341 -0
- package/src/playwright-host-log.ts +16 -0
- package/src/plist.ts +27 -0
- package/src/poll-until.ts +123 -0
- package/src/profiles.ts +179 -0
- package/src/public-brand.ts +9 -0
- package/src/react-native-host-modules.ts +26 -0
- package/src/render-mode.ts +27 -0
- package/src/rolldown-plugin-swift.ts +240 -0
- package/src/runtime-assets.ts +233 -0
- package/src/runtime-delivery.ts +54 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +514 -0
- package/src/screenshots/orchestrate.ts +256 -0
- package/src/screenshots/org-deck.ts +91 -0
- package/src/screenshots/registry.ts +58 -0
- package/src/screenshots/schema.ts +364 -0
- package/src/sdk.ts +15 -0
- package/src/settings-bundle.ts +418 -0
- package/src/shell-bridge-identity.ts +15 -0
- package/src/sim-client.ts +697 -0
- package/src/skills/builtin/a11y-review.ts +132 -0
- package/src/skills/builtin/compat-check.ts +71 -0
- package/src/skills/builtin/maestro-test.ts +121 -0
- package/src/skills/builtin/perf-profile.ts +90 -0
- package/src/skills/builtin/screenshot-all.ts +52 -0
- package/src/skills/builtin/visual-diff.ts +100 -0
- package/src/skills/registry.ts +107 -0
- package/src/skills/types.ts +41 -0
- package/src/swift-submit.ts +185 -0
- package/src/swift.ts +7 -0
- package/src/team-machine-prompt.ts +73 -0
- package/src/vite-plugin-one.ts +264 -0
- package/src/vite-plugin-swift.ts +620 -0
- package/src/vite-plugin.ts +1946 -0
- package/src/worklets-babel.ts +250 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// plain-language Maestro authoring for `rnx maestro generate`.
|
|
2
|
+
|
|
3
|
+
import { mkdtempSync, writeFileSync } from 'fs'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import yaml from 'yaml'
|
|
7
|
+
import { authHeaderValue, resolveCliAuth } from '../auth'
|
|
8
|
+
import { registerRun, stepSummaryFromTrace } from '../run-registry'
|
|
9
|
+
import { createBridgeFromParsed, parseBridgeCliArgs } from '../ws-bridge'
|
|
10
|
+
import {
|
|
11
|
+
findUnexpectedFlowArgs,
|
|
12
|
+
FLOW_ARG_VALUE_FLAGS,
|
|
13
|
+
FLOW_BRIDGE_ARG_OPTIONS,
|
|
14
|
+
getLastFlowPreviewUploadResult,
|
|
15
|
+
getLastFlowTraceSteps,
|
|
16
|
+
runFlowPlayback,
|
|
17
|
+
type UnexpectedFlowArg,
|
|
18
|
+
} from './flow'
|
|
19
|
+
import { inspectDescribe } from './inspect/core'
|
|
20
|
+
import { resolveDefaultUploadOrigin } from './upload'
|
|
21
|
+
|
|
22
|
+
const DEFAULT_TEST_MODEL = process.env.RNX_TEST_MODEL || 'deepseek-v4-flash'
|
|
23
|
+
const DESCRIBE_CONTEXT_LIMIT = 18_000
|
|
24
|
+
const LLM_TIMEOUT_MS = 180_000
|
|
25
|
+
|
|
26
|
+
function flagValue(args: string[], name: string): string | undefined {
|
|
27
|
+
const index = args.indexOf(name)
|
|
28
|
+
return index >= 0 ? args[index + 1] : undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function flagValueAny(args: string[], names: string[]): string | undefined {
|
|
32
|
+
for (const name of names) {
|
|
33
|
+
const value = flagValue(args, name)
|
|
34
|
+
if (value) return value
|
|
35
|
+
}
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// generate is a front-end for runFlowPlayback: it inspects the sim, asks the
|
|
40
|
+
// model for a flow, then hands the argv down. these are the only flags it reads
|
|
41
|
+
// itself; everything else belongs to the runner and is forwarded verbatim. this
|
|
42
|
+
// file used to keep its own allowlist of what to forward, which silently
|
|
43
|
+
// dropped --proof, --record, --profile, --screenshots, --out and --base-url
|
|
44
|
+
// while `maestro test` honoured all six.
|
|
45
|
+
const GENERATE_VALUE_FLAGS = ['--origin', '--preview-origin', '--public-origin']
|
|
46
|
+
|
|
47
|
+
const VALUE_FLAGS = new Set([...GENERATE_VALUE_FLAGS, ...FLOW_ARG_VALUE_FLAGS])
|
|
48
|
+
|
|
49
|
+
export function findPrompt(args: string[]): { prompt: string; index: number } | null {
|
|
50
|
+
for (let i = 0; i < args.length; i++) {
|
|
51
|
+
const arg = args[i]
|
|
52
|
+
if (!arg) continue
|
|
53
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
54
|
+
i += 1
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
if (arg.startsWith('-')) continue
|
|
58
|
+
return { prompt: arg, index: i }
|
|
59
|
+
}
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function stripPromptForBridge(args: string[], promptIndex: number): string[] {
|
|
64
|
+
return args.filter((_, index) => index !== promptIndex)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function describeCurrentSim(args: string[], promptIndex: number): Promise<string> {
|
|
68
|
+
const bridgeArgs = stripPromptForBridge(args, promptIndex)
|
|
69
|
+
const parsedBridgeArgs = parseBridgeCliArgs(bridgeArgs, {
|
|
70
|
+
stripBooleanFlags: FLOW_BRIDGE_ARG_OPTIONS.stripBooleanFlags,
|
|
71
|
+
stripValueFlags: [
|
|
72
|
+
...GENERATE_VALUE_FLAGS,
|
|
73
|
+
...FLOW_BRIDGE_ARG_OPTIONS.stripValueFlags,
|
|
74
|
+
],
|
|
75
|
+
})
|
|
76
|
+
const bridge = createBridgeFromParsed(parsedBridgeArgs)
|
|
77
|
+
try {
|
|
78
|
+
const describe = await inspectDescribe(bridge, {
|
|
79
|
+
describe: true,
|
|
80
|
+
verbose: false,
|
|
81
|
+
filter: '',
|
|
82
|
+
compact: true,
|
|
83
|
+
hideXy: true,
|
|
84
|
+
})
|
|
85
|
+
const parts = [
|
|
86
|
+
typeof describe.nodeCount === 'number' ? `nodes: ${describe.nodeCount}` : '',
|
|
87
|
+
describe.tree || '',
|
|
88
|
+
].filter(Boolean)
|
|
89
|
+
const text = parts.join('\n')
|
|
90
|
+
return text.length > DESCRIBE_CONTEXT_LIMIT
|
|
91
|
+
? text.slice(0, DESCRIBE_CONTEXT_LIMIT) + '\n[tree truncated]'
|
|
92
|
+
: text
|
|
93
|
+
} finally {
|
|
94
|
+
bridge.close()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const RNX_TEST_SYSTEM_PROMPT = `
|
|
99
|
+
You write Maestro-compatible rnx YAML flows from a user's plain-language test goal.
|
|
100
|
+
Return only JSON, no markdown, with this exact shape:
|
|
101
|
+
{"summary":"short human-readable result goal","steps":[{"waitFor":{"text":"...","timeout":10000}}]}
|
|
102
|
+
|
|
103
|
+
Use only these flow commands:
|
|
104
|
+
- waitFor: { "text"?: string, "id"?: string, "timeout"?: number }
|
|
105
|
+
- assertVisible: string or { "text"?: string, "id"?: string }
|
|
106
|
+
- assertNotVisible: string or { "text"?: string, "id"?: string }
|
|
107
|
+
- tapOn: string or { "text"?: string, "id"?: string, "index"?: number }
|
|
108
|
+
- inputText: string
|
|
109
|
+
- pressKey: string
|
|
110
|
+
- hideKeyboard: true
|
|
111
|
+
- swipe: { "direction": "UP" | "DOWN" | "LEFT" | "RIGHT", "duration"?: number }
|
|
112
|
+
- waitForAnimationToEnd: true
|
|
113
|
+
- takeScreenshot: "label"
|
|
114
|
+
|
|
115
|
+
Prefer ids/testIDs from the tree. Prefer visible text when no id exists.
|
|
116
|
+
Do not invent app-specific data that is not visible. Keep the flow short.
|
|
117
|
+
End with an assertion or wait that proves the user's goal.
|
|
118
|
+
`.trim()
|
|
119
|
+
|
|
120
|
+
function buildPromptRequest(prompt: string, describe: string): string {
|
|
121
|
+
return `
|
|
122
|
+
User goal:
|
|
123
|
+
${prompt}
|
|
124
|
+
|
|
125
|
+
Current rnx visible tree:
|
|
126
|
+
${describe || '[no tree available]'}
|
|
127
|
+
|
|
128
|
+
Generate the shortest replayable flow that verifies the goal from this current state.
|
|
129
|
+
`.trim()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function generateFlowJson(args: {
|
|
133
|
+
prompt: string
|
|
134
|
+
describe: string
|
|
135
|
+
origin: string
|
|
136
|
+
authHeader: string
|
|
137
|
+
}): Promise<string> {
|
|
138
|
+
const res = await fetch(`${args.origin.replace(/\/$/, '')}/api/llm`, {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers: {
|
|
141
|
+
'content-type': 'application/json',
|
|
142
|
+
authorization: args.authHeader,
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
modelId: DEFAULT_TEST_MODEL,
|
|
146
|
+
context: {
|
|
147
|
+
systemPrompt: RNX_TEST_SYSTEM_PROMPT,
|
|
148
|
+
messages: [
|
|
149
|
+
{
|
|
150
|
+
role: 'user',
|
|
151
|
+
content: buildPromptRequest(args.prompt, args.describe),
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
options: { maxTokens: 1800, temperature: 0.2 },
|
|
157
|
+
threadId: null,
|
|
158
|
+
}),
|
|
159
|
+
signal: AbortSignal.timeout(LLM_TIMEOUT_MS),
|
|
160
|
+
})
|
|
161
|
+
if (!res.ok || !res.body) {
|
|
162
|
+
const text = await res.text().catch(() => '')
|
|
163
|
+
throw new Error(`/api/llm ${res.status}${text ? `: ${text}` : ''}`)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const reader = res.body.getReader()
|
|
167
|
+
const decoder = new TextDecoder()
|
|
168
|
+
const deltas: string[] = []
|
|
169
|
+
let buffer = ''
|
|
170
|
+
// /api/llm speaks pi-agent-core's proxy event protocol. text events carry
|
|
171
|
+
// visible deltas; transport diagnostics and keepalives are ignored here.
|
|
172
|
+
const handleLine = (line: string) => {
|
|
173
|
+
const trimmed = line.trim()
|
|
174
|
+
if (!trimmed) return
|
|
175
|
+
const event = JSON.parse(trimmed) as {
|
|
176
|
+
type?: string
|
|
177
|
+
delta?: string
|
|
178
|
+
errorMessage?: string
|
|
179
|
+
}
|
|
180
|
+
if (event.type === 'text_delta' && event.delta) {
|
|
181
|
+
deltas.push(event.delta)
|
|
182
|
+
} else if (event.type === 'error') {
|
|
183
|
+
throw new Error(event.errorMessage || 'llm error')
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (;;) {
|
|
188
|
+
const { value, done } = await reader.read()
|
|
189
|
+
if (done) break
|
|
190
|
+
buffer += decoder.decode(value, { stream: true })
|
|
191
|
+
let newline: number
|
|
192
|
+
while ((newline = buffer.indexOf('\n')) >= 0) {
|
|
193
|
+
handleLine(buffer.slice(0, newline))
|
|
194
|
+
buffer = buffer.slice(newline + 1)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
buffer += decoder.decode()
|
|
198
|
+
if (buffer.trim()) handleLine(buffer)
|
|
199
|
+
|
|
200
|
+
const text = deltas.join('').trim()
|
|
201
|
+
if (!text) throw new Error('llm returned an empty test flow')
|
|
202
|
+
return text
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function extractJson(text: string): unknown {
|
|
206
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)
|
|
207
|
+
const candidate = fenced
|
|
208
|
+
? fenced[1]
|
|
209
|
+
: text.slice(text.indexOf('{'), text.lastIndexOf('}') + 1)
|
|
210
|
+
return JSON.parse(candidate)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
214
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseGeneratedFlow(text: string): {
|
|
218
|
+
summary: string
|
|
219
|
+
steps: Record<string, unknown>[]
|
|
220
|
+
yamlText: string
|
|
221
|
+
} {
|
|
222
|
+
const parsed = extractJson(text)
|
|
223
|
+
if (!isRecord(parsed)) throw new Error('llm did not return a JSON object')
|
|
224
|
+
const summary =
|
|
225
|
+
typeof parsed.summary === 'string' && parsed.summary.trim()
|
|
226
|
+
? parsed.summary.trim().slice(0, 500)
|
|
227
|
+
: 'rnx generated test'
|
|
228
|
+
const rawSteps = parsed.steps
|
|
229
|
+
if (!Array.isArray(rawSteps) || rawSteps.length === 0) {
|
|
230
|
+
throw new Error('llm returned no flow steps')
|
|
231
|
+
}
|
|
232
|
+
const steps = rawSteps.map((step, index) => {
|
|
233
|
+
if (!isRecord(step)) throw new Error(`flow step ${index + 1} is not an object`)
|
|
234
|
+
return step
|
|
235
|
+
})
|
|
236
|
+
return { summary, steps, yamlText: yaml.stringify(steps) }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function buildFlowArgs(
|
|
240
|
+
args: string[],
|
|
241
|
+
promptIndex: number,
|
|
242
|
+
flowPath: string,
|
|
243
|
+
origin: string,
|
|
244
|
+
) {
|
|
245
|
+
const out = [
|
|
246
|
+
flowPath,
|
|
247
|
+
'--preview',
|
|
248
|
+
'--preview-origin',
|
|
249
|
+
origin,
|
|
250
|
+
'--billing-kind',
|
|
251
|
+
'test_run',
|
|
252
|
+
]
|
|
253
|
+
for (let i = 0; i < args.length; i++) {
|
|
254
|
+
if (i === promptIndex) continue
|
|
255
|
+
const arg = args[i]
|
|
256
|
+
if (!arg) continue
|
|
257
|
+
if (arg === '--origin' || arg === '--preview-origin') {
|
|
258
|
+
i += 1
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
if (arg === '--public-origin') {
|
|
262
|
+
const value = args[i + 1]
|
|
263
|
+
if (value) out.push('--preview-public-origin', value)
|
|
264
|
+
i += 1
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
267
|
+
out.push(arg)
|
|
268
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
269
|
+
const value = args[i + 1]
|
|
270
|
+
if (value !== undefined) out.push(value)
|
|
271
|
+
i += 1
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return out
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// an argument nothing reads is an argument that silently changes what the run
|
|
278
|
+
// does, and generate hands its argv to runFlowPlayback, which is the end of the
|
|
279
|
+
// line for argv. so whatever survives buildFlowArgs is checked against the
|
|
280
|
+
// runner's own vocabulary, before the sim inspection and the model call rather
|
|
281
|
+
// than after them.
|
|
282
|
+
export function findUnexpectedGenerateArgs(
|
|
283
|
+
args: string[],
|
|
284
|
+
promptIndex: number,
|
|
285
|
+
): UnexpectedFlowArg[] {
|
|
286
|
+
const problems: UnexpectedFlowArg[] = []
|
|
287
|
+
for (let i = 0; i < args.length; i++) {
|
|
288
|
+
if (i === promptIndex) continue
|
|
289
|
+
const arg = args[i]
|
|
290
|
+
if (!arg) continue
|
|
291
|
+
// generate writes --billing-kind onto the flow argv itself and getFlag
|
|
292
|
+
// takes the first occurrence, so a caller's value loses to it in silence.
|
|
293
|
+
if (arg === '--billing-kind') {
|
|
294
|
+
problems.push({ arg, message: `${arg} is set by rnx maestro generate` })
|
|
295
|
+
i += 1
|
|
296
|
+
continue
|
|
297
|
+
}
|
|
298
|
+
if (!GENERATE_VALUE_FLAGS.includes(arg)) continue
|
|
299
|
+
const value = args[i + 1]
|
|
300
|
+
if (value === undefined || value.startsWith('-')) {
|
|
301
|
+
problems.push({ arg, message: `${arg} expects a value` })
|
|
302
|
+
}
|
|
303
|
+
i += 1
|
|
304
|
+
}
|
|
305
|
+
return [
|
|
306
|
+
...problems,
|
|
307
|
+
...findUnexpectedFlowArgs(buildFlowArgs(args, promptIndex, '<flow>', '<origin>')),
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function runMaestroGenerate(args: string[]): Promise<number> {
|
|
312
|
+
const promptArg = findPrompt(args)
|
|
313
|
+
if (!promptArg) {
|
|
314
|
+
console.error(' usage: rnx maestro generate "test goal"')
|
|
315
|
+
return 1
|
|
316
|
+
}
|
|
317
|
+
const { prompt, index: promptIndex } = promptArg
|
|
318
|
+
const unexpected = findUnexpectedGenerateArgs(args, promptIndex)
|
|
319
|
+
if (unexpected.length > 0) {
|
|
320
|
+
for (const problem of unexpected) {
|
|
321
|
+
console.error(` error: ${problem.message}`)
|
|
322
|
+
}
|
|
323
|
+
console.error('\n run `rnx maestro --help` for the flags this command accepts')
|
|
324
|
+
return 1
|
|
325
|
+
}
|
|
326
|
+
const cliAuth = resolveCliAuth()
|
|
327
|
+
if (!cliAuth || cliAuth.kind === 'github') {
|
|
328
|
+
console.error(' rnx maestro generate "<goal>" needs a login or RNX_API_KEY.')
|
|
329
|
+
console.error(' API keys must include preview_upload and llm scopes.')
|
|
330
|
+
return 1
|
|
331
|
+
}
|
|
332
|
+
const authHeader = authHeaderValue(cliAuth)
|
|
333
|
+
const origin = await resolveDefaultUploadOrigin(
|
|
334
|
+
flagValueAny(args, ['--origin', '--preview-origin']),
|
|
335
|
+
)
|
|
336
|
+
console.log(' inspecting current sim…')
|
|
337
|
+
const describe = await describeCurrentSim(args, promptIndex)
|
|
338
|
+
console.log(' generating Maestro flow…')
|
|
339
|
+
const generated = await generateFlowJson({ prompt, describe, origin, authHeader })
|
|
340
|
+
const flow = parseGeneratedFlow(generated)
|
|
341
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'rnx-maestro-generate-'))
|
|
342
|
+
const flowPath = join(tempDir, 'flow.yaml')
|
|
343
|
+
writeFileSync(flowPath, flow.yamlText)
|
|
344
|
+
console.log(` flow: ${flowPath}`)
|
|
345
|
+
|
|
346
|
+
const startedAt = Date.now()
|
|
347
|
+
const flowArgs = buildFlowArgs(args, promptIndex, flowPath, origin)
|
|
348
|
+
const exitCode = await runFlowPlayback(flowArgs)
|
|
349
|
+
const upload = getLastFlowPreviewUploadResult()
|
|
350
|
+
const traceSteps = getLastFlowTraceSteps()
|
|
351
|
+
const failedStep = traceSteps.find((step) => step.status === 'failure')
|
|
352
|
+
const status = exitCode === 0 ? 'passed' : 'failed'
|
|
353
|
+
const run = await registerRun({
|
|
354
|
+
origin,
|
|
355
|
+
kind: 'maestro',
|
|
356
|
+
prompt,
|
|
357
|
+
summary: flow.summary,
|
|
358
|
+
status,
|
|
359
|
+
failureMessage:
|
|
360
|
+
status === 'failed' ? (failedStep?.error ?? 'flow playback failed') : null,
|
|
361
|
+
previewShareId: upload?.shareId ?? null,
|
|
362
|
+
owner: flagValue(args, '--owner') ?? null,
|
|
363
|
+
repo: flagValue(args, '--repo') ?? null,
|
|
364
|
+
durationMs: Date.now() - startedAt,
|
|
365
|
+
flowYamlSizeBytes: Buffer.byteLength(flow.yamlText, 'utf8'),
|
|
366
|
+
...stepSummaryFromTrace(traceSteps),
|
|
367
|
+
auth: cliAuth,
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
console.log(`\n result: ${status}`)
|
|
371
|
+
if (run) console.log(` run: ${run.id}`)
|
|
372
|
+
if (upload?.previewUrl) console.log(` preview: ${upload.previewUrl}`)
|
|
373
|
+
if (run?.traceUrl) console.log(` replay: ${run.traceUrl}`)
|
|
374
|
+
return exitCode
|
|
375
|
+
}
|