@tangle-network/browser-agent-driver 0.16.1 → 0.18.0

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.
@@ -5,7 +5,7 @@
5
5
  * Uses Vercel AI SDK for multi-provider support (OpenAI, Anthropic, Google, Codex CLI, Claude Code).
6
6
  */
7
7
  import type { ModelMessage, LanguageModel } from 'ai';
8
- import type { Action, PageState, AgentConfig, DesignFinding, GoalVerification } from '../types.js';
8
+ import type { Action, PageState, AgentConfig, DesignFinding, GoalVerification, Plan } from '../types.js';
9
9
  export interface BrainDecision {
10
10
  action: Action;
11
11
  nextActions?: Action[];
@@ -190,6 +190,37 @@ export declare class Brain {
190
190
  }, options?: {
191
191
  forceVision?: boolean;
192
192
  }): Promise<BrainDecision>;
193
+ /**
194
+ * Gen 7: ONE LLM call generates a structured plan for the entire task.
195
+ *
196
+ * The runner executes the plan deterministically (no LLM between steps),
197
+ * falling back to per-action `decide()` only when verification fails.
198
+ * This is the architectural shift that breaks the "1 LLM call per
199
+ * action" assumption — a 9-action task becomes 1 plan call + 9
200
+ * deterministic executes instead of 9 LLM calls.
201
+ *
202
+ * Returns null when:
203
+ * - the LLM response is unparseable JSON (fall through to per-action)
204
+ * - the plan has zero steps
205
+ * - any plan step has an invalid/unknown action shape
206
+ *
207
+ * The caller (BrowserAgent.run) treats null as "planner unavailable,
208
+ * use per-action loop".
209
+ */
210
+ plan(goal: string, state: PageState, options?: {
211
+ maxSteps?: number;
212
+ extraContext?: string;
213
+ }): Promise<{
214
+ plan: Plan | null;
215
+ raw: string;
216
+ durationMs: number;
217
+ tokensUsed?: number;
218
+ inputTokens?: number;
219
+ outputTokens?: number;
220
+ cacheReadInputTokens?: number;
221
+ cacheCreationInputTokens?: number;
222
+ parseError?: string;
223
+ }>;
193
224
  /**
194
225
  * Evaluate quality of the current page state.
195
226
  * Takes a screenshot and asks the LLM to rate the visual quality,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/brain/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/brain/index.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,8 @@ ACTIONS:
27
27
  - {"action": "evaluate", "criteria": "Is the layout professional? Are colors consistent?"}
28
28
  - {"action": "runScript", "script": "document.querySelector('.count').textContent"} — run JS in page context and get the result. Use for reading content not in the a11y tree (canvas, computed styles, hidden state).
29
29
  - {"action": "verifyPreview"} — after the app builds, inspect the preview iframe. Returns URL, title, a11y tree, and errors. Use this AFTER you see a preview iframe on the page.
30
+ - {"action": "fill", "fields": {"@t1": "Jordan", "@t2": "Rivera"}, "selects": {"@s1": "WA"}, "checks": ["@c1", "@c2"]} — BATCH fill multiple form fields, dropdowns, and checkboxes in ONE turn. Use this whenever you can see 2+ form fields you need to fill — it's dramatically faster than per-field type/click. fields/selects/checks are all optional but at least one must be non-empty.
31
+ - {"action": "clickSequence", "refs": ["@r1", "@r2", "@r3"]} — click a known sequence of refs in order. Use for multi-step UI navigation chains where the click order is obvious from the page structure.
30
32
  - {"action": "complete", "result": "description of what was accomplished"}
31
33
  - {"action": "abort", "reason": "why you cannot continue"}
32
34
 
@@ -60,7 +62,11 @@ RULES:
60
62
  11. After the app builds and a preview is visible, use "verifyPreview" to check for errors before completing
61
63
  12. BLOCKER-FIRST POLICY: if a modal, limit, quota, permission, or error dialog blocks progress, resolve THAT first before continuing the main goal
62
64
  13. For quota/limit blockers, use an unblock ladder: open manage path -> clean up old test resources if needed -> retry the original action
63
- 14. If the same action triggers the same blocker twice, switch strategy immediately (different button/path), do not repeat blind retries`;
65
+ 14. If the same action triggers the same blocker twice, switch strategy immediately (different button/path), do not repeat blind retries
66
+ 15. BATCH FILL FOR MULTI-FIELD FORMS: when you can see 2+ form fields that need to be filled, ALWAYS use a single "fill" action with all the fields at once instead of multiple type/click turns. A 5-field form takes 1 turn with fill, not 10 turns with type. Same for dropdowns (use selects map) and checkboxes (use checks array). The page rarely cares which order fields are filled — batch them.
67
+ - CRITICAL: every key in fields/selects/checks MUST be an @ref taken VERBATIM from the ELEMENTS list (e.g., "@t1f2a"), or a simple [data-testid="..."] selector copied from the DATA-TESTID SELECTORS section. NEVER invent CSS combinators like "[data-testid=\"x\"] input" or "@refXXX child". If a target doesn't appear in the snapshot, use single-step type/click for it instead.
68
+ - Date inputs (type="date") and spinbuttons (year/month/day) typically need single-step "type" actions, NOT batch fill. They have non-text input behavior that confuses Playwright's fill(). Skip them in your batch and handle them with type after.
69
+ - If a batch fill fails, do NOT retry the same batch on the next turn. The error message will tell you which target failed — switch to single-step type/click for that target and shrink your next batch to just the targets that work.`;
64
70
  /** Search-related rules (15-17): injected when page has search elements or /search URL */
