@kolisachint/hoocode-agent 0.4.99 → 0.4.100

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.100] - 2026-06-28
4
+
5
+ ### Changed
6
+
7
+ - `browser_flow`/`browser_resume` now render a `NeedsParent` suspension as a compact,
8
+ readable block (request kind, goal/expected_state/fields, page title/url, named
9
+ controls with stable selectors, and a one-line headings summary) instead of
10
+ pretty-printing the entire `ParentRequest` JSON. On busy pages the raw observation
11
+ dump was thousands of mostly-empty entries straight into the model context; the
12
+ screenshot already conveys the page, so only the actionable text layer is kept,
13
+ cutting the per-suspension output by an order of magnitude.
14
+
3
15
  ## [0.4.99] - 2026-06-28
4
16
 
5
17
  ### Fixed
@@ -1 +1 @@
1
- {"version":3,"file":"browser-flow.d.ts","sourceRoot":"","sources":["../../../src/core/tools/browser-flow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EACN,KAAK,mBAAmB,EACxB,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAEhB,KAAK,aAAa,EAGlB,KAAK,WAAW,EAEhB,0BAA0B,EAE1B,MAAM,0BAA0B,CAAC;AAGlC,QAAA,MAAM,iBAAiB;;;;;;;EAmDrB,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEhE,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,UAAU,GAAG,cAAc,CAAC;IACpC,qEAAqE;IACrE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,yEAAyE;IACzE,WAAW,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACvC,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;CAAG;AAyF1E;;;;;GAKG;AACH,wBAAsB,WAAW,CAChC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,EACnD,aAAa,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC,CA2D9C;AAED,wBAAgB,+BAA+B,CAC9C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,sBAAsB,GAC9B,cAAc,CAAC,OAAO,iBAAiB,EAAE,kBAAkB,CAAC,CAiG9D;AAED,wBAAgB,qBAAqB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,sBAAsB,GAC9B,SAAS,CAAC,OAAO,iBAAiB,CAAC,CAErC","sourcesContent":["/**\n * `browser_flow` tool: start a deterministic browsertools flow.\n *\n * Spawns a `browsertools serve` process and issues `flow_start`. If the flow\n * runs to completion deterministically it returns the evidence inline. If replay\n * hits a point only an LLM can resolve, the serve process suspends with\n * `Outcome::NeedsParent`: this tool fetches the suspension screenshot, parks the\n * live session under its `ResumeToken`, and returns the typed `ParentRequest`\n * (plus the screenshot as an image) to the agent. The agent reasons and answers\n * with the companion `browser_resume` tool. See {@link browsertools-shared}.\n */\n\nimport { exec } from \"node:child_process\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport type { ImageContent, TextContent } from \"@kolisachint/hoocode-ai\";\nimport { type Static, Type } from \"typebox\";\nimport type { AgentToolResult } from \"../extensions/types.js\";\nimport { defineTool, type ToolDefinition } from \"../extensions/types.js\";\nimport {\n\ttype BrowserClientConfig,\n\tBrowsertoolsServeClient,\n\ttype BrowsertoolsToolOptions,\n\ttype FlowOutcome,\n\ttype GetResourceResult,\n\ttype ParentRequest,\n\tparkIdleClient,\n\tparkSession,\n\ttype ResumeToken,\n\tresolveBrowsertoolsBinary,\n\tresolveBrowsertoolsOptions,\n\ttakeIdleClient,\n} from \"./browsertools-shared.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\nconst browserFlowSchema = Type.Object({\n\tflow_path: Type.Optional(\n\t\tType.String({ description: \"Path to the .flow.json file to execute. Provide this or `flow`.\" }),\n\t),\n\tflow: Type.Optional(\n\t\tType.Record(Type.String(), Type.Unknown(), {\n\t\t\tdescription:\n\t\t\t\t\"Inline flow definition object (alternative to `flow_path`). Shape: \" +\n\t\t\t\t\"{ id: string, name: string, version: number, start_url: string, steps: Step[] }. \" +\n\t\t\t\t\"Each Step is { id: string, action: Action }. Action is a tagged object keyed by \" +\n\t\t\t\t\"`action`; the exact fields per variant (do NOT add extras like `goal` to the wrong \" +\n\t\t\t\t\"variant): \" +\n\t\t\t\t'{ action: \"navigate\", url: string } | ' +\n\t\t\t\t'{ action: \"click\", selector: string, fallbacks?: string[] } | ' +\n\t\t\t\t'{ action: \"fill\", selector: string, value_tpl: string } | ' +\n\t\t\t\t'{ action: \"select\", selector: string, value_tpl: string } | ' +\n\t\t\t\t'{ action: \"wait_settle\" } | ' +\n\t\t\t\t'{ action: \"checkpoint\", asserts: Invariant[] } | ' +\n\t\t\t\t'{ action: \"decide\", goal: string } | ' +\n\t\t\t\t'{ action: \"classify\" } | ' +\n\t\t\t\t'{ action: \"verify_visual\", expected_state: string } | ' +\n\t\t\t\t'{ action: \"extract_semantic\", fields: string[] }. ' +\n\t\t\t\t\"IMPORTANT: `extract_semantic.fields` MUST be an array of field-name strings \" +\n\t\t\t\t'(e.g. [\"person\", \"movie_count\"]), never an object/map, and `extract_semantic` takes ' +\n\t\t\t\t\"no `goal` (only `decide` does). Invariant (for checkpoint asserts) is one of: \" +\n\t\t\t\t'{ kind: \"element_present\", selector: string } | ' +\n\t\t\t\t'{ kind: \"text_present\", selector?: string, substr: string } | ' +\n\t\t\t\t'{ kind: \"url_matches\", pattern: string }.',\n\t\t}),\n\t),\n\tvars: Type.Optional(\n\t\tType.Record(Type.String(), Type.Unknown(), {\n\t\t\tdescription: \"Variables interpolated into the flow ({{var}} placeholders).\",\n\t\t}),\n\t),\n\tstore: Type.Optional(Type.String({ description: \"Path to the evidence store directory for this run.\" })),\n\tlive_view: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Start a live viewer that streams the page and the agent's tool-call log over a local \" +\n\t\t\t\t\"WebSocket, and auto-open it in your default browser. Set HOOCODE_BROWSERTOOLS_NO_OPEN=1 to \" +\n\t\t\t\t\"print the URL without opening. Best for flows that suspend or run long.\",\n\t\t}),\n\t),\n\theadful: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Launch a real on-screen Chromium window instead of a headless browser. Requires a desktop \" +\n\t\t\t\t\"display; unlike live_view it does not show the tool-call log.\",\n\t\t}),\n\t),\n});\n\nexport type BrowserFlowInput = Static<typeof browserFlowSchema>;\n\n/** Structured details surfaced alongside the model-facing content. */\nexport interface BrowserFlowDetails {\n\tstatus: \"complete\" | \"needs_parent\";\n\t/** Present when status is \"needs_parent\": resume with this token. */\n\ttoken?: ResumeToken;\n\t/** Present when status is \"needs_parent\": the kind of parent request. */\n\trequestKind?: ParentRequest[\"request\"];\n\t/** Present when status is \"complete\": the flow result/evidence. */\n\tresult?: unknown;\n}\n\nexport interface BrowserFlowToolOptions extends BrowsertoolsToolOptions {}\n\n/** ParentResponse shape hint per ParentRequest kind (mirrors contract.rs). */\nfunction parentResponseHint(kind: ParentRequest[\"request\"]): string {\n\tswitch (kind) {\n\t\tcase \"classify_state\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"state\", \"state\": \"<your label>\" }';\n\t\tcase \"verify_visual\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"verified\", \"passed\": true | false }';\n\t\tcase \"extract_semantic\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"extracted\", \"fields\": { \"<field>\": \"<value>\", ... } }';\n\t\tcase \"decide_next_action\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"next_action\", \"action\": <action object> }';\n\t\tcase \"reidentify_element\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"element\", \"selector\": \"<css selector>\" }';\n\t\tdefault:\n\t\t\treturn \"Reply with browser_resume providing the appropriate ParentResponse object.\";\n\t}\n}\n\n/** Compact, model-facing reminder of the inline-flow action schema, appended to\n * an `invalid inline flow` error so the model can self-correct in one turn\n * instead of guessing field shapes across several rounds. */\nconst FLOW_SCHEMA_HINT =\n\t\"Inline flow shape: { id, name, version, start_url, steps: [{ id, action }] }. \" +\n\t\"Action variants (use EXACTLY these fields): \" +\n\t'{ action: \"navigate\", url } | { action: \"click\", selector, fallbacks? } | ' +\n\t'{ action: \"fill\", selector, value_tpl } | { action: \"select\", selector, value_tpl } | ' +\n\t'{ action: \"wait_settle\" } | { action: \"checkpoint\", asserts } | ' +\n\t'{ action: \"decide\", goal } | { action: \"classify\" } | ' +\n\t'{ action: \"verify_visual\", expected_state } | { action: \"extract_semantic\", fields }. ' +\n\t'`extract_semantic.fields` MUST be a string array like [\"person\",\"count\"] (not an object), ' +\n\t\"and it takes no `goal` — only `decide` does.\";\n\n/** If an error is a browsertools inline-flow validation failure, append the\n * schema hint so the model fixes the flow on the next call. Other errors pass\n * through unchanged. */\nfunction enrichFlowError(error: unknown): unknown {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tif (message.includes(\"invalid inline flow\") && !message.includes(\"Action variants\")) {\n\t\treturn new Error(`${message}\\n\\n${FLOW_SCHEMA_HINT}`);\n\t}\n\treturn error;\n}\n\n/** Best-effort: open a URL in the OS default browser. Never throws. Suppressed by\n * HOOCODE_BROWSERTOOLS_NO_OPEN (the URL is still surfaced to the agent). */\nfunction openInBrowser(url: string): boolean {\n\tconst suppress = process.env.HOOCODE_BROWSERTOOLS_NO_OPEN?.trim();\n\tif (suppress === \"1\" || suppress?.toLowerCase() === \"true\") return false;\n\tconst openCmd = process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n\ttry {\n\t\texec(`${openCmd} \"${url}\"`);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Start the streamed live viewer and auto-open it. Best-effort: a failure here\n * must not abort the flow, so it degrades to returning undefined. Returns a\n * human-readable status line to prepend to the tool result, or undefined. */\nasync function startLiveView(client: BrowsertoolsServeClient): Promise<string | undefined> {\n\ttry {\n\t\tconst result = await client.request<{ url?: string; error?: string }>(\"live_view_start\", {});\n\t\tif (!result?.url) return undefined;\n\t\tconst opened = openInBrowser(result.url);\n\t\treturn opened ? `Live view opened in your browser: ${result.url}` : `Live view available at: ${result.url}`;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Fetch the suspension screenshot for a ParentRequest as an ImageContent block.\n * Best-effort: a fetch failure degrades to no image rather than failing the flow. */\nasync function fetchScreenshot(\n\tclient: BrowsertoolsServeClient,\n\trequest: ParentRequest,\n): Promise<ImageContent | undefined> {\n\tif (!request.screenshot_ref) return undefined;\n\ttry {\n\t\tconst resource = await client.request<GetResourceResult>(\"get_resource\", { ref: request.screenshot_ref });\n\t\tif (!resource?.png_base64) return undefined;\n\t\treturn { type: \"image\", data: resource.png_base64, mimeType: resource.mime || \"image/png\" };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Map a `flow_start`/`flow_resume` outcome to a tool result, owning the live\n * client: dispose it on terminal outcomes, or park it under the new resume token\n * when the flow suspends again. `rounds` is the number of NeedsParent yields seen\n * so far for this flow (including the one being processed), used for the cap.\n */\nexport async function advanceFlow(\n\tclient: BrowsertoolsServeClient,\n\toutcome: FlowOutcome,\n\trounds: number,\n\topts: ReturnType<typeof resolveBrowsertoolsOptions>,\n\tbrowserConfig?: BrowserClientConfig,\n): Promise<AgentToolResult<BrowserFlowDetails>> {\n\tif (outcome.outcome === \"complete\") {\n\t\tif (browserConfig) {\n\t\t\tparkIdleClient(client, browserConfig.headful, browserConfig.browserPath, browserConfig.idleTimeoutMs);\n\t\t} else {\n\t\t\tclient.dispose();\n\t\t}\n\t\tconst result = outcome.result;\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\", text: `Flow complete.\\n${JSON.stringify(result ?? {}, null, 2)}` }],\n\t\t\tdetails: { status: \"complete\", result },\n\t\t};\n\t}\n\n\tif (outcome.outcome === \"failed\") {\n\t\tif (browserConfig) {\n\t\t\tparkIdleClient(client, browserConfig.headful, browserConfig.browserPath, browserConfig.idleTimeoutMs);\n\t\t} else {\n\t\t\tclient.dispose();\n\t\t}\n\t\tconst where = outcome.step_id ? ` at step \"${outcome.step_id}\"` : \"\";\n\t\tconst kind = outcome.kind ? ` (${outcome.kind})` : \"\";\n\t\tthrow new Error(`browsertools flow failed${where}: ${outcome.detail ?? \"unknown error\"}${kind}`);\n\t}\n\n\tif (outcome.outcome === \"needs_parent\") {\n\t\tif (rounds > opts.maxParentRounds) {\n\t\t\tclient.dispose();\n\t\t\tthrow new Error(\n\t\t\t\t`browsertools flow exceeded the maximum of ${opts.maxParentRounds} NeedsParent rounds; aborting to avoid a runaway loop`,\n\t\t\t);\n\t\t}\n\t\tconst { request, token } = outcome;\n\t\tconst image = await fetchScreenshot(client, request);\n\t\tparkSession(\n\t\t\ttoken,\n\t\t\tclient,\n\t\t\trounds,\n\t\t\tbrowserConfig?.idleTimeoutMs ?? opts.idleTimeoutMs,\n\t\t\tbrowserConfig?.headful ?? false,\n\t\t\tbrowserConfig?.browserPath,\n\t\t);\n\n\t\tconst text =\n\t\t\t`Flow suspended — parent decision required (NeedsParent).\\n` +\n\t\t\t`request: ${JSON.stringify(request, null, 2)}\\n` +\n\t\t\t`resume token: ${token}\\n` +\n\t\t\t`${parentResponseHint(request.request)}\\n` +\n\t\t\t(image ? \"A screenshot of the current page is attached.\" : \"(no screenshot available)\");\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (image) content.push(image);\n\t\treturn {\n\t\t\tcontent,\n\t\t\tdetails: { status: \"needs_parent\", token, requestKind: request.request },\n\t\t};\n\t}\n\n\tclient.dispose();\n\tthrow new Error(`browsertools returned an unrecognized flow outcome: ${JSON.stringify(outcome)}`);\n}\n\nexport function createBrowserFlowToolDefinition(\n\tcwd: string,\n\toptions?: BrowserFlowToolOptions,\n): ToolDefinition<typeof browserFlowSchema, BrowserFlowDetails> {\n\tconst opts = resolveBrowsertoolsOptions(options);\n\treturn defineTool({\n\t\tname: \"browser_flow\",\n\t\tlabel: \"browser flow\",\n\t\tdescription:\n\t\t\t\"Start a deterministic browser flow (browsertools). Runs a saved .flow.json (or inline flow) \" +\n\t\t\t\"against a headless browser and returns the evidence on completion. If the flow needs an LLM \" +\n\t\t\t\"decision mid-replay (classify a page state, verify a visual, extract a value, decide the next \" +\n\t\t\t\"action, or re-identify a drifted element) it suspends and returns a typed request plus a \" +\n\t\t\t\"screenshot; answer it with the browser_resume tool using the returned token. Off by default; \" +\n\t\t\t\"enabled with --enable-browsertools.\\n\\n\" +\n\t\t\t\"AGENTIC LOOP (preferred for exploration): for any task where you must read or navigate based on \" +\n\t\t\t\"page content, build the flow from `decide`/`extract_semantic`/`classify`/`verify_visual` steps. \" +\n\t\t\t\"Each such step SUSPENDS and hands you a screenshot of the current page. Read the screenshot, then \" +\n\t\t\t\"call browser_resume with the next action, and keep looping until the outcome is `complete`. Do \" +\n\t\t\t\"NOT fall back to webfetch/curl to read page content you could read from the screenshot — that \" +\n\t\t\t\"bypasses the live session and breaks on auth-gated or JS-rendered pages. A flow ENDS as soon as \" +\n\t\t\t\"its last step runs, so chain several `decide` steps (interleaved with `wait_settle`) when you \" +\n\t\t\t\"need a multi-step journey (search -> open result -> scroll -> extract).\\n\\n\" +\n\t\t\t\"RESUME RESPONSE SHAPES (browser_resume `response` field): decide_next_action -> \" +\n\t\t\t'{ response: \"next_action\", action: <Action> }; classify_state -> { response: \"state\", state: \"<label>\" }; ' +\n\t\t\t'verify_visual -> { response: \"verified\", passed: true|false }; extract_semantic -> ' +\n\t\t\t'{ response: \"extracted\", fields: { <field>: <value> } }; reidentify_element -> ' +\n\t\t\t'{ response: \"element\", selector: \"<css>\" }.\\n\\n' +\n\t\t\t\"ACTION (for next_action) is the same shape as a flow step's action: { action: 'navigate', url }, \" +\n\t\t\t\"{ action: 'click', selector, fallbacks?: string[] }, { action: 'fill', selector, value_tpl }, \" +\n\t\t\t\"{ action: 'select', selector, value_tpl }, { action: 'wait_settle' }. Prefer stable CSS/id \" +\n\t\t\t\"selectors, and ALWAYS pass a `fallbacks` array of alternate selectors for click/fill, because \" +\n\t\t\t\"the primary selector often drifts (e.g. click '.suggestion-link' with fallbacks \" +\n\t\t\t\"['a.mw-searchSuggest-link', '#typeahead-suggestions a']).\\n\\n\" +\n\t\t\t\"VISIBILITY: pass headful:true to launch a real on-screen browser window the user can watch; \" +\n\t\t\t\"live_view:true additionally streams a mirror + tool-call log to a local URL (set live_view:false \" +\n\t\t\t\"to suppress the mirror when the instance defaults it on).\",\n\t\tpromptSnippet: \"Run a deterministic browser flow, pausing for LLM decisions when needed\",\n\t\tparameters: browserFlowSchema,\n\t\tasync execute(_toolCallId, params: BrowserFlowInput, signal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tif (!params.flow_path && !params.flow) {\n\t\t\t\tthrow new Error(\"browser_flow requires either `flow_path` or `flow`\");\n\t\t\t}\n\n\t\t\tconst binaryPath = await resolveBrowsertoolsBinary(options);\n\t\t\tconst headful = params.headful ?? opts.headful;\n\t\t\t// Reuse the shared idle client if browser config matches, otherwise create a new\n\t\t\t// one. This keeps a single Chromium process and live-view port across calls.\n\t\t\tlet client = takeIdleClient(headful, opts.browserPath, opts.idleTimeoutMs);\n\t\t\tif (!client) {\n\t\t\t\tclient = new BrowsertoolsServeClient(binaryPath, {\n\t\t\t\t\tcwd,\n\t\t\t\t\tbrowserPath: opts.browserPath,\n\t\t\t\t\tserveArgs: opts.serveArgs,\n\t\t\t\t\trequestTimeoutMs: opts.requestTimeoutMs,\n\t\t\t\t\theadful,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// If the call is aborted before we hand the client to the registry, make\n\t\t\t// sure the serve process is torn down.\n\t\t\tconst onAbort = () => client.dispose();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\ttry {\n\t\t\t\tconst startParams: Record<string, unknown> = {};\n\t\t\t\tif (params.flow_path) startParams.flow_path = params.flow_path;\n\t\t\t\tif (params.flow) startParams.flow = params.flow;\n\t\t\t\tif (params.vars) startParams.vars = params.vars;\n\t\t\t\tif (params.store) startParams.store = params.store;\n\n\t\t\t\t// Bring up the live viewer before the flow runs so the page render and\n\t\t\t\t// tool-call log are visible from the first step. The per-call param wins\n\t\t\t\t// over the instance default (--enable-browser-live-preview).\n\t\t\t\tconst liveViewEnabled = params.live_view ?? opts.liveView;\n\t\t\t\tconst liveViewStatus = liveViewEnabled ? await startLiveView(client) : undefined;\n\n\t\t\t\tconst outcome = await client.request<FlowOutcome>(\"flow_start\", startParams);\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tclient.dispose();\n\t\t\t\t\tthrow new Error(\"Operation aborted\");\n\t\t\t\t}\n\t\t\t\tconst browserConfig: BrowserClientConfig = {\n\t\t\t\t\theadful,\n\t\t\t\t\tbrowserPath: opts.browserPath,\n\t\t\t\t\tidleTimeoutMs: opts.idleTimeoutMs,\n\t\t\t\t};\n\t\t\t\tconst result = await advanceFlow(client, outcome, 1, opts, browserConfig);\n\t\t\t\tif (liveViewStatus) {\n\t\t\t\t\tresult.content.unshift({ type: \"text\", text: liveViewStatus });\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tclient.dispose();\n\t\t\t\tthrow enrichFlowError(error);\n\t\t\t} finally {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t});\n}\n\nexport function createBrowserFlowTool(\n\tcwd: string,\n\toptions?: BrowserFlowToolOptions,\n): AgentTool<typeof browserFlowSchema> {\n\treturn wrapToolDefinition(createBrowserFlowToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"browser-flow.d.ts","sourceRoot":"","sources":["../../../src/core/tools/browser-flow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EACN,KAAK,mBAAmB,EACxB,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAEhB,KAAK,aAAa,EAGlB,KAAK,WAAW,EAEhB,0BAA0B,EAE1B,MAAM,0BAA0B,CAAC;AAGlC,QAAA,MAAM,iBAAiB;;;;;;;EAmDrB,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEhE,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,UAAU,GAAG,cAAc,CAAC;IACpC,qEAAqE;IACrE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,yEAAyE;IACzE,WAAW,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACvC,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;CAAG;AAyL1E;;;;;GAKG;AACH,wBAAsB,WAAW,CAChC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,EACnD,aAAa,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC,CA2D9C;AAED,wBAAgB,+BAA+B,CAC9C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,sBAAsB,GAC9B,cAAc,CAAC,OAAO,iBAAiB,EAAE,kBAAkB,CAAC,CAiG9D;AAED,wBAAgB,qBAAqB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,sBAAsB,GAC9B,SAAS,CAAC,OAAO,iBAAiB,CAAC,CAErC","sourcesContent":["/**\n * `browser_flow` tool: start a deterministic browsertools flow.\n *\n * Spawns a `browsertools serve` process and issues `flow_start`. If the flow\n * runs to completion deterministically it returns the evidence inline. If replay\n * hits a point only an LLM can resolve, the serve process suspends with\n * `Outcome::NeedsParent`: this tool fetches the suspension screenshot, parks the\n * live session under its `ResumeToken`, and returns the typed `ParentRequest`\n * (plus the screenshot as an image) to the agent. The agent reasons and answers\n * with the companion `browser_resume` tool. See {@link browsertools-shared}.\n */\n\nimport { exec } from \"node:child_process\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport type { ImageContent, TextContent } from \"@kolisachint/hoocode-ai\";\nimport { type Static, Type } from \"typebox\";\nimport type { AgentToolResult } from \"../extensions/types.js\";\nimport { defineTool, type ToolDefinition } from \"../extensions/types.js\";\nimport {\n\ttype BrowserClientConfig,\n\tBrowsertoolsServeClient,\n\ttype BrowsertoolsToolOptions,\n\ttype FlowOutcome,\n\ttype GetResourceResult,\n\ttype ParentRequest,\n\tparkIdleClient,\n\tparkSession,\n\ttype ResumeToken,\n\tresolveBrowsertoolsBinary,\n\tresolveBrowsertoolsOptions,\n\ttakeIdleClient,\n} from \"./browsertools-shared.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\nconst browserFlowSchema = Type.Object({\n\tflow_path: Type.Optional(\n\t\tType.String({ description: \"Path to the .flow.json file to execute. Provide this or `flow`.\" }),\n\t),\n\tflow: Type.Optional(\n\t\tType.Record(Type.String(), Type.Unknown(), {\n\t\t\tdescription:\n\t\t\t\t\"Inline flow definition object (alternative to `flow_path`). Shape: \" +\n\t\t\t\t\"{ id: string, name: string, version: number, start_url: string, steps: Step[] }. \" +\n\t\t\t\t\"Each Step is { id: string, action: Action }. Action is a tagged object keyed by \" +\n\t\t\t\t\"`action`; the exact fields per variant (do NOT add extras like `goal` to the wrong \" +\n\t\t\t\t\"variant): \" +\n\t\t\t\t'{ action: \"navigate\", url: string } | ' +\n\t\t\t\t'{ action: \"click\", selector: string, fallbacks?: string[] } | ' +\n\t\t\t\t'{ action: \"fill\", selector: string, value_tpl: string } | ' +\n\t\t\t\t'{ action: \"select\", selector: string, value_tpl: string } | ' +\n\t\t\t\t'{ action: \"wait_settle\" } | ' +\n\t\t\t\t'{ action: \"checkpoint\", asserts: Invariant[] } | ' +\n\t\t\t\t'{ action: \"decide\", goal: string } | ' +\n\t\t\t\t'{ action: \"classify\" } | ' +\n\t\t\t\t'{ action: \"verify_visual\", expected_state: string } | ' +\n\t\t\t\t'{ action: \"extract_semantic\", fields: string[] }. ' +\n\t\t\t\t\"IMPORTANT: `extract_semantic.fields` MUST be an array of field-name strings \" +\n\t\t\t\t'(e.g. [\"person\", \"movie_count\"]), never an object/map, and `extract_semantic` takes ' +\n\t\t\t\t\"no `goal` (only `decide` does). Invariant (for checkpoint asserts) is one of: \" +\n\t\t\t\t'{ kind: \"element_present\", selector: string } | ' +\n\t\t\t\t'{ kind: \"text_present\", selector?: string, substr: string } | ' +\n\t\t\t\t'{ kind: \"url_matches\", pattern: string }.',\n\t\t}),\n\t),\n\tvars: Type.Optional(\n\t\tType.Record(Type.String(), Type.Unknown(), {\n\t\t\tdescription: \"Variables interpolated into the flow ({{var}} placeholders).\",\n\t\t}),\n\t),\n\tstore: Type.Optional(Type.String({ description: \"Path to the evidence store directory for this run.\" })),\n\tlive_view: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Start a live viewer that streams the page and the agent's tool-call log over a local \" +\n\t\t\t\t\"WebSocket, and auto-open it in your default browser. Set HOOCODE_BROWSERTOOLS_NO_OPEN=1 to \" +\n\t\t\t\t\"print the URL without opening. Best for flows that suspend or run long.\",\n\t\t}),\n\t),\n\theadful: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Launch a real on-screen Chromium window instead of a headless browser. Requires a desktop \" +\n\t\t\t\t\"display; unlike live_view it does not show the tool-call log.\",\n\t\t}),\n\t),\n});\n\nexport type BrowserFlowInput = Static<typeof browserFlowSchema>;\n\n/** Structured details surfaced alongside the model-facing content. */\nexport interface BrowserFlowDetails {\n\tstatus: \"complete\" | \"needs_parent\";\n\t/** Present when status is \"needs_parent\": resume with this token. */\n\ttoken?: ResumeToken;\n\t/** Present when status is \"needs_parent\": the kind of parent request. */\n\trequestKind?: ParentRequest[\"request\"];\n\t/** Present when status is \"complete\": the flow result/evidence. */\n\tresult?: unknown;\n}\n\nexport interface BrowserFlowToolOptions extends BrowsertoolsToolOptions {}\n\n/** ParentResponse shape hint per ParentRequest kind (mirrors contract.rs). */\nfunction parentResponseHint(kind: ParentRequest[\"request\"]): string {\n\tswitch (kind) {\n\t\tcase \"classify_state\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"state\", \"state\": \"<your label>\" }';\n\t\tcase \"verify_visual\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"verified\", \"passed\": true | false }';\n\t\tcase \"extract_semantic\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"extracted\", \"fields\": { \"<field>\": \"<value>\", ... } }';\n\t\tcase \"decide_next_action\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"next_action\", \"action\": <action object> }';\n\t\tcase \"reidentify_element\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"element\", \"selector\": \"<css selector>\" }';\n\t\tdefault:\n\t\t\treturn \"Reply with browser_resume providing the appropriate ParentResponse object.\";\n\t}\n}\n\n/** A control fact from the browsertools observation (mirrors observe.rs InputFact). */\ninterface ObservationInput {\n\tkind?: string;\n\taccessible_name?: string;\n\tselector_hint?: string;\n}\n\n/** A landmark fact from the browsertools observation (mirrors observe.rs Landmark). */\ninterface ObservationLandmark {\n\trole?: string;\n\tname?: string;\n}\n\n/** The HTML-derived observation the engine attaches to decide/classify requests. */\ninterface Observation {\n\turl?: string;\n\ttitle?: string;\n\tinputs?: ObservationInput[];\n\tlandmarks?: ObservationLandmark[];\n\ttext_blocks?: string[];\n\thas_error_region?: boolean;\n}\n\n/** Truncate a string to `max` chars with an ellipsis, for compact rendering. */\nfunction clip(s: string, max = 80): string {\n\tconst t = s.trim();\n\treturn t.length > max ? `${t.slice(0, max - 1)}\\u2026` : t;\n}\n\n/** Render one observation control as a single compact line. */\nfunction renderControl(i: ObservationInput): string {\n\tconst sel = i.selector_hint && i.selector_hint !== \"button\" ? i.selector_hint : undefined;\n\tconst name = i.accessible_name ? `\"${clip(i.accessible_name, 60)}\"` : undefined;\n\tconst parts = [i.kind, name, sel].filter(Boolean);\n\treturn ` - ${parts.join(\" \")}`;\n}\n\n/**\n * Summarize a verbose `decide_next_action`/`classify_state` observation into a\n * compact, readable block instead of dumping the raw JSON (which on busy pages is\n * thousands of mostly-empty entries straight into the model context). The\n * screenshot already conveys the page; this is just the actionable text layer:\n * url/title, named controls, salient headings. Returns undefined when there is no\n * observation to summarize.\n */\nfunction summarizeObservation(observation: Observation | undefined): string | undefined {\n\tif (!observation || typeof observation !== \"object\") return undefined;\n\tconst lines: string[] = [];\n\tif (observation.title) lines.push(`page: ${clip(observation.title, 100)}`);\n\tif (observation.url) lines.push(`url: ${clip(observation.url, 120)}`);\n\tif (observation.has_error_region) lines.push(\"note: page has an error/alert region\");\n\n\tconst controls = (observation.inputs ?? []).filter((i) => i.kind !== \"_more\");\n\tif (controls.length) {\n\t\tlines.push(`controls (${controls.length}):`);\n\t\tfor (const c of controls.slice(0, 25)) lines.push(renderControl(c));\n\t\tif (controls.length > 25) lines.push(` - …+${controls.length - 25} more`);\n\t}\n\n\tconst text = (observation.text_blocks ?? []).filter((t) => t?.trim());\n\tif (text.length) {\n\t\tlines.push(\n\t\t\t`headings: ${text\n\t\t\t\t.slice(0, 12)\n\t\t\t\t.map((t) => clip(t, 50))\n\t\t\t\t.join(\" · \")}`,\n\t\t);\n\t}\n\treturn lines.length ? lines.join(\"\\n\") : undefined;\n}\n\n/**\n * Build the compact, model-facing text for a `NeedsParent` suspension. Renders\n * only the fields that matter per request kind (goal, expected_state, fields,\n * description) plus a summarized observation, rather than pretty-printing the\n * entire request — which is the dominant token sink for browser flows.\n */\nfunction formatParentRequest(request: ParentRequest): string {\n\tconst kind = request.request;\n\tconst lines: string[] = [`request: ${kind}`];\n\tconst r = request as ParentRequest & {\n\t\tgoal?: string;\n\t\texpected_state?: string;\n\t\tdescription?: string;\n\t\tfields?: string[];\n\t\tobservation?: Observation;\n\t};\n\tif (r.goal) lines.push(`goal: ${r.goal}`);\n\tif (r.expected_state) lines.push(`expected_state: ${r.expected_state}`);\n\tif (r.description) lines.push(`element: ${r.description}`);\n\tif (Array.isArray(r.fields) && r.fields.length) lines.push(`fields: ${r.fields.join(\", \")}`);\n\tconst summary = summarizeObservation(r.observation);\n\tif (summary) lines.push(summary);\n\treturn lines.join(\"\\n\");\n}\n\n/** Compact, model-facing reminder of the inline-flow action schema, appended to\n * an `invalid inline flow` error so the model can self-correct in one turn\n * instead of guessing field shapes across several rounds. */\nconst FLOW_SCHEMA_HINT =\n\t\"Inline flow shape: { id, name, version, start_url, steps: [{ id, action }] }. \" +\n\t\"Action variants (use EXACTLY these fields): \" +\n\t'{ action: \"navigate\", url } | { action: \"click\", selector, fallbacks? } | ' +\n\t'{ action: \"fill\", selector, value_tpl } | { action: \"select\", selector, value_tpl } | ' +\n\t'{ action: \"wait_settle\" } | { action: \"checkpoint\", asserts } | ' +\n\t'{ action: \"decide\", goal } | { action: \"classify\" } | ' +\n\t'{ action: \"verify_visual\", expected_state } | { action: \"extract_semantic\", fields }. ' +\n\t'`extract_semantic.fields` MUST be a string array like [\"person\",\"count\"] (not an object), ' +\n\t\"and it takes no `goal` — only `decide` does.\";\n\n/** If an error is a browsertools inline-flow validation failure, append the\n * schema hint so the model fixes the flow on the next call. Other errors pass\n * through unchanged. */\nfunction enrichFlowError(error: unknown): unknown {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tif (message.includes(\"invalid inline flow\") && !message.includes(\"Action variants\")) {\n\t\treturn new Error(`${message}\\n\\n${FLOW_SCHEMA_HINT}`);\n\t}\n\treturn error;\n}\n\n/** Best-effort: open a URL in the OS default browser. Never throws. Suppressed by\n * HOOCODE_BROWSERTOOLS_NO_OPEN (the URL is still surfaced to the agent). */\nfunction openInBrowser(url: string): boolean {\n\tconst suppress = process.env.HOOCODE_BROWSERTOOLS_NO_OPEN?.trim();\n\tif (suppress === \"1\" || suppress?.toLowerCase() === \"true\") return false;\n\tconst openCmd = process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n\ttry {\n\t\texec(`${openCmd} \"${url}\"`);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Start the streamed live viewer and auto-open it. Best-effort: a failure here\n * must not abort the flow, so it degrades to returning undefined. Returns a\n * human-readable status line to prepend to the tool result, or undefined. */\nasync function startLiveView(client: BrowsertoolsServeClient): Promise<string | undefined> {\n\ttry {\n\t\tconst result = await client.request<{ url?: string; error?: string }>(\"live_view_start\", {});\n\t\tif (!result?.url) return undefined;\n\t\tconst opened = openInBrowser(result.url);\n\t\treturn opened ? `Live view opened in your browser: ${result.url}` : `Live view available at: ${result.url}`;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Fetch the suspension screenshot for a ParentRequest as an ImageContent block.\n * Best-effort: a fetch failure degrades to no image rather than failing the flow. */\nasync function fetchScreenshot(\n\tclient: BrowsertoolsServeClient,\n\trequest: ParentRequest,\n): Promise<ImageContent | undefined> {\n\tif (!request.screenshot_ref) return undefined;\n\ttry {\n\t\tconst resource = await client.request<GetResourceResult>(\"get_resource\", { ref: request.screenshot_ref });\n\t\tif (!resource?.png_base64) return undefined;\n\t\treturn { type: \"image\", data: resource.png_base64, mimeType: resource.mime || \"image/png\" };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Map a `flow_start`/`flow_resume` outcome to a tool result, owning the live\n * client: dispose it on terminal outcomes, or park it under the new resume token\n * when the flow suspends again. `rounds` is the number of NeedsParent yields seen\n * so far for this flow (including the one being processed), used for the cap.\n */\nexport async function advanceFlow(\n\tclient: BrowsertoolsServeClient,\n\toutcome: FlowOutcome,\n\trounds: number,\n\topts: ReturnType<typeof resolveBrowsertoolsOptions>,\n\tbrowserConfig?: BrowserClientConfig,\n): Promise<AgentToolResult<BrowserFlowDetails>> {\n\tif (outcome.outcome === \"complete\") {\n\t\tif (browserConfig) {\n\t\t\tparkIdleClient(client, browserConfig.headful, browserConfig.browserPath, browserConfig.idleTimeoutMs);\n\t\t} else {\n\t\t\tclient.dispose();\n\t\t}\n\t\tconst result = outcome.result;\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\", text: `Flow complete.\\n${JSON.stringify(result ?? {}, null, 2)}` }],\n\t\t\tdetails: { status: \"complete\", result },\n\t\t};\n\t}\n\n\tif (outcome.outcome === \"failed\") {\n\t\tif (browserConfig) {\n\t\t\tparkIdleClient(client, browserConfig.headful, browserConfig.browserPath, browserConfig.idleTimeoutMs);\n\t\t} else {\n\t\t\tclient.dispose();\n\t\t}\n\t\tconst where = outcome.step_id ? ` at step \"${outcome.step_id}\"` : \"\";\n\t\tconst kind = outcome.kind ? ` (${outcome.kind})` : \"\";\n\t\tthrow new Error(`browsertools flow failed${where}: ${outcome.detail ?? \"unknown error\"}${kind}`);\n\t}\n\n\tif (outcome.outcome === \"needs_parent\") {\n\t\tif (rounds > opts.maxParentRounds) {\n\t\t\tclient.dispose();\n\t\t\tthrow new Error(\n\t\t\t\t`browsertools flow exceeded the maximum of ${opts.maxParentRounds} NeedsParent rounds; aborting to avoid a runaway loop`,\n\t\t\t);\n\t\t}\n\t\tconst { request, token } = outcome;\n\t\tconst image = await fetchScreenshot(client, request);\n\t\tparkSession(\n\t\t\ttoken,\n\t\t\tclient,\n\t\t\trounds,\n\t\t\tbrowserConfig?.idleTimeoutMs ?? opts.idleTimeoutMs,\n\t\t\tbrowserConfig?.headful ?? false,\n\t\t\tbrowserConfig?.browserPath,\n\t\t);\n\n\t\tconst text =\n\t\t\t`Flow suspended — parent decision required (NeedsParent).\\n` +\n\t\t\t`${formatParentRequest(request)}\\n` +\n\t\t\t`resume token: ${token}\\n` +\n\t\t\t`${parentResponseHint(request.request)}\\n` +\n\t\t\t(image ? \"A screenshot of the current page is attached.\" : \"(no screenshot available)\");\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (image) content.push(image);\n\t\treturn {\n\t\t\tcontent,\n\t\t\tdetails: { status: \"needs_parent\", token, requestKind: request.request },\n\t\t};\n\t}\n\n\tclient.dispose();\n\tthrow new Error(`browsertools returned an unrecognized flow outcome: ${JSON.stringify(outcome)}`);\n}\n\nexport function createBrowserFlowToolDefinition(\n\tcwd: string,\n\toptions?: BrowserFlowToolOptions,\n): ToolDefinition<typeof browserFlowSchema, BrowserFlowDetails> {\n\tconst opts = resolveBrowsertoolsOptions(options);\n\treturn defineTool({\n\t\tname: \"browser_flow\",\n\t\tlabel: \"browser flow\",\n\t\tdescription:\n\t\t\t\"Start a deterministic browser flow (browsertools). Runs a saved .flow.json (or inline flow) \" +\n\t\t\t\"against a headless browser and returns the evidence on completion. If the flow needs an LLM \" +\n\t\t\t\"decision mid-replay (classify a page state, verify a visual, extract a value, decide the next \" +\n\t\t\t\"action, or re-identify a drifted element) it suspends and returns a typed request plus a \" +\n\t\t\t\"screenshot; answer it with the browser_resume tool using the returned token. Off by default; \" +\n\t\t\t\"enabled with --enable-browsertools.\\n\\n\" +\n\t\t\t\"AGENTIC LOOP (preferred for exploration): for any task where you must read or navigate based on \" +\n\t\t\t\"page content, build the flow from `decide`/`extract_semantic`/`classify`/`verify_visual` steps. \" +\n\t\t\t\"Each such step SUSPENDS and hands you a screenshot of the current page. Read the screenshot, then \" +\n\t\t\t\"call browser_resume with the next action, and keep looping until the outcome is `complete`. Do \" +\n\t\t\t\"NOT fall back to webfetch/curl to read page content you could read from the screenshot — that \" +\n\t\t\t\"bypasses the live session and breaks on auth-gated or JS-rendered pages. A flow ENDS as soon as \" +\n\t\t\t\"its last step runs, so chain several `decide` steps (interleaved with `wait_settle`) when you \" +\n\t\t\t\"need a multi-step journey (search -> open result -> scroll -> extract).\\n\\n\" +\n\t\t\t\"RESUME RESPONSE SHAPES (browser_resume `response` field): decide_next_action -> \" +\n\t\t\t'{ response: \"next_action\", action: <Action> }; classify_state -> { response: \"state\", state: \"<label>\" }; ' +\n\t\t\t'verify_visual -> { response: \"verified\", passed: true|false }; extract_semantic -> ' +\n\t\t\t'{ response: \"extracted\", fields: { <field>: <value> } }; reidentify_element -> ' +\n\t\t\t'{ response: \"element\", selector: \"<css>\" }.\\n\\n' +\n\t\t\t\"ACTION (for next_action) is the same shape as a flow step's action: { action: 'navigate', url }, \" +\n\t\t\t\"{ action: 'click', selector, fallbacks?: string[] }, { action: 'fill', selector, value_tpl }, \" +\n\t\t\t\"{ action: 'select', selector, value_tpl }, { action: 'wait_settle' }. Prefer stable CSS/id \" +\n\t\t\t\"selectors, and ALWAYS pass a `fallbacks` array of alternate selectors for click/fill, because \" +\n\t\t\t\"the primary selector often drifts (e.g. click '.suggestion-link' with fallbacks \" +\n\t\t\t\"['a.mw-searchSuggest-link', '#typeahead-suggestions a']).\\n\\n\" +\n\t\t\t\"VISIBILITY: pass headful:true to launch a real on-screen browser window the user can watch; \" +\n\t\t\t\"live_view:true additionally streams a mirror + tool-call log to a local URL (set live_view:false \" +\n\t\t\t\"to suppress the mirror when the instance defaults it on).\",\n\t\tpromptSnippet: \"Run a deterministic browser flow, pausing for LLM decisions when needed\",\n\t\tparameters: browserFlowSchema,\n\t\tasync execute(_toolCallId, params: BrowserFlowInput, signal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tif (!params.flow_path && !params.flow) {\n\t\t\t\tthrow new Error(\"browser_flow requires either `flow_path` or `flow`\");\n\t\t\t}\n\n\t\t\tconst binaryPath = await resolveBrowsertoolsBinary(options);\n\t\t\tconst headful = params.headful ?? opts.headful;\n\t\t\t// Reuse the shared idle client if browser config matches, otherwise create a new\n\t\t\t// one. This keeps a single Chromium process and live-view port across calls.\n\t\t\tlet client = takeIdleClient(headful, opts.browserPath, opts.idleTimeoutMs);\n\t\t\tif (!client) {\n\t\t\t\tclient = new BrowsertoolsServeClient(binaryPath, {\n\t\t\t\t\tcwd,\n\t\t\t\t\tbrowserPath: opts.browserPath,\n\t\t\t\t\tserveArgs: opts.serveArgs,\n\t\t\t\t\trequestTimeoutMs: opts.requestTimeoutMs,\n\t\t\t\t\theadful,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// If the call is aborted before we hand the client to the registry, make\n\t\t\t// sure the serve process is torn down.\n\t\t\tconst onAbort = () => client.dispose();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\ttry {\n\t\t\t\tconst startParams: Record<string, unknown> = {};\n\t\t\t\tif (params.flow_path) startParams.flow_path = params.flow_path;\n\t\t\t\tif (params.flow) startParams.flow = params.flow;\n\t\t\t\tif (params.vars) startParams.vars = params.vars;\n\t\t\t\tif (params.store) startParams.store = params.store;\n\n\t\t\t\t// Bring up the live viewer before the flow runs so the page render and\n\t\t\t\t// tool-call log are visible from the first step. The per-call param wins\n\t\t\t\t// over the instance default (--enable-browser-live-preview).\n\t\t\t\tconst liveViewEnabled = params.live_view ?? opts.liveView;\n\t\t\t\tconst liveViewStatus = liveViewEnabled ? await startLiveView(client) : undefined;\n\n\t\t\t\tconst outcome = await client.request<FlowOutcome>(\"flow_start\", startParams);\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tclient.dispose();\n\t\t\t\t\tthrow new Error(\"Operation aborted\");\n\t\t\t\t}\n\t\t\t\tconst browserConfig: BrowserClientConfig = {\n\t\t\t\t\theadful,\n\t\t\t\t\tbrowserPath: opts.browserPath,\n\t\t\t\t\tidleTimeoutMs: opts.idleTimeoutMs,\n\t\t\t\t};\n\t\t\t\tconst result = await advanceFlow(client, outcome, 1, opts, browserConfig);\n\t\t\t\tif (liveViewStatus) {\n\t\t\t\t\tresult.content.unshift({ type: \"text\", text: liveViewStatus });\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tclient.dispose();\n\t\t\t\tthrow enrichFlowError(error);\n\t\t\t} finally {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t});\n}\n\nexport function createBrowserFlowTool(\n\tcwd: string,\n\toptions?: BrowserFlowToolOptions,\n): AgentTool<typeof browserFlowSchema> {\n\treturn wrapToolDefinition(createBrowserFlowToolDefinition(cwd, options));\n}\n"]}
@@ -70,6 +70,76 @@ function parentResponseHint(kind) {
70
70
  return "Reply with browser_resume providing the appropriate ParentResponse object.";
71
71
  }
72
72
  }
