@q-agent/agent 0.1.9 → 0.1.11

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.
@@ -854,12 +854,40 @@ async function processExplorationJob(cfg, session) {
854
854
  const args = [script, session.baseUrl];
855
855
  if (storageState)
856
856
  args.push(storageState);
857
- const child = (0, child_process_1.spawn)((0, paths_1.nodeBin)(), args, { cwd: nm, env: nodePathEnv(nm) });
857
+ // Run the explore browser HEADED on the paired device: the user watches the
858
+ // session, and a headed browser dodges WAF/bot-protection that blocks headless.
859
+ const child = (0, child_process_1.spawn)((0, paths_1.nodeBin)(), args, {
860
+ cwd: nm,
861
+ env: { ...nodePathEnv(nm), QAGENT_EXPLORE_HEADED: "1" },
862
+ });
858
863
  activeChild = child;
864
+ // Capture the driver's stderr so a launch failure (missing browser, bad
865
+ // Playwright resolution, …) is visible instead of a silent "complete".
866
+ let driverStderr = "";
867
+ child.stderr?.on("data", (d) => {
868
+ driverStderr += String(d);
869
+ });
859
870
  const driver = makeExploreDriver(child);
860
871
  try {
861
- // Wait for the driver's readiness signal before the first observe.
862
- await driver.ready();
872
+ // Wait for the driver's readiness signal before the first observe. If the
873
+ // driver died before signalling ready, finalize with a clear driver-error
874
+ // (carrying its stderr) rather than letting the loop break silently.
875
+ const ready = await driver.ready();
876
+ if (!ready || ready.ok === false) {
877
+ const error = (ready && ready.error) || "driver failed to start";
878
+ const detail = driverStderr.trim();
879
+ await api
880
+ .postExploreFinalize(cfg, session.sessionId, {
881
+ discovered: { routes: [], selectors: [] },
882
+ log: [{ phase: "driver", error, stderr: detail || undefined }],
883
+ stopReason: "driver-error",
884
+ stepsTaken: 0,
885
+ })
886
+ .catch((err) => console.error("postExploreFinalize failed:", err));
887
+ (0, bus_1.emit)("explore-complete", { sessionId: session.sessionId, stopReason: "driver-error", error });
888
+ console.error(`Exploration ${session.sessionId} driver-error: ${error}${detail ? ` — ${detail}` : ""}`);
889
+ return;
890
+ }
863
891
  await runExplorationLoop(cfg, session, driver, api, bus_1.emit);
864
892
  }
865
893
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q-agent/agent",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Q-Agent Local Agent — claims execution jobs from the Q-Agent server and runs Playwright locally, so manual login/MFA happens on the user's own machine and session credentials never leave it.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  //
19
19
  // Protocol (one JSON object per line, each way):
20
20
  // {"cmd":"observe"}
21
- // -> {"ok":true,"url":..,"path":..,"a11y":<accessibility.snapshot()>,
21
+ // -> {"ok":true,"url":..,"path":..,"a11y":<ariaSnapshot() role+name tree>,
22
22
  // "elements":[<distilled interactive DOM>]}
23
23
  // {"cmd":"act","action":"goto|click|fill|expectVisible","args":{...}}
24
24
  // -> {"ok":bool,"error":str|null,"changed":bool}
@@ -121,7 +121,11 @@ async function doAct(action, args) {
121
121
  }
122
122
 
123
123
  async function doObserve() {
124
- const a11y = await page.accessibility.snapshot();
124
+ // Playwright removed page.accessibility; the supported role+name tree is
125
+ // locator.ariaSnapshot() (a compact YAML string, ideal for the model and
126
+ // maps directly to getByRole). Best-effort — never fail observe on it.
127
+ let a11y = '';
128
+ try { a11y = await page.locator('body').ariaSnapshot(); } catch { a11y = ''; }
125
129
  const elements = await page.evaluate(DISTILL);
126
130
  const url = page.url();
127
131
  let path = '';
@@ -155,12 +159,21 @@ async function handle(line) {
155
159
 
156
160
  (async () => {
157
161
  if (!baseURL) { console.error('explore_session: missing baseURL arg'); process.exit(1); }
158
- browser = await chromium.launch({ headless: true });
162
+ // Headed when QAGENT_EXPLORE_HEADED=1 (the Local Agent sets it so the user can
163
+ // watch, and a headed browser trips WAF/bot-protection far less than headless);
164
+ // headless otherwise (e.g. the server, which has no display).
165
+ browser = await chromium.launch({ headless: process.env.QAGENT_EXPLORE_HEADED !== '1' });
159
166
  const contextOpts = { baseURL };
160
167
  if (storageState) contextOpts.storageState = storageState;
161
168
  const context = await browser.newContext(contextOpts);
162
169
  page = await context.newPage();
163
170
 
171
+ // Land on the app before the first observe so step 1 sees the real page,
172
+ // not about:blank. Best-effort — the model can still goto elsewhere.
173
+ if (baseURL) {
174
+ try { await page.goto(baseURL, { waitUntil: 'domcontentloaded' }); } catch {}
175
+ }
176
+
164
177
  // Serialize commands: one line in -> one response out, in order.
165
178
  const rl = readline.createInterface({ input: process.stdin });
166
179
  let chain = Promise.resolve();