65
71
  const SEARCH_RULES = `
66
72
  15. SEARCH FORMS: Always interact with the form (type in search box, then click Search or press Enter). Do NOT navigate to a URL with search query parameters — many sites require form submission to trigger filtering. If a search yields no results, try the page's own search box rather than the site-wide search
@@ -91,7 +97,10 @@ EXAMPLE 1 — Multi-step form fill (use actual refs from ELEMENTS, not these pla
91
97
  {"plan":["Navigate to signup page","Fill email field","Fill password field","Click submit","Verify success"],"currentStep":1,"action":{"action":"type","selector":"@REF","text":"user@example.com"},"reasoning":"I see the signup form with email input [ref=...] and password input [ref=...]. Starting with email since it is the first required field.","expectedEffect":"Email field should show 'user@example.com'"}
92
98
 
93
99
  EXAMPLE 2 — Recovery after failure:
94
- {"plan":["Click the send button","Wait for response"],"currentStep":0,"action":{"action":"scroll","direction":"down","amount":300},"reasoning":"My last click failed because the element was not visible in the viewport. I can see from the screenshot that the send button is below the fold. Scrolling down to bring it into view before retrying.","expectedEffect":"The send button should become visible in the viewport"}`;
100
+ {"plan":["Click the send button","Wait for response"],"currentStep":0,"action":{"action":"scroll","direction":"down","amount":300},"reasoning":"My last click failed because the element was not visible in the viewport. I can see from the screenshot that the send button is below the fold. Scrolling down to bring it into view before retrying.","expectedEffect":"The send button should become visible in the viewport"}
101
+
102
+ EXAMPLE 3 — Batch fill a multi-field form (one turn instead of ten):
103
+ {"plan":["Fill all visible Personal Info fields","Click Next","Fill Contact step","Submit"],"currentStep":0,"action":{"action":"fill","fields":{"@firstname":"Jordan","@lastname":"Rivera","@dob":"1990-04-15"},"selects":{"@gender":"other"}},"reasoning":"Step 1 of the form has 3 text fields and 1 select all visible at once. Filling them in a single batch action saves 7 turns vs typing each individually.","expectedEffect":"All four Step 1 fields populated with the supplied values"}`;
95
104
  /** Full static prompt (all rules) — used as default when config.systemPrompt is not set */
96
105
  const SYSTEM_PROMPT = CORE_RULES + SEARCH_RULES + DATA_EXTRACTION_RULES + HEAVY_PAGE_RULES + REASONING_SUFFIX;
97
106
  /** Pattern for detecting data-extraction keywords in goal text */
@@ -1004,6 +1013,224 @@ ${visibleSnapshot}`;
1004
1013
  modelUsed: effectiveModel,