73
+ /** Truncate a string to `max` chars with an ellipsis, for compact rendering. */
74
+ function clip(s, max = 80) {
75
+ const t = s.trim();
76
+ return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
77
+ }
78
+ /** Render one observation control as a single compact line. */
79
+ function renderControl(i) {
80
+ const sel = i.selector_hint && i.selector_hint !== "button" ? i.selector_hint : undefined;
81
+ const name = i.accessible_name ? `"${clip(i.accessible_name, 60)}"` : undefined;
82
+ const parts = [i.kind, name, sel].filter(Boolean);
83
+ return ` - ${parts.join(" ")}`;
84
+ }
85
+ /**
86
+ * Summarize a verbose `decide_next_action`/`classify_state` observation into a
87
+ * compact, readable block instead of dumping the raw JSON (which on busy pages is
88
+ * thousands of mostly-empty entries straight into the model context). The
89
+ * screenshot already conveys the page; this is just the actionable text layer:
90
+ * url/title, named controls, salient headings. Returns undefined when there is no
91
+ * observation to summarize.
92
+ */
93
+ function summarizeObservation(observation) {
94
+ if (!observation || typeof observation !== "object")
95
+ return undefined;
96
+ const lines = [];
97
+ if (observation.title)
98
+ lines.push(`page: ${clip(observation.title, 100)}`);
99
+ if (observation.url)
100
+ lines.push(`url: ${clip(observation.url, 120)}`);
101
+ if (observation.has_error_region)
102
+ lines.push("note: page has an error/alert region");
103
+ const controls = (observation.inputs ?? []).filter((i) => i.kind !== "_more");
104
+ if (controls.length) {
105
+ lines.push(`controls (${controls.length}):`);
106
+ for (const c of controls.slice(0, 25))
107
+ lines.push(renderControl(c));
108
+ if (controls.length > 25)
109
+ lines.push(` - …+${controls.length - 25} more`);
110
+ }
111
+ const text = (observation.text_blocks ?? []).filter((t) => t?.trim());
112
+ if (text.length) {
113
+ lines.push(`headings: ${text
114
+ .slice(0, 12)
115
+ .map((t) => clip(t, 50))
116
+ .join(" · ")}`);
117
+ }
118
+ return lines.length ? lines.join("\n") : undefined;
119
+ }
120
+ /**
121
+ * Build the compact, model-facing text for a `NeedsParent` suspension. Renders
122
+ * only the fields that matter per request kind (goal, expected_state, fields,
123
+ * description) plus a summarized observation, rather than pretty-printing the
124
+ * entire request — which is the dominant token sink for browser flows.
125
+ */
126
+ function formatParentRequest(request) {
127
+ const kind = request.request;
128
+ const lines = [`request: ${kind}`];
129
+ const r = request;
130
+ if (r.goal)
131
+ lines.push(`goal: ${r.goal}`);
132
+ if (r.expected_state)
133
+ lines.push(`expected_state: ${r.expected_state}`);
134
+ if (r.description)
135
+ lines.push(`element: ${r.description}`);
136
+ if (Array.isArray(r.fields) && r.fields.length)
137
+ lines.push(`fields: ${r.fields.join(", ")}`);
138
+ const summary = summarizeObservation(r.observation);
139
+ if (summary)
140
+ lines.push(summary);
141
+ return lines.join("\n");
142
+ }
73
143
  /** Compact, model-facing reminder of the inline-flow action schema, appended to
74
144
  * an `invalid inline flow` error so the model can self-correct in one turn
75
145
  * instead of guessing field shapes across several rounds. */
