From 3e030fbbd931cb7f5862e13dfb1f2b454384f8ef Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 4 Jul 2026 13:05:24 +0000 Subject: [PATCH] =?UTF-8?q?feat(m3.5):=20AI=20analyze=20=E2=80=94=20determ?= =?UTF-8?q?inistic=20form=20fill=20+=20safety=20+=20bounded=20execute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `tron analyze` and wires the SDK page.analyze/step/runTask (PRD §11). Dry-run by default; --execute runs a bounded, safety-gated fill loop. tron analyze "Fill this contact form" --data ./lead.json tron analyze "..." --data ./lead.json --execute --no-submit tron analyze form --json packages/agent-runtime/src/analyze — deterministic, no LLM: - data/matching: flatten --data to dot-paths; match label/name/placeholder/ARIA to data with synonyms + confidence (values referenced by path, never echoed). - policy: risk classification — credential/payment/PII fields never auto-fill; submit needs --allow-submit; payment/irreversible submits blocked even then; CAPTCHA stops the loop. - form-script: in-page reader (refs from the snapshot's data-tron-ref, required, type, submit). forms/planner: build a form map + safe ordered plan; report missing required data and ambiguous mappings instead of guessing. - analyze: orchestration — dry-run returns the plan; execute fills low-risk fields then stops before the gated submit (bounded by --max-steps). - analyze-cli/-bin: `tron analyze` with --data (file/inline/stdin), --execute, --no-submit, --allow-submit, --policy, --json. Open-ended navigation goals return AI_PROVIDER_NOT_CONFIGURED (deterministic form path is complete; the BYOK/local planner is a follow-up). Wiring: install.sh routes `tron analyze` via a new generic bin launcher (tron-node.mjs) that resolves agent-runtime's @tronbrowser/* imports; build- release ships agent-runtime dist as analyze/; the SDK depends on agent-runtime and implements page.analyze/step/runTask; tron-run resolver maps agent-runtime. Tests (+38): matching/policy/data units, the in-page form reader (happy-dom), the analyze orchestration (dry-run map, missing data, CAPTCHA, high-risk field never filled, execute+no-submit, allow-submit, high-risk submit refused), and the CLI with fakes. Verified `tron analyze` end-to-end (dry-run + execute) via a WS CDP stub. Full workspace suite green in CI order. Co-Authored-By: Claude Opus 4.8 --- apps/desktop/launcher/tron-node.mjs | 26 ++++ apps/desktop/launcher/tron-run.mjs | 2 + apps/desktop/scripts/build-release.sh | 14 +- apps/web/public/install.sh | 9 ++ docs/analyze.md | 66 ++++++++ packages/agent-runtime/package.json | 4 + packages/agent-runtime/src/analyze-bin.ts | 13 ++ .../agent-runtime/src/analyze-cli.test.ts | 88 +++++++++++ packages/agent-runtime/src/analyze-cli.ts | 145 ++++++++++++++++++ .../agent-runtime/src/analyze/analyze.test.ts | 139 +++++++++++++++++ packages/agent-runtime/src/analyze/analyze.ts | 129 ++++++++++++++++ packages/agent-runtime/src/analyze/data.ts | 40 +++++ .../src/analyze/form-script.test.ts | 66 ++++++++ .../agent-runtime/src/analyze/form-script.ts | 86 +++++++++++ packages/agent-runtime/src/analyze/format.ts | 48 ++++++ packages/agent-runtime/src/analyze/forms.ts | 43 ++++++ packages/agent-runtime/src/analyze/index.ts | 13 ++ .../agent-runtime/src/analyze/matching.ts | 81 ++++++++++ packages/agent-runtime/src/analyze/planner.ts | 94 ++++++++++++ packages/agent-runtime/src/analyze/policy.ts | 70 +++++++++ packages/agent-runtime/src/analyze/types.ts | 94 ++++++++++++ .../agent-runtime/src/analyze/units.test.ts | 67 ++++++++ packages/agent-runtime/src/index.ts | 3 + packages/sdk/package.json | 1 + packages/sdk/src/index.ts | 9 ++ packages/sdk/src/page.ts | 43 ++++-- packages/sdk/src/sdk.test.ts | 9 +- pnpm-lock.yaml | 10 ++ 28 files changed, 1393 insertions(+), 19 deletions(-) create mode 100644 apps/desktop/launcher/tron-node.mjs create mode 100644 docs/analyze.md create mode 100644 packages/agent-runtime/src/analyze-bin.ts create mode 100644 packages/agent-runtime/src/analyze-cli.test.ts create mode 100644 packages/agent-runtime/src/analyze-cli.ts create mode 100644 packages/agent-runtime/src/analyze/analyze.test.ts create mode 100644 packages/agent-runtime/src/analyze/analyze.ts create mode 100644 packages/agent-runtime/src/analyze/data.ts create mode 100644 packages/agent-runtime/src/analyze/form-script.test.ts create mode 100644 packages/agent-runtime/src/analyze/form-script.ts create mode 100644 packages/agent-runtime/src/analyze/format.ts create mode 100644 packages/agent-runtime/src/analyze/forms.ts create mode 100644 packages/agent-runtime/src/analyze/index.ts create mode 100644 packages/agent-runtime/src/analyze/matching.ts create mode 100644 packages/agent-runtime/src/analyze/planner.ts create mode 100644 packages/agent-runtime/src/analyze/policy.ts create mode 100644 packages/agent-runtime/src/analyze/types.ts create mode 100644 packages/agent-runtime/src/analyze/units.test.ts diff --git a/apps/desktop/launcher/tron-node.mjs b/apps/desktop/launcher/tron-node.mjs new file mode 100644 index 0000000..06d56e0 --- /dev/null +++ b/apps/desktop/launcher/tron-node.mjs @@ -0,0 +1,26 @@ +// Generic launcher for shipped Node bins that import @tronbrowser/* packages by +// bare specifier (e.g. analyze-bin -> @tronbrowser/browser-core). Registers the +// resolver hook that maps those specifiers to the sibling dist trees in the +// launcher payload, then runs the target entry. +// +// node tron-node.mjs [args...] +import { register } from 'node:module'; +import { dirname, join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const here = dirname(fileURLToPath(import.meta.url)); +register(pathToFileURL(join(here, 'tron-run-hooks.mjs')), import.meta.url, { + data: { + '@tronbrowser/browser-core': pathToFileURL(join(here, 'automate', 'index.js')).href, + '@tronbrowser/agent-runtime': pathToFileURL(join(here, 'analyze', 'index.js')).href, + '@tronbrowser/sdk': pathToFileURL(join(here, 'sdk', 'index.js')).href, + }, +}); + +const entry = process.argv[2]; +if (!entry) { + process.stderr.write('tron-node: missing entry\n'); + process.exit(2); +} +process.argv = [process.argv[0], entry, ...process.argv.slice(3)]; +await import(pathToFileURL(entry).href); diff --git a/apps/desktop/launcher/tron-run.mjs b/apps/desktop/launcher/tron-run.mjs index 4111e3e..4c0bd9f 100644 --- a/apps/desktop/launcher/tron-run.mjs +++ b/apps/desktop/launcher/tron-run.mjs @@ -10,6 +10,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const sdkEntry = join(here, 'sdk', 'index.js'); const coreEntry = join(here, 'automate', 'index.js'); +const agentEntry = join(here, 'analyze', 'index.js'); const argv = process.argv.slice(2); let script; @@ -43,6 +44,7 @@ register(pathToFileURL(join(here, 'tron-run-hooks.mjs')), import.meta.url, { data: { '@tronbrowser/sdk': pathToFileURL(sdkEntry).href, '@tronbrowser/browser-core': pathToFileURL(coreEntry).href, + '@tronbrowser/agent-runtime': pathToFileURL(agentEntry).href, }, }); diff --git a/apps/desktop/scripts/build-release.sh b/apps/desktop/scripts/build-release.sh index b8a1304..98a50f1 100755 --- a/apps/desktop/scripts/build-release.sh +++ b/apps/desktop/scripts/build-release.sh @@ -61,15 +61,17 @@ stage_automation() { # dest dir local s="$1" command -v node >/dev/null 2>&1 && command -v pnpm >/dev/null 2>&1 || { echo " ! automation runtime skipped (needs node + pnpm)"; return; } - if ( cd "$REPO_ROOT" && pnpm --filter @tronbrowser/browser-core --filter @tronbrowser/sdk build >/dev/null 2>&1 ); then - rm -rf "$s/automate" "$s/sdk" + if ( cd "$REPO_ROOT" && pnpm --filter @tronbrowser/browser-core --filter @tronbrowser/agent-runtime --filter @tronbrowser/sdk build >/dev/null 2>&1 ); then + rm -rf "$s/automate" "$s/analyze" "$s/sdk" cp -R "$REPO_ROOT/packages/browser-core/dist" "$s/automate" printf '{\n "type": "module"\n}\n' > "$s/automate/package.json" + cp -R "$REPO_ROOT/packages/agent-runtime/dist" "$s/analyze" + printf '{\n "type": "module"\n}\n' > "$s/analyze/package.json" cp -R "$REPO_ROOT/packages/sdk/dist" "$s/sdk" printf '{\n "type": "module"\n}\n' > "$s/sdk/package.json" - echo " + bundled automation runtime + SDK (tron snapshot/extract/run)" + echo " + bundled automation + analyze + SDK (tron snapshot/extract/analyze/run)" else - echo " ! automation runtime skipped (browser-core/sdk build failed)" + echo " ! automation runtime skipped (browser-core/agent-runtime/sdk build failed)" fi } @@ -84,9 +86,11 @@ stage() { # dest dir # Managed-session engine for `tron browser …` / `tron open` (PRD M3.1). Sits # next to the shim; the `tron` dispatcher resolves it relative to $CURRENT. install -m 0755 "$DESKTOP/launcher/tron-session" "$s/tron-session" - # `tron run` launcher + ESM resolver hook (PRD M3.4). + # `tron run` launcher + ESM resolver hook (PRD M3.4) and the generic bin + # launcher used by `tron analyze` (PRD M3.5). install -m 0644 "$DESKTOP/launcher/tron-run.mjs" "$s/tron-run.mjs" install -m 0644 "$DESKTOP/launcher/tron-run-hooks.mjs" "$s/tron-run-hooks.mjs" + install -m 0644 "$DESKTOP/launcher/tron-node.mjs" "$s/tron-node.mjs" stage_automation "$s" # -L dereferences the branding symlinks (icons/logo.svg -> repo-root logo.svg) # so the package contains real files, not dangling links. diff --git a/apps/web/public/install.sh b/apps/web/public/install.sh index 94945fa..ecf2d94 100755 --- a/apps/web/public/install.sh +++ b/apps/web/public/install.sh @@ -91,6 +91,7 @@ Usage: tron screenshot

Save a PNG of the current page (--full-page) tron headless One-shot: --snapshot | --screenshot

| --extract tron run