1005
1014
  };
1006
1015
  }
1016
+ /**
1017
+ * Gen 7: ONE LLM call generates a structured plan for the entire task.
1018
+ *
1019
+ * The runner executes the plan deterministically (no LLM between steps),
1020
+ * falling back to per-action `decide()` only when verification fails.
1021
+ * This is the architectural shift that breaks the "1 LLM call per
1022
+ * action" assumption — a 9-action task becomes 1 plan call + 9
1023
+ * deterministic executes instead of 9 LLM calls.
1024
+ *
1025
+ * Returns null when:
1026
+ * - the LLM response is unparseable JSON (fall through to per-action)
1027
+ * - the plan has zero steps
1028
+ * - any plan step has an invalid/unknown action shape
1029
+ *
1030
+ * The caller (BrowserAgent.run) treats null as "planner unavailable,
1031
+ * use per-action loop".
1032
+ */
1033
+ async plan(goal, state, options) {
1034
+ const startedAt = Date.now();
1035
+ const maxSteps = options?.maxSteps ?? 12;
1036
+ const extraContext = options?.extraContext;
1037
+ // Snapshot budget for the planner: keep it conservative — the planner
1038
+ // prompt is itself substantial, and we want the LLM to focus on the
1039
+ // STRUCTURE of the page rather than every leaf element.
1040
+ const snapshot = budgetSnapshot(state.snapshot, 12_000);
1041
+ const planSystemPrompt = `You are a planning engine for a browser automation agent.
1042
+
1043
+ Given a user goal and the current page state, your job is to generate a complete, ordered plan of actions that the agent will execute deterministically without re-entering you between steps. After each step, the runner verifies your stated post-condition. If verification fails the runner falls back to a per-action loop, so your job is to write a plan that requires the FEWEST steps and where every step's post-condition is reliably observable.
1044
+
1045
+ KEY PRINCIPLES:
1046
+
1047
+ 1. PREFER BATCH VERBS. The driver supports batch \`fill\` (fill N text fields, set N selects, check N checkboxes in ONE action) and \`clickSequence\` (N sequential clicks). Use these aggressively. A 19-field form should be 2-4 fill steps, not 19 type steps.
1048
+
1049
+ CRITICAL EXCLUSION: DO NOT INCLUDE ANY SPINBUTTON OR DATE INPUT IN YOUR PLAN. AT ALL. Period. If you see \`spinbutton\` in the snapshot (year, month, day, hour, minute spinners) or any input that looks like a date/time picker, OMIT it from your plan completely. Playwright's locator.fill() and locator.click() both time out on these elements, and your plan will deviate and fall back to the per-action loop. The per-action fallback knows how to handle them. Just LEAVE THEM OUT. Your plan should silently skip those elements and continue with the rest of the task as if they don't exist. The runner WILL handle them after your plan completes — you do not need to plan a step for them.
1050
+
1051
+ 2. ASSUME THE SNAPSHOT IS COMPLETE. The agent will execute your plan deterministically — you only get to see the page state ONCE (now). All @refs you emit must come from the ELEMENTS list below. Do not invent refs. If you don't see an element you'd need, do NOT plan a step for it — leave a gap and the runner will recover.
1052
+
1053
+ 3. POST-CONDITIONS MUST BE OBSERVABLE. Each step's expectedEffect should describe a concrete change the runner can see in the next snapshot: a URL change, a new visible element, a status text update. Vague effects like "form is filled" are useless because verification can't check them. Use concrete strings: "Status text shows 'Account Created!'" or "URL contains /confirm/".
1054
+
1055
+ 4. NAVIGATION CHANGES THE PAGE. Once you emit a \`navigate\`, \`click\` on a Next button, or any action that loads a new page, the @refs from the current snapshot are NO LONGER VALID. After such an action, your subsequent steps cannot rely on the same refs — they must use natural-language post-conditions until the runner falls back to per-action mode and observes the new page.
1056
+
1057
+ 5. MAX ${maxSteps} STEPS. If the task genuinely requires more, plan the first ${maxSteps} and let the runner replan from the resulting state.
1058
+
1059
+ 6. ONLY EMIT \`complete\` IF THE FINAL POST-CONDITION IS GENUINELY VERIFIABLE FROM THE PRIOR STEP'S expectedEffect. Do NOT fabricate success. If you cannot reliably know from the initial state alone whether the task succeeded (e.g. you can't predict whether a server submission will succeed, you don't know what the success message will say, or the form has multi-step navigation past your visibility), simply STOP planning at the last step you're confident about. The runner will fall through to the per-action loop after your plan exhausts and that loop will continue toward completion. It is BETTER to plan 5 confident steps and let the per-action loop finish than to plan 12 speculative steps with a fabricated complete at the end.
1060
+
1061
+ ACTION VERBS (same as the per-action prompt):
1062
+ - {"action": "click", "selector": "@REF"}
1063
+ - {"action": "type", "selector": "@REF", "text": "..."}
1064
+ - {"action": "press", "selector": "@REF", "key": "Enter"}
1065
+ - {"action": "select", "selector": "@REF", "value": "..."}
1066
+ - {"action": "scroll", "direction": "up"|"down", "amount": 500}
1067
+ - {"action": "navigate", "url": "..."}
1068
+ - {"action": "wait", "ms": 1000}
1069
+ - {"action": "fill", "fields": {"@a": "v1", "@b": "v2"}, "selects": {"@c": "v3"}, "checks": ["@d", "@e"]}
1070
+ - {"action": "clickSequence", "refs": ["@a", "@b", "@c"]}
1071
+ - {"action": "runScript", "script": "document.querySelector('.x').textContent"}
1072
+ - {"action": "complete", "result": "..."}
1073
+ - {"action": "abort", "reason": "..."}
1074
+
1075
+ RESPONSE FORMAT — respond with ONLY this JSON:
1076
+ {
1077
+ "reasoning": "1-2 sentence strategy summary",
1078
+ "steps": [
1079
+ {
1080
+ "action": { "action": "fill", "fields": { "@t1": "Jordan", "@t2": "Rivera" } },
1081
+ "expectedEffect": "First name and last name fields are populated",
1082
+ "rationale": "Step 1 of the multi-step form: batch-fill all visible Personal Info text fields"
1083
+ },
1084
+ ...
1085
+ ],
1086
+ "finalResult": "Account creation form completed and confirmation visible"
1087
+ }
1088
+
1089
+ DO NOT include any prose outside the JSON. DO NOT use markdown code blocks. The runner parses your response with JSON.parse() and will fall through to the per-action loop on parse failure.`;
1090
+ // Replan path: when the runner re-enters plan() after a previous plan
1091
+ // deviated, it injects a deviation summary. The system prompt is byte-
1092
+ // stable so prompt cache still hits — only the user message changes.
1093
+ const userText = `GOAL: ${goal}
1094
+
1095
+ CURRENT PAGE:
1096
+ URL: ${state.url}
1097
+ Title: ${state.title}
1098
+
1099
+ ELEMENTS:
1100
+ ${snapshot}
1101
+ ${extraContext ? `\n${extraContext}\n` : ''}
1102
+ What is the complete plan?`;
1103
+ const result = await this.generate(planSystemPrompt, [{ role: 'user', content: userText }], { provider: this.provider, model: this.modelName },
1104
+ // Plans need more output tokens than decide() — a 10-step plan with
1105
+ // batch fills + rationale per step is comfortably over 1000 tokens.
1106
+ 2_500).catch((err) => ({
1107
+ text: '',
1108
+ tokensUsed: undefined,
1109
+ inputTokens: undefined,
1110
+ outputTokens: undefined,
1111
+ cacheReadInputTokens: undefined,
1112
+ cacheCreationInputTokens: undefined,
1113
+ _error: err instanceof Error ? err.message : String(err),
1114
+ }));
1115
+ const durationMs = Date.now() - startedAt;
1116
+ const raw = result.text;
1117
+ if (!raw) {
1118
+ return {
1119
+ plan: null,
1120
+ raw: '',
1121
+ durationMs,
1122
+ parseError: result._error ?? 'empty response',
1123
+ };
1124
+ }
1125
+ // Reuse the same JSON tolerance as decide(): strip markdown fences,
1126
+ // then JSON.parse. On parse failure, return null and let the runner
1127
+ // fall through.
1128
+ let body = raw.trim();
1129
+ if (body.startsWith('```')) {
1130
+ body = body.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
1131
+ }
1132
+ let parsed;
1133
+ try {
1134
+ parsed = JSON.parse(body);
1135
+ }
1136
+ catch (err) {
1137
+ return {
1138
+ plan: null,
1139
+ raw,
1140
+ durationMs,
1141
+ tokensUsed: result.tokensUsed,
1142
+ inputTokens: result.inputTokens,
1143
+ outputTokens: result.outputTokens,
1144
+ cacheReadInputTokens: result.cacheReadInputTokens,
1145
+ cacheCreationInputTokens: result.cacheCreationInputTokens,
1146
+ parseError: err instanceof Error ? err.message : String(err),
1147
+ };
1148
+ }
1149
+ if (!Array.isArray(parsed.steps) || parsed.steps.length === 0) {
1150
+ return {
1151
+ plan: null,
1152
+ raw,
1153
+ durationMs,
1154
+ tokensUsed: result.tokensUsed,
1155
+ inputTokens: result.inputTokens,
1156
+ outputTokens: result.outputTokens,
1157
+ cacheReadInputTokens: result.cacheReadInputTokens,
1158
+ cacheCreationInputTokens: result.cacheCreationInputTokens,
1159
+ parseError: 'plan has zero steps',
1160
+ };
1161
+ }
1162
+ // Validate each step. Each must have a parseable action and a non-empty
1163
+ // expectedEffect string. We use the same validateAction helper that the
1164
+ // per-action parser uses, so the action shapes stay consistent.
1165
+ const steps = [];
1166
+ for (const [idx, rawStep] of parsed.steps.entries()) {
1167
+ if (!rawStep || typeof rawStep !== 'object') {
1168
+ return {
1169
+ plan: null,
1170
+ raw,
1171
+ durationMs,
1172
+ tokensUsed: result.tokensUsed,
1173
+ inputTokens: result.inputTokens,
1174
+ outputTokens: result.outputTokens,
1175
+ cacheReadInputTokens: result.cacheReadInputTokens,
1176
+ cacheCreationInputTokens: result.cacheCreationInputTokens,
1177
+ parseError: `step ${idx + 1}: not an object`,
1178
+ };
1179
+ }
1180
+ const stepObj = rawStep;
1181
+ const actionRaw = stepObj.action;
1182
+ if (!actionRaw || typeof actionRaw !== 'object') {
1183
+ return {
1184
+ plan: null,
1185
+ raw,
1186
+ durationMs,
1187
+ parseError: `step ${idx + 1}: missing action`,
1188
+ };
1189
+ }
1190
+ const actionData = actionRaw;
1191
+ const actionType = actionData.action;
1192
+ if (typeof actionType !== 'string') {
1193
+ return {
1194
+ plan: null,
1195
+ raw,
1196
+ durationMs,
1197
+ parseError: `step ${idx + 1}: action.action must be a string`,
1198
+ };
1199
+ }
1200
+ let action;
1201
+ try {
1202
+ action = validateAction(actionType, actionData);
1203
+ }
1204
+ catch (err) {
1205
+ return {
1206
+ plan: null,
1207
+ raw,
1208
+ durationMs,
1209
+ parseError: `step ${idx + 1}: ${err instanceof Error ? err.message : String(err)}`,
1210
+ };
1211
+ }
1212
+ const expectedEffect = typeof stepObj.expectedEffect === 'string' && stepObj.expectedEffect.length > 0
1213
+ ? stepObj.expectedEffect
1214
+ : 'page state advances after this action';
1215
+ const rationale = typeof stepObj.rationale === 'string' ? stepObj.rationale : undefined;
1216
+ steps.push({ action, expectedEffect, ...(rationale ? { rationale } : {}) });
1217
+ }
1218
+ const plan = {
1219
+ steps: steps.slice(0, maxSteps),
1220
+ ...(typeof parsed.finalResult === 'string' ? { finalResult: parsed.finalResult } : {}),
1221
+ ...(typeof parsed.reasoning === 'string' ? { reasoning: parsed.reasoning } : {}),
1222
+ };
1223
+ return {
1224
+ plan,
1225
+ raw,
1226
+ durationMs,
1227
+ tokensUsed: result.tokensUsed,
1228
+ inputTokens: result.inputTokens,
1229
+ outputTokens: result.outputTokens,
1230
+ cacheReadInputTokens: result.cacheReadInputTokens,
1231
+ cacheCreationInputTokens: result.cacheCreationInputTokens,
1232
+ };
1233
+ }
1007
1234
  /**
1008
1235
  * Evaluate quality of the current page state.
1009
1236
  * Takes a screenshot and asks the LLM to rate the visual quality,
@@ -1318,6 +1545,7 @@ Only include facts that are genuinely useful. Quality over quantity. Max 10 fact
1318
1545
  'click', 'type', 'press', 'hover', 'select',
1319
1546
  'scroll', 'navigate', 'wait', 'evaluate', 'runScript',
1320
1547
  'verifyPreview', 'complete', 'abort',
1548
+ 'fill', 'clickSequence',
1321
1549
  ]);
1322
1550
  try {
1323
1551
  const parsed = JSON.parse(text);
@@ -1544,8 +1772,51 @@ function validateAction(actionType, data) {
1544
1772
  return { action: 'complete', result: optStr('result') };
1545
1773
  case 'abort':
1546
1774
  return { action: 'abort', reason: optStr('reason') || 'No reason provided' };
1775
+ case 'fill': {
1776
+ // Multi-field batch fill — at least one of fields/selects/checks must be non-empty
1777
+ const fields = isStringRecord(data.fields) ? data.fields : undefined;
1778
+ const selects = isStringRecord(data.selects) ? data.selects : undefined;
1779
+ const checks = Array.isArray(data.checks) && data.checks.every((c) => typeof c === 'string')
1780
+ ? data.checks
1781
+ : undefined;
1782
+ const fieldCount = (fields ? Object.keys(fields).length : 0)
1783
+ + (selects ? Object.keys(selects).length : 0)
1784
+ + (checks ? checks.length : 0);
1785
+ if (fieldCount === 0) {
1786
+ throw new Error('fill action requires at least one of "fields" (object), "selects" (object), or "checks" (string[])');
1787
+ }
1788
+ return {
1789
+ action: 'fill',
1790
+ ...(fields ? { fields } : {}),
1791
+ ...(selects ? { selects } : {}),
1792
+ ...(checks ? { checks } : {}),
1793
+ };
1794
+ }
1795
+ case 'clickSequence': {
1796
+ const refs = Array.isArray(data.refs) && data.refs.every((r) => typeof r === 'string')
1797
+ ? data.refs
1798
+ : null;
1799
+ if (!refs || refs.length === 0) {
1800
+ throw new Error('clickSequence action requires "refs" (string[]) with at least one entry');
1801
+ }
1802
+ return {
1803
+ action: 'clickSequence',
1804
+ refs,
1805
+ ...(typeof data.intervalMs === 'number' ? { intervalMs: data.intervalMs } : {}),
1806
+ };
1807
+ }
1547
1808
  default:
1548
1809
  throw new Error(`Unknown action type: ${actionType}`);
1549
1810
  }
1550
1811
  }
1812
+ /** Type guard: value is a Record<string, string> */
1813
+ function isStringRecord(value) {
1814
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1815
+ return false;
1816
+ for (const v of Object.values(value)) {
1817
+ if (typeof v !== 'string')
1818
+ return false;
1819
+ }
1820
+ return true;
1821
+ }
1551
1822
  //# sourceMappingURL=index.js.map