@@ -177,7 +247,7 @@ export async function advanceFlow(client, outcome, rounds, opts, browserConfig)
177
247
  const image = await fetchScreenshot(client, request);
178
248
  parkSession(token, client, rounds, browserConfig?.idleTimeoutMs ?? opts.idleTimeoutMs, browserConfig?.headful ?? false, browserConfig?.browserPath);
179
249
  const text = `Flow suspended — parent decision required (NeedsParent).\n` +
180
- `request: ${JSON.stringify(request, null, 2)}\n` +
250
+ `${formatParentRequest(request)}\n` +
181
251
  `resume token: ${token}\n` +
182
252
  `${parentResponseHint(request.request)}\n` +
183
253
  (image ? "A screenshot of the current page is attached." : "(no screenshot available)");
@@ -1 +1 @@
1
- {"version":3,"file":"browser-flow.js","sourceRoot":"","sources":["../../../src/core/tools/browser-flow.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `browser_flow` tool: start a deterministic browsertools flow.\n *\n * Spawns a `browsertools serve` process and issues `flow_start`. If the flow\n * runs to completion deterministically it returns the evidence inline. If replay\n * hits a point only an LLM can resolve, the serve process suspends with\n * `Outcome::NeedsParent`: this tool fetches the suspension screenshot, parks the\n * live session under its `ResumeToken`, and returns the typed `ParentRequest`\n * (plus the screenshot as an image) to the agent. The agent reasons and answers\n * with the companion `browser_resume` tool. See {@link browsertools-shared}.\n */\n\nimport { exec } from \"node:child_process\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport type { ImageContent, TextContent } from \"@kolisachint/hoocode-ai\";\nimport { type Static, Type } from \"typebox\";\nimport type { AgentToolResult } from \"../extensions/types.js\";\nimport { defineTool, type ToolDefinition } from \"../extensions/types.js\";\nimport {\n\ttype BrowserClientConfig,\n\tBrowsertoolsServeClient,\n\ttype BrowsertoolsToolOptions,\n\ttype FlowOutcome,\n\ttype GetResourceResult,\n\ttype ParentRequest,\n\tparkIdleClient,\n\tparkSession,\n\ttype ResumeToken,\n\tresolveBrowsertoolsBinary,\n\tresolveBrowsertoolsOptions,\n\ttakeIdleClient,\n} from \"./browsertools-shared.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\nconst browserFlowSchema = Type.Object({\n\tflow_path: Type.Optional(\n\t\tType.String({ description: \"Path to the .flow.json file to execute. Provide this or `flow`.\" }),\n\t),\n\tflow: Type.Optional(\n\t\tType.Record(Type.String(), Type.Unknown(), {\n\t\t\tdescription:\n\t\t\t\t\"Inline flow definition object (alternative to `flow_path`). Shape: \" +\n\t\t\t\t\"{ id: string, name: string, version: number, start_url: string, steps: Step[] }. \" +\n\t\t\t\t\"Each Step is { id: string, action: Action }. Action is a tagged object keyed by \" +\n\t\t\t\t\"`action`; the exact fields per variant (do NOT add extras like `goal` to the wrong \" +\n\t\t\t\t\"variant): \" +\n\t\t\t\t'{ action: \"navigate\", url: string } | ' +\n\t\t\t\t'{ action: \"click\", selector: string, fallbacks?: string[] } | ' +\n\t\t\t\t'{ action: \"fill\", selector: string, value_tpl: string } | ' +\n\t\t\t\t'{ action: \"select\", selector: string, value_tpl: string } | ' +\n\t\t\t\t'{ action: \"wait_settle\" } | ' +\n\t\t\t\t'{ action: \"checkpoint\", asserts: Invariant[] } | ' +\n\t\t\t\t'{ action: \"decide\", goal: string } | ' +\n\t\t\t\t'{ action: \"classify\" } | ' +\n\t\t\t\t'{ action: \"verify_visual\", expected_state: string } | ' +\n\t\t\t\t'{ action: \"extract_semantic\", fields: string[] }. ' +\n\t\t\t\t\"IMPORTANT: `extract_semantic.fields` MUST be an array of field-name strings \" +\n\t\t\t\t'(e.g. [\"person\", \"movie_count\"]), never an object/map, and `extract_semantic` takes ' +\n\t\t\t\t\"no `goal` (only `decide` does). Invariant (for checkpoint asserts) is one of: \" +\n\t\t\t\t'{ kind: \"element_present\", selector: string } | ' +\n\t\t\t\t'{ kind: \"text_present\", selector?: string, substr: string } | ' +\n\t\t\t\t'{ kind: \"url_matches\", pattern: string }.',\n\t\t}),\n\t),\n\tvars: Type.Optional(\n\t\tType.Record(Type.String(), Type.Unknown(), {\n\t\t\tdescription: \"Variables interpolated into the flow ({{var}} placeholders).\",\n\t\t}),\n\t),\n\tstore: Type.Optional(Type.String({ description: \"Path to the evidence store directory for this run.\" })),\n\tlive_view: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Start a live viewer that streams the page and the agent's tool-call log over a local \" +\n\t\t\t\t\"WebSocket, and auto-open it in your default browser. Set HOOCODE_BROWSERTOOLS_NO_OPEN=1 to \" +\n\t\t\t\t\"print the URL without opening. Best for flows that suspend or run long.\",\n\t\t}),\n\t),\n\theadful: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Launch a real on-screen Chromium window instead of a headless browser. Requires a desktop \" +\n\t\t\t\t\"display; unlike live_view it does not show the tool-call log.\",\n\t\t}),\n\t),\n});\n\nexport type BrowserFlowInput = Static<typeof browserFlowSchema>;\n\n/** Structured details surfaced alongside the model-facing content. */\nexport interface BrowserFlowDetails {\n\tstatus: \"complete\" | \"needs_parent\";\n\t/** Present when status is \"needs_parent\": resume with this token. */\n\ttoken?: ResumeToken;\n\t/** Present when status is \"needs_parent\": the kind of parent request. */\n\trequestKind?: ParentRequest[\"request\"];\n\t/** Present when status is \"complete\": the flow result/evidence. */\n\tresult?: unknown;\n}\n\nexport interface BrowserFlowToolOptions extends BrowsertoolsToolOptions {}\n\n/** ParentResponse shape hint per ParentRequest kind (mirrors contract.rs). */\nfunction parentResponseHint(kind: ParentRequest[\"request\"]): string {\n\tswitch (kind) {\n\t\tcase \"classify_state\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"state\", \"state\": \"<your label>\" }';\n\t\tcase \"verify_visual\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"verified\", \"passed\": true | false }';\n\t\tcase \"extract_semantic\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"extracted\", \"fields\": { \"<field>\": \"<value>\", ... } }';\n\t\tcase \"decide_next_action\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"next_action\", \"action\": <action object> }';\n\t\tcase \"reidentify_element\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"element\", \"selector\": \"<css selector>\" }';\n\t\tdefault:\n\t\t\treturn \"Reply with browser_resume providing the appropriate ParentResponse object.\";\n\t}\n}\n\n/** Compact, model-facing reminder of the inline-flow action schema, appended to\n * an `invalid inline flow` error so the model can self-correct in one turn\n * instead of guessing field shapes across several rounds. */\nconst FLOW_SCHEMA_HINT =\n\t\"Inline flow shape: { id, name, version, start_url, steps: [{ id, action }] }. \" +\n\t\"Action variants (use EXACTLY these fields): \" +\n\t'{ action: \"navigate\", url } | { action: \"click\", selector, fallbacks? } | ' +\n\t'{ action: \"fill\", selector, value_tpl } | { action: \"select\", selector, value_tpl } | ' +\n\t'{ action: \"wait_settle\" } | { action: \"checkpoint\", asserts } | ' +\n\t'{ action: \"decide\", goal } | { action: \"classify\" } | ' +\n\t'{ action: \"verify_visual\", expected_state } | { action: \"extract_semantic\", fields }. ' +\n\t'`extract_semantic.fields` MUST be a string array like [\"person\",\"count\"] (not an object), ' +\n\t\"and it takes no `goal` — only `decide` does.\";\n\n/** If an error is a browsertools inline-flow validation failure, append the\n * schema hint so the model fixes the flow on the next call. Other errors pass\n * through unchanged. */\nfunction enrichFlowError(error: unknown): unknown {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tif (message.includes(\"invalid inline flow\") && !message.includes(\"Action variants\")) {\n\t\treturn new Error(`${message}\\n\\n${FLOW_SCHEMA_HINT}`);\n\t}\n\treturn error;\n}\n\n/** Best-effort: open a URL in the OS default browser. Never throws. Suppressed by\n * HOOCODE_BROWSERTOOLS_NO_OPEN (the URL is still surfaced to the agent). */\nfunction openInBrowser(url: string): boolean {\n\tconst suppress = process.env.HOOCODE_BROWSERTOOLS_NO_OPEN?.trim();\n\tif (suppress === \"1\" || suppress?.toLowerCase() === \"true\") return false;\n\tconst openCmd = process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n\ttry {\n\t\texec(`${openCmd} \"${url}\"`);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Start the streamed live viewer and auto-open it. Best-effort: a failure here\n * must not abort the flow, so it degrades to returning undefined. Returns a\n * human-readable status line to prepend to the tool result, or undefined. */\nasync function startLiveView(client: BrowsertoolsServeClient): Promise<string | undefined> {\n\ttry {\n\t\tconst result = await client.request<{ url?: string; error?: string }>(\"live_view_start\", {});\n\t\tif (!result?.url) return undefined;\n\t\tconst opened = openInBrowser(result.url);\n\t\treturn opened ? `Live view opened in your browser: ${result.url}` : `Live view available at: ${result.url}`;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Fetch the suspension screenshot for a ParentRequest as an ImageContent block.\n * Best-effort: a fetch failure degrades to no image rather than failing the flow. */\nasync function fetchScreenshot(\n\tclient: BrowsertoolsServeClient,\n\trequest: ParentRequest,\n): Promise<ImageContent | undefined> {\n\tif (!request.screenshot_ref) return undefined;\n\ttry {\n\t\tconst resource = await client.request<GetResourceResult>(\"get_resource\", { ref: request.screenshot_ref });\n\t\tif (!resource?.png_base64) return undefined;\n\t\treturn { type: \"image\", data: resource.png_base64, mimeType: resource.mime || \"image/png\" };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Map a `flow_start`/`flow_resume` outcome to a tool result, owning the live\n * client: dispose it on terminal outcomes, or park it under the new resume token\n * when the flow suspends again. `rounds` is the number of NeedsParent yields seen\n * so far for this flow (including the one being processed), used for the cap.\n */\nexport async function advanceFlow(\n\tclient: BrowsertoolsServeClient,\n\toutcome: FlowOutcome,\n\trounds: number,\n\topts: ReturnType<typeof resolveBrowsertoolsOptions>,\n\tbrowserConfig?: BrowserClientConfig,\n): Promise<AgentToolResult<BrowserFlowDetails>> {\n\tif (outcome.outcome === \"complete\") {\n\t\tif (browserConfig) {\n\t\t\tparkIdleClient(client, browserConfig.headful, browserConfig.browserPath, browserConfig.idleTimeoutMs);\n\t\t} else {\n\t\t\tclient.dispose();\n\t\t}\n\t\tconst result = outcome.result;\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\", text: `Flow complete.\\n${JSON.stringify(result ?? {}, null, 2)}` }],\n\t\t\tdetails: { status: \"complete\", result },\n\t\t};\n\t}\n\n\tif (outcome.outcome === \"failed\") {\n\t\tif (browserConfig) {\n\t\t\tparkIdleClient(client, browserConfig.headful, browserConfig.browserPath, browserConfig.idleTimeoutMs);\n\t\t} else {\n\t\t\tclient.dispose();\n\t\t}\n\t\tconst where = outcome.step_id ? ` at step \"${outcome.step_id}\"` : \"\";\n\t\tconst kind = outcome.kind ? ` (${outcome.kind})` : \"\";\n\t\tthrow new Error(`browsertools flow failed${where}: ${outcome.detail ?? \"unknown error\"}${kind}`);\n\t}\n\n\tif (outcome.outcome === \"needs_parent\") {\n\t\tif (rounds > opts.maxParentRounds) {\n\t\t\tclient.dispose();\n\t\t\tthrow new Error(\n\t\t\t\t`browsertools flow exceeded the maximum of ${opts.maxParentRounds} NeedsParent rounds; aborting to avoid a runaway loop`,\n\t\t\t);\n\t\t}\n\t\tconst { request, token } = outcome;\n\t\tconst image = await fetchScreenshot(client, request);\n\t\tparkSession(\n\t\t\ttoken,\n\t\t\tclient,\n\t\t\trounds,\n\t\t\tbrowserConfig?.idleTimeoutMs ?? opts.idleTimeoutMs,\n\t\t\tbrowserConfig?.headful ?? false,\n\t\t\tbrowserConfig?.browserPath,\n\t\t);\n\n\t\tconst text =\n\t\t\t`Flow suspended — parent decision required (NeedsParent).\\n` +\n\t\t\t`request: ${JSON.stringify(request, null, 2)}\\n` +\n\t\t\t`resume token: ${token}\\n` +\n\t\t\t`${parentResponseHint(request.request)}\\n` +\n\t\t\t(image ? \"A screenshot of the current page is attached.\" : \"(no screenshot available)\");\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (image) content.push(image);\n\t\treturn {\n\t\t\tcontent,\n\t\t\tdetails: { status: \"needs_parent\", token, requestKind: request.request },\n\t\t};\n\t}\n\n\tclient.dispose();\n\tthrow new Error(`browsertools returned an unrecognized flow outcome: ${JSON.stringify(outcome)}`);\n}\n\nexport function createBrowserFlowToolDefinition(\n\tcwd: string,\n\toptions?: BrowserFlowToolOptions,\n): ToolDefinition<typeof browserFlowSchema, BrowserFlowDetails> {\n\tconst opts = resolveBrowsertoolsOptions(options);\n\treturn defineTool({\n\t\tname: \"browser_flow\",\n\t\tlabel: \"browser flow\",\n\t\tdescription:\n\t\t\t\"Start a deterministic browser flow (browsertools). Runs a saved .flow.json (or inline flow) \" +\n\t\t\t\"against a headless browser and returns the evidence on completion. If the flow needs an LLM \" +\n\t\t\t\"decision mid-replay (classify a page state, verify a visual, extract a value, decide the next \" +\n\t\t\t\"action, or re-identify a drifted element) it suspends and returns a typed request plus a \" +\n\t\t\t\"screenshot; answer it with the browser_resume tool using the returned token. Off by default; \" +\n\t\t\t\"enabled with --enable-browsertools.\\n\\n\" +\n\t\t\t\"AGENTIC LOOP (preferred for exploration): for any task where you must read or navigate based on \" +\n\t\t\t\"page content, build the flow from `decide`/`extract_semantic`/`classify`/`verify_visual` steps. \" +\n\t\t\t\"Each such step SUSPENDS and hands you a screenshot of the current page. Read the screenshot, then \" +\n\t\t\t\"call browser_resume with the next action, and keep looping until the outcome is `complete`. Do \" +\n\t\t\t\"NOT fall back to webfetch/curl to read page content you could read from the screenshot — that \" +\n\t\t\t\"bypasses the live session and breaks on auth-gated or JS-rendered pages. A flow ENDS as soon as \" +\n\t\t\t\"its last step runs, so chain several `decide` steps (interleaved with `wait_settle`) when you \" +\n\t\t\t\"need a multi-step journey (search -> open result -> scroll -> extract).\\n\\n\" +\n\t\t\t\"RESUME RESPONSE SHAPES (browser_resume `response` field): decide_next_action -> \" +\n\t\t\t'{ response: \"next_action\", action: <Action> }; classify_state -> { response: \"state\", state: \"<label>\" }; ' +\n\t\t\t'verify_visual -> { response: \"verified\", passed: true|false }; extract_semantic -> ' +\n\t\t\t'{ response: \"extracted\", fields: { <field>: <value> } }; reidentify_element -> ' +\n\t\t\t'{ response: \"element\", selector: \"<css>\" }.\\n\\n' +\n\t\t\t\"ACTION (for next_action) is the same shape as a flow step's action: { action: 'navigate', url }, \" +\n\t\t\t\"{ action: 'click', selector, fallbacks?: string[] }, { action: 'fill', selector, value_tpl }, \" +\n\t\t\t\"{ action: 'select', selector, value_tpl }, { action: 'wait_settle' }. Prefer stable CSS/id \" +\n\t\t\t\"selectors, and ALWAYS pass a `fallbacks` array of alternate selectors for click/fill, because \" +\n\t\t\t\"the primary selector often drifts (e.g. click '.suggestion-link' with fallbacks \" +\n\t\t\t\"['a.mw-searchSuggest-link', '#typeahead-suggestions a']).\\n\\n\" +\n\t\t\t\"VISIBILITY: pass headful:true to launch a real on-screen browser window the user can watch; \" +\n\t\t\t\"live_view:true additionally streams a mirror + tool-call log to a local URL (set live_view:false \" +\n\t\t\t\"to suppress the mirror when the instance defaults it on).\",\n\t\tpromptSnippet: \"Run a deterministic browser flow, pausing for LLM decisions when needed\",\n\t\tparameters: browserFlowSchema,\n\t\tasync execute(_toolCallId, params: BrowserFlowInput, signal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tif (!params.flow_path && !params.flow) {\n\t\t\t\tthrow new Error(\"browser_flow requires either `flow_path` or `flow`\");\n\t\t\t}\n\n\t\t\tconst binaryPath = await resolveBrowsertoolsBinary(options);\n\t\t\tconst headful = params.headful ?? opts.headful;\n\t\t\t// Reuse the shared idle client if browser config matches, otherwise create a new\n\t\t\t// one. This keeps a single Chromium process and live-view port across calls.\n\t\t\tlet client = takeIdleClient(headful, opts.browserPath, opts.idleTimeoutMs);\n\t\t\tif (!client) {\n\t\t\t\tclient = new BrowsertoolsServeClient(binaryPath, {\n\t\t\t\t\tcwd,\n\t\t\t\t\tbrowserPath: opts.browserPath,\n\t\t\t\t\tserveArgs: opts.serveArgs,\n\t\t\t\t\trequestTimeoutMs: opts.requestTimeoutMs,\n\t\t\t\t\theadful,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// If the call is aborted before we hand the client to the registry, make\n\t\t\t// sure the serve process is torn down.\n\t\t\tconst onAbort = () => client.dispose();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\ttry {\n\t\t\t\tconst startParams: Record<string, unknown> = {};\n\t\t\t\tif (params.flow_path) startParams.flow_path = params.flow_path;\n\t\t\t\tif (params.flow) startParams.flow = params.flow;\n\t\t\t\tif (params.vars) startParams.vars = params.vars;\n\t\t\t\tif (params.store) startParams.store = params.store;\n\n\t\t\t\t// Bring up the live viewer before the flow runs so the page render and\n\t\t\t\t// tool-call log are visible from the first step. The per-call param wins\n\t\t\t\t// over the instance default (--enable-browser-live-preview).\n\t\t\t\tconst liveViewEnabled = params.live_view ?? opts.liveView;\n\t\t\t\tconst liveViewStatus = liveViewEnabled ? await startLiveView(client) : undefined;\n\n\t\t\t\tconst outcome = await client.request<FlowOutcome>(\"flow_start\", startParams);\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tclient.dispose();\n\t\t\t\t\tthrow new Error(\"Operation aborted\");\n\t\t\t\t}\n\t\t\t\tconst browserConfig: BrowserClientConfig = {\n\t\t\t\t\theadful,\n\t\t\t\t\tbrowserPath: opts.browserPath,\n\t\t\t\t\tidleTimeoutMs: opts.idleTimeoutMs,\n\t\t\t\t};\n\t\t\t\tconst result = await advanceFlow(client, outcome, 1, opts, browserConfig);\n\t\t\t\tif (liveViewStatus) {\n\t\t\t\t\tresult.content.unshift({ type: \"text\", text: liveViewStatus });\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tclient.dispose();\n\t\t\t\tthrow enrichFlowError(error);\n\t\t\t} finally {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t});\n}\n\nexport function createBrowserFlowTool(\n\tcwd: string,\n\toptions?: BrowserFlowToolOptions,\n): AgentTool<typeof browserFlowSchema> {\n\treturn wrapToolDefinition(createBrowserFlowToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"browser-flow.js","sourceRoot":"","sources":["../../../src/core/tools/browser-flow.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `browser_flow` tool: start a deterministic browsertools flow.\n *\n * Spawns a `browsertools serve` process and issues `flow_start`. If the flow\n * runs to completion deterministically it returns the evidence inline. If replay\n * hits a point only an LLM can resolve, the serve process suspends with\n * `Outcome::NeedsParent`: this tool fetches the suspension screenshot, parks the\n * live session under its `ResumeToken`, and returns the typed `ParentRequest`\n * (plus the screenshot as an image) to the agent. The agent reasons and answers\n * with the companion `browser_resume` tool. See {@link browsertools-shared}.\n */\n\nimport { exec } from \"node:child_process\";\nimport type { AgentTool } from \"@kolisachint/hoocode-agent-core\";\nimport type { ImageContent, TextContent } from \"@kolisachint/hoocode-ai\";\nimport { type Static, Type } from \"typebox\";\nimport type { AgentToolResult } from \"../extensions/types.js\";\nimport { defineTool, type ToolDefinition } from \"../extensions/types.js\";\nimport {\n\ttype BrowserClientConfig,\n\tBrowsertoolsServeClient,\n\ttype BrowsertoolsToolOptions,\n\ttype FlowOutcome,\n\ttype GetResourceResult,\n\ttype ParentRequest,\n\tparkIdleClient,\n\tparkSession,\n\ttype ResumeToken,\n\tresolveBrowsertoolsBinary,\n\tresolveBrowsertoolsOptions,\n\ttakeIdleClient,\n} from \"./browsertools-shared.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\n\nconst browserFlowSchema = Type.Object({\n\tflow_path: Type.Optional(\n\t\tType.String({ description: \"Path to the .flow.json file to execute. Provide this or `flow`.\" }),\n\t),\n\tflow: Type.Optional(\n\t\tType.Record(Type.String(), Type.Unknown(), {\n\t\t\tdescription:\n\t\t\t\t\"Inline flow definition object (alternative to `flow_path`). Shape: \" +\n\t\t\t\t\"{ id: string, name: string, version: number, start_url: string, steps: Step[] }. \" +\n\t\t\t\t\"Each Step is { id: string, action: Action }. Action is a tagged object keyed by \" +\n\t\t\t\t\"`action`; the exact fields per variant (do NOT add extras like `goal` to the wrong \" +\n\t\t\t\t\"variant): \" +\n\t\t\t\t'{ action: \"navigate\", url: string } | ' +\n\t\t\t\t'{ action: \"click\", selector: string, fallbacks?: string[] } | ' +\n\t\t\t\t'{ action: \"fill\", selector: string, value_tpl: string } | ' +\n\t\t\t\t'{ action: \"select\", selector: string, value_tpl: string } | ' +\n\t\t\t\t'{ action: \"wait_settle\" } | ' +\n\t\t\t\t'{ action: \"checkpoint\", asserts: Invariant[] } | ' +\n\t\t\t\t'{ action: \"decide\", goal: string } | ' +\n\t\t\t\t'{ action: \"classify\" } | ' +\n\t\t\t\t'{ action: \"verify_visual\", expected_state: string } | ' +\n\t\t\t\t'{ action: \"extract_semantic\", fields: string[] }. ' +\n\t\t\t\t\"IMPORTANT: `extract_semantic.fields` MUST be an array of field-name strings \" +\n\t\t\t\t'(e.g. [\"person\", \"movie_count\"]), never an object/map, and `extract_semantic` takes ' +\n\t\t\t\t\"no `goal` (only `decide` does). Invariant (for checkpoint asserts) is one of: \" +\n\t\t\t\t'{ kind: \"element_present\", selector: string } | ' +\n\t\t\t\t'{ kind: \"text_present\", selector?: string, substr: string } | ' +\n\t\t\t\t'{ kind: \"url_matches\", pattern: string }.',\n\t\t}),\n\t),\n\tvars: Type.Optional(\n\t\tType.Record(Type.String(), Type.Unknown(), {\n\t\t\tdescription: \"Variables interpolated into the flow ({{var}} placeholders).\",\n\t\t}),\n\t),\n\tstore: Type.Optional(Type.String({ description: \"Path to the evidence store directory for this run.\" })),\n\tlive_view: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Start a live viewer that streams the page and the agent's tool-call log over a local \" +\n\t\t\t\t\"WebSocket, and auto-open it in your default browser. Set HOOCODE_BROWSERTOOLS_NO_OPEN=1 to \" +\n\t\t\t\t\"print the URL without opening. Best for flows that suspend or run long.\",\n\t\t}),\n\t),\n\theadful: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Launch a real on-screen Chromium window instead of a headless browser. Requires a desktop \" +\n\t\t\t\t\"display; unlike live_view it does not show the tool-call log.\",\n\t\t}),\n\t),\n});\n\nexport type BrowserFlowInput = Static<typeof browserFlowSchema>;\n\n/** Structured details surfaced alongside the model-facing content. */\nexport interface BrowserFlowDetails {\n\tstatus: \"complete\" | \"needs_parent\";\n\t/** Present when status is \"needs_parent\": resume with this token. */\n\ttoken?: ResumeToken;\n\t/** Present when status is \"needs_parent\": the kind of parent request. */\n\trequestKind?: ParentRequest[\"request\"];\n\t/** Present when status is \"complete\": the flow result/evidence. */\n\tresult?: unknown;\n}\n\nexport interface BrowserFlowToolOptions extends BrowsertoolsToolOptions {}\n\n/** ParentResponse shape hint per ParentRequest kind (mirrors contract.rs). */\nfunction parentResponseHint(kind: ParentRequest[\"request\"]): string {\n\tswitch (kind) {\n\t\tcase \"classify_state\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"state\", \"state\": \"<your label>\" }';\n\t\tcase \"verify_visual\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"verified\", \"passed\": true | false }';\n\t\tcase \"extract_semantic\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"extracted\", \"fields\": { \"<field>\": \"<value>\", ... } }';\n\t\tcase \"decide_next_action\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"next_action\", \"action\": <action object> }';\n\t\tcase \"reidentify_element\":\n\t\t\treturn 'Reply with browser_resume response: { \"response\": \"element\", \"selector\": \"<css selector>\" }';\n\t\tdefault:\n\t\t\treturn \"Reply with browser_resume providing the appropriate ParentResponse object.\";\n\t}\n}\n\n/** A control fact from the browsertools observation (mirrors observe.rs InputFact). */\ninterface ObservationInput {\n\tkind?: string;\n\taccessible_name?: string;\n\tselector_hint?: string;\n}\n\n/** A landmark fact from the browsertools observation (mirrors observe.rs Landmark). */\ninterface ObservationLandmark {\n\trole?: string;\n\tname?: string;\n}\n\n/** The HTML-derived observation the engine attaches to decide/classify requests. */\ninterface Observation {\n\turl?: string;\n\ttitle?: string;\n\tinputs?: ObservationInput[];\n\tlandmarks?: ObservationLandmark[];\n\ttext_blocks?: string[];\n\thas_error_region?: boolean;\n}\n\n/** Truncate a string to `max` chars with an ellipsis, for compact rendering. */\nfunction clip(s: string, max = 80): string {\n\tconst t = s.trim();\n\treturn t.length > max ? `${t.slice(0, max - 1)}\\u2026` : t;\n}\n\n/** Render one observation control as a single compact line. */\nfunction renderControl(i: ObservationInput): string {\n\tconst sel = i.selector_hint && i.selector_hint !== \"button\" ? i.selector_hint : undefined;\n\tconst name = i.accessible_name ? `\"${clip(i.accessible_name, 60)}\"` : undefined;\n\tconst parts = [i.kind, name, sel].filter(Boolean);\n\treturn ` - ${parts.join(\" \")}`;\n}\n\n/**\n * Summarize a verbose `decide_next_action`/`classify_state` observation into a\n * compact, readable block instead of dumping the raw JSON (which on busy pages is\n * thousands of mostly-empty entries straight into the model context). The\n * screenshot already conveys the page; this is just the actionable text layer:\n * url/title, named controls, salient headings. Returns undefined when there is no\n * observation to summarize.\n */\nfunction summarizeObservation(observation: Observation | undefined): string | undefined {\n\tif (!observation || typeof observation !== \"object\") return undefined;\n\tconst lines: string[] = [];\n\tif (observation.title) lines.push(`page: ${clip(observation.title, 100)}`);\n\tif (observation.url) lines.push(`url: ${clip(observation.url, 120)}`);\n\tif (observation.has_error_region) lines.push(\"note: page has an error/alert region\");\n\n\tconst controls = (observation.inputs ?? []).filter((i) => i.kind !== \"_more\");\n\tif (controls.length) {\n\t\tlines.push(`controls (${controls.length}):`);\n\t\tfor (const c of controls.slice(0, 25)) lines.push(renderControl(c));\n\t\tif (controls.length > 25) lines.push(` - …+${controls.length - 25} more`);\n\t}\n\n\tconst text = (observation.text_blocks ?? []).filter((t) => t?.trim());\n\tif (text.length) {\n\t\tlines.push(\n\t\t\t`headings: ${text\n\t\t\t\t.slice(0, 12)\n\t\t\t\t.map((t) => clip(t, 50))\n\t\t\t\t.join(\" · \")}`,\n\t\t);\n\t}\n\treturn lines.length ? lines.join(\"\\n\") : undefined;\n}\n\n/**\n * Build the compact, model-facing text for a `NeedsParent` suspension. Renders\n * only the fields that matter per request kind (goal, expected_state, fields,\n * description) plus a summarized observation, rather than pretty-printing the\n * entire request — which is the dominant token sink for browser flows.\n */\nfunction formatParentRequest(request: ParentRequest): string {\n\tconst kind = request.request;\n\tconst lines: string[] = [`request: ${kind}`];\n\tconst r = request as ParentRequest & {\n\t\tgoal?: string;\n\t\texpected_state?: string;\n\t\tdescription?: string;\n\t\tfields?: string[];\n\t\tobservation?: Observation;\n\t};\n\tif (r.goal) lines.push(`goal: ${r.goal}`);\n\tif (r.expected_state) lines.push(`expected_state: ${r.expected_state}`);\n\tif (r.description) lines.push(`element: ${r.description}`);\n\tif (Array.isArray(r.fields) && r.fields.length) lines.push(`fields: ${r.fields.join(\", \")}`);\n\tconst summary = summarizeObservation(r.observation);\n\tif (summary) lines.push(summary);\n\treturn lines.join(\"\\n\");\n}\n\n/** Compact, model-facing reminder of the inline-flow action schema, appended to\n * an `invalid inline flow` error so the model can self-correct in one turn\n * instead of guessing field shapes across several rounds. */\nconst FLOW_SCHEMA_HINT =\n\t\"Inline flow shape: { id, name, version, start_url, steps: [{ id, action }] }. \" +\n\t\"Action variants (use EXACTLY these fields): \" +\n\t'{ action: \"navigate\", url } | { action: \"click\", selector, fallbacks? } | ' +\n\t'{ action: \"fill\", selector, value_tpl } | { action: \"select\", selector, value_tpl } | ' +\n\t'{ action: \"wait_settle\" } | { action: \"checkpoint\", asserts } | ' +\n\t'{ action: \"decide\", goal } | { action: \"classify\" } | ' +\n\t'{ action: \"verify_visual\", expected_state } | { action: \"extract_semantic\", fields }. ' +\n\t'`extract_semantic.fields` MUST be a string array like [\"person\",\"count\"] (not an object), ' +\n\t\"and it takes no `goal` — only `decide` does.\";\n\n/** If an error is a browsertools inline-flow validation failure, append the\n * schema hint so the model fixes the flow on the next call. Other errors pass\n * through unchanged. */\nfunction enrichFlowError(error: unknown): unknown {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tif (message.includes(\"invalid inline flow\") && !message.includes(\"Action variants\")) {\n\t\treturn new Error(`${message}\\n\\n${FLOW_SCHEMA_HINT}`);\n\t}\n\treturn error;\n}\n\n/** Best-effort: open a URL in the OS default browser. Never throws. Suppressed by\n * HOOCODE_BROWSERTOOLS_NO_OPEN (the URL is still surfaced to the agent). */\nfunction openInBrowser(url: string): boolean {\n\tconst suppress = process.env.HOOCODE_BROWSERTOOLS_NO_OPEN?.trim();\n\tif (suppress === \"1\" || suppress?.toLowerCase() === \"true\") return false;\n\tconst openCmd = process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"start\" : \"xdg-open\";\n\ttry {\n\t\texec(`${openCmd} \"${url}\"`);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Start the streamed live viewer and auto-open it. Best-effort: a failure here\n * must not abort the flow, so it degrades to returning undefined. Returns a\n * human-readable status line to prepend to the tool result, or undefined. */\nasync function startLiveView(client: BrowsertoolsServeClient): Promise<string | undefined> {\n\ttry {\n\t\tconst result = await client.request<{ url?: string; error?: string }>(\"live_view_start\", {});\n\t\tif (!result?.url) return undefined;\n\t\tconst opened = openInBrowser(result.url);\n\t\treturn opened ? `Live view opened in your browser: ${result.url}` : `Live view available at: ${result.url}`;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Fetch the suspension screenshot for a ParentRequest as an ImageContent block.\n * Best-effort: a fetch failure degrades to no image rather than failing the flow. */\nasync function fetchScreenshot(\n\tclient: BrowsertoolsServeClient,\n\trequest: ParentRequest,\n): Promise<ImageContent | undefined> {\n\tif (!request.screenshot_ref) return undefined;\n\ttry {\n\t\tconst resource = await client.request<GetResourceResult>(\"get_resource\", { ref: request.screenshot_ref });\n\t\tif (!resource?.png_base64) return undefined;\n\t\treturn { type: \"image\", data: resource.png_base64, mimeType: resource.mime || \"image/png\" };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Map a `flow_start`/`flow_resume` outcome to a tool result, owning the live\n * client: dispose it on terminal outcomes, or park it under the new resume token\n * when the flow suspends again. `rounds` is the number of NeedsParent yields seen\n * so far for this flow (including the one being processed), used for the cap.\n */\nexport async function advanceFlow(\n\tclient: BrowsertoolsServeClient,\n\toutcome: FlowOutcome,\n\trounds: number,\n\topts: ReturnType<typeof resolveBrowsertoolsOptions>,\n\tbrowserConfig?: BrowserClientConfig,\n): Promise<AgentToolResult<BrowserFlowDetails>> {\n\tif (outcome.outcome === \"complete\") {\n\t\tif (browserConfig) {\n\t\t\tparkIdleClient(client, browserConfig.headful, browserConfig.browserPath, browserConfig.idleTimeoutMs);\n\t\t} else {\n\t\t\tclient.dispose();\n\t\t}\n\t\tconst result = outcome.result;\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\", text: `Flow complete.\\n${JSON.stringify(result ?? {}, null, 2)}` }],\n\t\t\tdetails: { status: \"complete\", result },\n\t\t};\n\t}\n\n\tif (outcome.outcome === \"failed\") {\n\t\tif (browserConfig) {\n\t\t\tparkIdleClient(client, browserConfig.headful, browserConfig.browserPath, browserConfig.idleTimeoutMs);\n\t\t} else {\n\t\t\tclient.dispose();\n\t\t}\n\t\tconst where = outcome.step_id ? ` at step \"${outcome.step_id}\"` : \"\";\n\t\tconst kind = outcome.kind ? ` (${outcome.kind})` : \"\";\n\t\tthrow new Error(`browsertools flow failed${where}: ${outcome.detail ?? \"unknown error\"}${kind}`);\n\t}\n\n\tif (outcome.outcome === \"needs_parent\") {\n\t\tif (rounds > opts.maxParentRounds) {\n\t\t\tclient.dispose();\n\t\t\tthrow new Error(\n\t\t\t\t`browsertools flow exceeded the maximum of ${opts.maxParentRounds} NeedsParent rounds; aborting to avoid a runaway loop`,\n\t\t\t);\n\t\t}\n\t\tconst { request, token } = outcome;\n\t\tconst image = await fetchScreenshot(client, request);\n\t\tparkSession(\n\t\t\ttoken,\n\t\t\tclient,\n\t\t\trounds,\n\t\t\tbrowserConfig?.idleTimeoutMs ?? opts.idleTimeoutMs,\n\t\t\tbrowserConfig?.headful ?? false,\n\t\t\tbrowserConfig?.browserPath,\n\t\t);\n\n\t\tconst text =\n\t\t\t`Flow suspended — parent decision required (NeedsParent).\\n` +\n\t\t\t`${formatParentRequest(request)}\\n` +\n\t\t\t`resume token: ${token}\\n` +\n\t\t\t`${parentResponseHint(request.request)}\\n` +\n\t\t\t(image ? \"A screenshot of the current page is attached.\" : \"(no screenshot available)\");\n\t\tconst content: (TextContent | ImageContent)[] = [{ type: \"text\", text }];\n\t\tif (image) content.push(image);\n\t\treturn {\n\t\t\tcontent,\n\t\t\tdetails: { status: \"needs_parent\", token, requestKind: request.request },\n\t\t};\n\t}\n\n\tclient.dispose();\n\tthrow new Error(`browsertools returned an unrecognized flow outcome: ${JSON.stringify(outcome)}`);\n}\n\nexport function createBrowserFlowToolDefinition(\n\tcwd: string,\n\toptions?: BrowserFlowToolOptions,\n): ToolDefinition<typeof browserFlowSchema, BrowserFlowDetails> {\n\tconst opts = resolveBrowsertoolsOptions(options);\n\treturn defineTool({\n\t\tname: \"browser_flow\",\n\t\tlabel: \"browser flow\",\n\t\tdescription:\n\t\t\t\"Start a deterministic browser flow (browsertools). Runs a saved .flow.json (or inline flow) \" +\n\t\t\t\"against a headless browser and returns the evidence on completion. If the flow needs an LLM \" +\n\t\t\t\"decision mid-replay (classify a page state, verify a visual, extract a value, decide the next \" +\n\t\t\t\"action, or re-identify a drifted element) it suspends and returns a typed request plus a \" +\n\t\t\t\"screenshot; answer it with the browser_resume tool using the returned token. Off by default; \" +\n\t\t\t\"enabled with --enable-browsertools.\\n\\n\" +\n\t\t\t\"AGENTIC LOOP (preferred for exploration): for any task where you must read or navigate based on \" +\n\t\t\t\"page content, build the flow from `decide`/`extract_semantic`/`classify`/`verify_visual` steps. \" +\n\t\t\t\"Each such step SUSPENDS and hands you a screenshot of the current page. Read the screenshot, then \" +\n\t\t\t\"call browser_resume with the next action, and keep looping until the outcome is `complete`. Do \" +\n\t\t\t\"NOT fall back to webfetch/curl to read page content you could read from the screenshot — that \" +\n\t\t\t\"bypasses the live session and breaks on auth-gated or JS-rendered pages. A flow ENDS as soon as \" +\n\t\t\t\"its last step runs, so chain several `decide` steps (interleaved with `wait_settle`) when you \" +\n\t\t\t\"need a multi-step journey (search -> open result -> scroll -> extract).\\n\\n\" +\n\t\t\t\"RESUME RESPONSE SHAPES (browser_resume `response` field): decide_next_action -> \" +\n\t\t\t'{ response: \"next_action\", action: <Action> }; classify_state -> { response: \"state\", state: \"<label>\" }; ' +\n\t\t\t'verify_visual -> { response: \"verified\", passed: true|false }; extract_semantic -> ' +\n\t\t\t'{ response: \"extracted\", fields: { <field>: <value> } }; reidentify_element -> ' +\n\t\t\t'{ response: \"element\", selector: \"<css>\" }.\\n\\n' +\n\t\t\t\"ACTION (for next_action) is the same shape as a flow step's action: { action: 'navigate', url }, \" +\n\t\t\t\"{ action: 'click', selector, fallbacks?: string[] }, { action: 'fill', selector, value_tpl }, \" +\n\t\t\t\"{ action: 'select', selector, value_tpl }, { action: 'wait_settle' }. Prefer stable CSS/id \" +\n\t\t\t\"selectors, and ALWAYS pass a `fallbacks` array of alternate selectors for click/fill, because \" +\n\t\t\t\"the primary selector often drifts (e.g. click '.suggestion-link' with fallbacks \" +\n\t\t\t\"['a.mw-searchSuggest-link', '#typeahead-suggestions a']).\\n\\n\" +\n\t\t\t\"VISIBILITY: pass headful:true to launch a real on-screen browser window the user can watch; \" +\n\t\t\t\"live_view:true additionally streams a mirror + tool-call log to a local URL (set live_view:false \" +\n\t\t\t\"to suppress the mirror when the instance defaults it on).\",\n\t\tpromptSnippet: \"Run a deterministic browser flow, pausing for LLM decisions when needed\",\n\t\tparameters: browserFlowSchema,\n\t\tasync execute(_toolCallId, params: BrowserFlowInput, signal) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tif (!params.flow_path && !params.flow) {\n\t\t\t\tthrow new Error(\"browser_flow requires either `flow_path` or `flow`\");\n\t\t\t}\n\n\t\t\tconst binaryPath = await resolveBrowsertoolsBinary(options);\n\t\t\tconst headful = params.headful ?? opts.headful;\n\t\t\t// Reuse the shared idle client if browser config matches, otherwise create a new\n\t\t\t// one. This keeps a single Chromium process and live-view port across calls.\n\t\t\tlet client = takeIdleClient(headful, opts.browserPath, opts.idleTimeoutMs);\n\t\t\tif (!client) {\n\t\t\t\tclient = new BrowsertoolsServeClient(binaryPath, {\n\t\t\t\t\tcwd,\n\t\t\t\t\tbrowserPath: opts.browserPath,\n\t\t\t\t\tserveArgs: opts.serveArgs,\n\t\t\t\t\trequestTimeoutMs: opts.requestTimeoutMs,\n\t\t\t\t\theadful,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// If the call is aborted before we hand the client to the registry, make\n\t\t\t// sure the serve process is torn down.\n\t\t\tconst onAbort = () => client.dispose();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\ttry {\n\t\t\t\tconst startParams: Record<string, unknown> = {};\n\t\t\t\tif (params.flow_path) startParams.flow_path = params.flow_path;\n\t\t\t\tif (params.flow) startParams.flow = params.flow;\n\t\t\t\tif (params.vars) startParams.vars = params.vars;\n\t\t\t\tif (params.store) startParams.store = params.store;\n\n\t\t\t\t// Bring up the live viewer before the flow runs so the page render and\n\t\t\t\t// tool-call log are visible from the first step. The per-call param wins\n\t\t\t\t// over the instance default (--enable-browser-live-preview).\n\t\t\t\tconst liveViewEnabled = params.live_view ?? opts.liveView;\n\t\t\t\tconst liveViewStatus = liveViewEnabled ? await startLiveView(client) : undefined;\n\n\t\t\t\tconst outcome = await client.request<FlowOutcome>(\"flow_start\", startParams);\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tclient.dispose();\n\t\t\t\t\tthrow new Error(\"Operation aborted\");\n\t\t\t\t}\n\t\t\t\tconst browserConfig: BrowserClientConfig = {\n\t\t\t\t\theadful,\n\t\t\t\t\tbrowserPath: opts.browserPath,\n\t\t\t\t\tidleTimeoutMs: opts.idleTimeoutMs,\n\t\t\t\t};\n\t\t\t\tconst result = await advanceFlow(client, outcome, 1, opts, browserConfig);\n\t\t\t\tif (liveViewStatus) {\n\t\t\t\t\tresult.content.unshift({ type: \"text\", text: liveViewStatus });\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tclient.dispose();\n\t\t\t\tthrow enrichFlowError(error);\n\t\t\t} finally {\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t});\n}\n\nexport function createBrowserFlowTool(\n\tcwd: string,\n\toptions?: BrowserFlowToolOptions,\n): AgentTool<typeof browserFlowSchema> {\n\treturn wrapToolDefinition(createBrowserFlowToolDefinition(cwd, options));\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.2.96",
4
+ "version": "0.2.97",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.2.96",
4
+ "version": "0.2.97",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.2.96",
4
+ "version": "0.2.97",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.2.96",
4
+ "version": "0.2.97",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-agent",
3
- "version": "0.4.99",
3
+ "version": "0.4.100",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "hoocodeConfig": {
@@ -45,9 +45,9 @@
45
45
  "prepublishOnly": "npm run clean && npm run build"
46
46
  },
47
47
  "dependencies": {
48
- "@kolisachint/hoocode-agent-core": "^0.4.99",
49
- "@kolisachint/hoocode-ai": "^0.4.99",
50
- "@kolisachint/hoocode-tui": "^0.4.99",
48
+ "@kolisachint/hoocode-agent-core": "^0.4.100",
49
+ "@kolisachint/hoocode-ai": "^0.4.100",
50
+ "@kolisachint/hoocode-tui": "^0.4.100",
51
51
  "@silvia-odwyer/photon-node": "^0.3.4",
52
52
  "chalk": "^5.5.0",
53
53
  "cli-highlight": "^2.1.11",