@petukhovart/agent-view 0.16.1 → 0.17.1

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.
Files changed (82) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +5 -3
  3. package/agents/act-decider.md +40 -0
  4. package/dist/cdp/transport.d.ts +2 -0
  5. package/dist/cdp/transport.d.ts.map +1 -1
  6. package/dist/cdp/transport.js +168 -9
  7. package/dist/cdp/transport.js.map +1 -1
  8. package/dist/cdp/types.d.ts +44 -1
  9. package/dist/cdp/types.d.ts.map +1 -1
  10. package/dist/cdp/types.js.map +1 -1
  11. package/dist/cli/client.d.ts.map +1 -1
  12. package/dist/cli/client.js +1 -3
  13. package/dist/cli/client.js.map +1 -1
  14. package/dist/cli/commands/act.d.ts +3 -0
  15. package/dist/cli/commands/act.d.ts.map +1 -0
  16. package/dist/cli/commands/act.js +19 -0
  17. package/dist/cli/commands/act.js.map +1 -0
  18. package/dist/cli/commands/click.d.ts +2 -0
  19. package/dist/cli/commands/click.d.ts.map +1 -1
  20. package/dist/cli/commands/click.js +9 -4
  21. package/dist/cli/commands/click.js.map +1 -1
  22. package/dist/cli/commands/dom.d.ts.map +1 -1
  23. package/dist/cli/commands/dom.js +1 -0
  24. package/dist/cli/commands/dom.js.map +1 -1
  25. package/dist/cli/commands/fill.d.ts +2 -0
  26. package/dist/cli/commands/fill.d.ts.map +1 -1
  27. package/dist/cli/commands/fill.js +11 -4
  28. package/dist/cli/commands/fill.js.map +1 -1
  29. package/dist/cli/commands/screenshot.d.ts +2 -0
  30. package/dist/cli/commands/screenshot.d.ts.map +1 -1
  31. package/dist/cli/commands/screenshot.js +10 -1
  32. package/dist/cli/commands/screenshot.js.map +1 -1
  33. package/dist/cli/commands/wait.d.ts +3 -1
  34. package/dist/cli/commands/wait.d.ts.map +1 -1
  35. package/dist/cli/commands/wait.js +6 -2
  36. package/dist/cli/commands/wait.js.map +1 -1
  37. package/dist/cli/commands/watch.js +1 -1
  38. package/dist/cli/commands/watch.js.map +1 -1
  39. package/dist/cli/index.js +46 -4
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/config/manager.d.ts.map +1 -1
  42. package/dist/config/manager.js +4 -0
  43. package/dist/config/manager.js.map +1 -1
  44. package/dist/config/types.d.ts +5 -0
  45. package/dist/config/types.d.ts.map +1 -1
  46. package/dist/inspectors/controls/index.d.ts +39 -0
  47. package/dist/inspectors/controls/index.d.ts.map +1 -0
  48. package/dist/inspectors/controls/index.js +140 -0
  49. package/dist/inspectors/controls/index.js.map +1 -0
  50. package/dist/inspectors/dom/index.d.ts +2 -0
  51. package/dist/inspectors/dom/index.d.ts.map +1 -1
  52. package/dist/inspectors/dom/index.js +21 -14
  53. package/dist/inspectors/dom/index.js.map +1 -1
  54. package/dist/server/act-replay.d.ts +23 -0
  55. package/dist/server/act-replay.d.ts.map +1 -0
  56. package/dist/server/act-replay.js +143 -0
  57. package/dist/server/act-replay.js.map +1 -0
  58. package/dist/server/act-script.d.ts +46 -0
  59. package/dist/server/act-script.d.ts.map +1 -0
  60. package/dist/server/act-script.js +46 -0
  61. package/dist/server/act-script.js.map +1 -0
  62. package/dist/server/act-session.d.ts +23 -0
  63. package/dist/server/act-session.d.ts.map +1 -0
  64. package/dist/server/act-session.js +304 -0
  65. package/dist/server/act-session.js.map +1 -0
  66. package/dist/server/locator.d.ts +19 -0
  67. package/dist/server/locator.d.ts.map +1 -0
  68. package/dist/server/locator.js +33 -0
  69. package/dist/server/locator.js.map +1 -0
  70. package/dist/server/port.d.ts +7 -0
  71. package/dist/server/port.d.ts.map +1 -0
  72. package/dist/server/port.js +19 -0
  73. package/dist/server/port.js.map +1 -0
  74. package/dist/server/server.d.ts +1 -0
  75. package/dist/server/server.d.ts.map +1 -1
  76. package/dist/server/server.js +76 -18
  77. package/dist/server/server.js.map +1 -1
  78. package/dist/types.d.ts +2 -0
  79. package/dist/types.d.ts.map +1 -1
  80. package/package.json +2 -1
  81. package/skills/verify/SKILL.md +12 -20
  82. package/skills/verify/references/commands.md +118 -4
@@ -0,0 +1,19 @@
1
+ import type { PageSession } from '../cdp/types.js';
2
+ export declare function testIdAttributes(configured: string | undefined): readonly string[];
3
+ export type Locator = {
4
+ css: string;
5
+ label: string;
6
+ };
7
+ export declare function locatorFromArgs(args: Record<string, unknown>): Locator | undefined;
8
+ export declare function testIdLocator(testid: string, testIdAttribute: string | undefined): Locator;
9
+ /**
10
+ * The first visible element a locator names. The label says when it was one of
11
+ * several, since acting on the first of many usually means the locator is too broad.
12
+ */
13
+ export declare function findByLocator(conn: PageSession, locator: Locator): Promise<{
14
+ backendDOMNodeId: number;
15
+ label: string;
16
+ } | {
17
+ error: string;
18
+ }>;
19
+ //# sourceMappingURL=locator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locator.d.ts","sourceRoot":"","sources":["../../src/server/locator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AASlD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,MAAM,EAAE,CAGlF;AAED,MAAM,MAAM,OAAO,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,SAAS,CAIlF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAG1F;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC;IAAE,gBAAgB,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAK1E"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Attributes tried when `testIdAttribute` is not configured, in order. Projects
3
+ * rarely set more than one, so accepting all of them spares every project the
4
+ * config line.
5
+ */
6
+ const DEFAULT_TEST_ID_ATTRIBUTES = ['data-testid', 'data-test-id', 'data-test', 'data-qa', 'data-cy'];
7
+ export function testIdAttributes(configured) {
8
+ // The DOM reports attribute names lowercased; a camel-cased config would match nothing.
9
+ return configured ? [configured.toLowerCase()] : DEFAULT_TEST_ID_ATTRIBUTES;
10
+ }
11
+ export function locatorFromArgs(args) {
12
+ const { testid, selector, testIdAttribute } = args;
13
+ if (typeof testid === 'string')
14
+ return testIdLocator(testid, typeof testIdAttribute === 'string' ? testIdAttribute : undefined);
15
+ return typeof selector === 'string' ? { css: selector, label: `selector "${selector}"` } : undefined;
16
+ }
17
+ export function testIdLocator(testid, testIdAttribute) {
18
+ const value = `"${testid.replace(/["\\]/g, '\\$&')}"`;
19
+ return { css: testIdAttributes(testIdAttribute).map(attr => `[${attr}=${value}]`).join(','), label: `testid "${testid}"` };
20
+ }
21
+ /**
22
+ * The first visible element a locator names. The label says when it was one of
23
+ * several, since acting on the first of many usually means the locator is too broad.
24
+ */
25
+ export async function findByLocator(conn, locator) {
26
+ const { backendDOMNodeId, count } = await conn.queryVisible(locator.css);
27
+ if (count === 0)
28
+ return { error: `No element matches ${locator.label}` };
29
+ if (backendDOMNodeId === null)
30
+ return { error: `None of ${count} element(s) matching ${locator.label} is visible` };
31
+ return { backendDOMNodeId, label: count > 1 ? `${locator.label} (first visible of ${count})` : locator.label };
32
+ }
33
+ //# sourceMappingURL=locator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locator.js","sourceRoot":"","sources":["../../src/server/locator.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,0BAA0B,GAAG,CAAC,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,CAAU,CAAA;AAE9G,MAAM,UAAU,gBAAgB,CAAC,UAA8B;IAC7D,wFAAwF;IACxF,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAA;AAC7E,CAAC;AAID,MAAM,UAAU,eAAe,CAAC,IAA6B;IAC3D,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,IAAI,CAAA;IAClD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC,MAAM,EAAE,OAAO,eAAe,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IAC/H,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AACtG,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,eAAmC;IAC/E,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAA;IACrD,OAAO,EAAE,GAAG,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,WAAW,MAAM,GAAG,EAAE,CAAA;AAC5H,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAiB,EACjB,OAAgB;IAEhB,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACxE,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,sBAAsB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAA;IACxE,IAAI,gBAAgB,KAAK,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,WAAW,KAAK,wBAAwB,OAAO,CAAC,KAAK,aAAa,EAAE,CAAA;IACnH,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,sBAAsB,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;AAChH,CAAC"}
@@ -0,0 +1,7 @@
1
+ /** `AGENT_VIEW_SERVER_PORT` runs a dev build next to the installed server. The spawned server inherits it. */
2
+ export declare const SERVER_PORT: number;
3
+ /** Home of the token files and saved act scripts. */
4
+ export declare const AGENT_VIEW_DIR: string;
5
+ /** Per-port token, so a dev server never overwrites the live server's token. */
6
+ export declare const TOKEN_PATH: string;
7
+ //# sourceMappingURL=port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../src/server/port.ts"],"names":[],"mappings":"AAKA,8GAA8G;AAC9G,eAAO,MAAM,WAAW,QAAiD,CAAA;AAEzE,qDAAqD;AACrD,eAAO,MAAM,cAAc,QAAiC,CAAA;AAE5D,gFAAgF;AAChF,eAAO,MAAM,UAAU,QAA+F,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ const DEFAULT_SERVER_PORT = 47922;
4
+ /** `AGENT_VIEW_SERVER_PORT` runs a dev build next to the installed server. The spawned server inherits it. */
5
+ export const SERVER_PORT = serverPort(process.env.AGENT_VIEW_SERVER_PORT);
6
+ /** Home of the token files and saved act scripts. */
7
+ export const AGENT_VIEW_DIR = join(homedir(), '.agent-view');
8
+ /** Per-port token, so a dev server never overwrites the live server's token. */
9
+ export const TOKEN_PATH = join(AGENT_VIEW_DIR, SERVER_PORT === DEFAULT_SERVER_PORT ? 'token' : `token-${SERVER_PORT}`);
10
+ function serverPort(raw) {
11
+ if (raw === undefined || raw === '')
12
+ return DEFAULT_SERVER_PORT;
13
+ const port = Number(raw);
14
+ // A typo must not fall back to the default: that is the live server the variable exists to avoid.
15
+ if (!Number.isInteger(port) || port < 1 || port > 65_535)
16
+ throw new Error(`AGENT_VIEW_SERVER_PORT=${raw} is not a port`);
17
+ return port;
18
+ }
19
+ //# sourceMappingURL=port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/server/port.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,MAAM,mBAAmB,GAAG,KAAK,CAAA;AAEjC,8GAA8G;AAC9G,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA;AAEzE,qDAAqD;AACrD,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAA;AAE5D,gFAAgF;AAChF,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,KAAK,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,CAAA;AAEtH,SAAS,UAAU,CAAC,GAAuB;IACzC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,mBAAmB,CAAA;IAC/D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;IACxB,kGAAkG;IAClG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,gBAAgB,CAAC,CAAA;IACxH,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -69,6 +69,7 @@ export declare class AgentViewServer {
69
69
  private handleDom;
70
70
  private findByFilter;
71
71
  private handleClick;
72
+ private handleAct;
72
73
  private handleDrag;
73
74
  private resolveDragPoint;
74
75
  private handleFill;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { createServer } from 'node:net';
2
2
  import { writeFile, unlink } from 'node:fs/promises';
3
3
  import { randomBytes } from 'node:crypto';
4
- import { homedir, tmpdir } from 'node:os';
4
+ import { tmpdir } from 'node:os';
5
5
  import { join, resolve } from 'node:path';
6
6
  import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
7
7
  import { getAdapter } from '../adapters/registry.js';
@@ -28,8 +28,10 @@ import { AxTreeCache } from '../cdp/ax-cache.js';
28
28
  import { WatchSession } from './watch-session.js';
29
29
  import { StopReason, WATCH_MIN_INTERVAL_MS } from '../inspectors/watch/index.js';
30
30
  import { buildMatcher } from './pattern.js';
31
+ import { findByLocator, locatorFromArgs, testIdAttributes } from './locator.js';
32
+ import { SERVER_PORT, AGENT_VIEW_DIR, TOKEN_PATH } from './port.js';
33
+ import { runAct } from './act-session.js';
31
34
  import { DEFAULT_TAIL_LINES, LogRecorder, filterLogLines, localStamp, parseSinceToken, readFeedLines, resolveLogFile, } from './log-recorder.js';
32
- const SERVER_PORT = 47922;
33
35
  /**
34
36
  * Every non-streaming command answers within this budget (plus its own `--timeout`,
35
37
  * for the commands that poll). A CDP call that never returns used to leave the CLI
@@ -43,8 +45,6 @@ const VALID_ENGINES = new Set(Object.values(WebGLEngine));
43
45
  const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
44
46
  const DELIMITER = '\n';
45
47
  const MAX_BUFFER_SIZE = 1_048_576; // 1 MB
46
- const TOKEN_DIR = join(homedir(), '.agent-view');
47
- const TOKEN_PATH = join(TOKEN_DIR, 'token');
48
48
  const EVAL_OUTPUT_CAP = 64 * 1024;
49
49
  const DEFAULT_CONSOLE_TARGETS = [TargetType.Page, TargetType.SharedWorker, TargetType.ServiceWorker];
50
50
  const DEFAULT_NETWORK_BUFFER = 200;
@@ -194,13 +194,14 @@ export class AgentViewServer {
194
194
  coverage: (req) => this.handleCoverage(req),
195
195
  listeners: (req) => this.handleListeners(req),
196
196
  heap: (req) => this.handleHeap(req),
197
+ act: (req) => this.handleAct(req),
197
198
  stop: () => this.handleStop(),
198
199
  };
199
200
  streamingCommands = new Set(['watch']);
200
201
  validCommands = new Set([...Object.keys(this.handlers), ...this.streamingCommands]);
201
202
  async start() {
202
203
  installCDPErrorGuard();
203
- mkdirSync(TOKEN_DIR, { recursive: true });
204
+ mkdirSync(AGENT_VIEW_DIR, { recursive: true });
204
205
  this.token = randomBytes(32).toString('hex');
205
206
  // Publish the token only after winning the port. A server that loses the bind race
206
207
  // must not overwrite the live server's token — that leaves every CLI call
@@ -225,6 +226,7 @@ export class AgentViewServer {
225
226
  networkRefs: new Map(),
226
227
  networkNextRef: 1,
227
228
  logRecorder: null,
229
+ act: null,
228
230
  };
229
231
  this.portStates.set(port, state);
230
232
  }
@@ -509,6 +511,7 @@ export class AgentViewServer {
509
511
  startRef: this.refStore.getNextRef(),
510
512
  compact,
511
513
  maxLines: argNum(req.args, 'maxLines'),
514
+ testIds: await conn.getTestIds(testIdAttributes(argStr(req.args, 'testIdAttribute'))),
512
515
  });
513
516
  this.refStore.store(refs, req.port, targetId, nextRef);
514
517
  if (isDiff) {
@@ -634,6 +637,15 @@ export class AgentViewServer {
634
637
  this.axTreeCache.invalidate(cacheKey);
635
638
  return { ok: true, data: `${verb} at (${x}, ${y})` };
636
639
  }
640
+ const locator = locatorFromArgs(req.args);
641
+ if (locator) {
642
+ const found = await findByLocator(conn, locator);
643
+ if ('error' in found)
644
+ return { ok: false, error: found.error };
645
+ await conn.clickByNodeId(found.backendDOMNodeId, clickOpts);
646
+ this.axTreeCache.invalidate(cacheKey);
647
+ return { ok: true, data: `${verb} ${found.label}` };
648
+ }
637
649
  const clickFilter = argStr(req.args, 'filter');
638
650
  if (clickFilter) {
639
651
  const filter = clickFilter;
@@ -648,7 +660,7 @@ export class AgentViewServer {
648
660
  }
649
661
  const ref = argNum(req.args, 'ref');
650
662
  if (ref === undefined) {
651
- return { ok: false, error: 'click requires --ref, --filter, or --pos' };
663
+ return { ok: false, error: 'click requires --ref, --filter, --testid, --selector, or --pos' };
652
664
  }
653
665
  const entry = this.refStore.get(ref);
654
666
  if (!entry) {
@@ -658,6 +670,19 @@ export class AgentViewServer {
658
670
  this.axTreeCache.invalidate(cacheKey);
659
671
  return { ok: true, data: `${verb} ref ${ref}` };
660
672
  }
673
+ async handleAct(req) {
674
+ const { targetId } = await this.resolveWindow(req);
675
+ const cacheKey = `${req.port}:${targetId}`;
676
+ return runAct(this.stateFor(req.port), req.args, {
677
+ conn: await this.getPageSession(req, targetId),
678
+ invalidateAxCache: () => this.axTreeCache.invalidate(cacheKey),
679
+ // A login that reloads the window kills the cached socket before its disconnect event lands.
680
+ reconnect: async () => {
681
+ this.dropSessionsForPort(req.port);
682
+ return this.getPageSession(req, (await this.resolveWindow(req)).targetId);
683
+ },
684
+ });
685
+ }
661
686
  async handleDrag(req) {
662
687
  const { targetId } = await this.resolveWindow(req);
663
688
  const conn = await this.getPageSession(req, targetId);
@@ -712,6 +737,15 @@ export class AgentViewServer {
712
737
  if (value === undefined) {
713
738
  return { ok: false, error: 'fill requires --value' };
714
739
  }
740
+ const locator = locatorFromArgs(req.args);
741
+ if (locator) {
742
+ const found = await findByLocator(conn, locator);
743
+ if ('error' in found)
744
+ return { ok: false, error: found.error };
745
+ await conn.fillByNodeId(found.backendDOMNodeId, value);
746
+ this.axTreeCache.invalidate(cacheKey);
747
+ return { ok: true, data: `Filled ${found.label} with "${value}"` };
748
+ }
715
749
  const fillFilter = argStr(req.args, 'filter');
716
750
  if (fillFilter) {
717
751
  const filter = fillFilter;
@@ -726,7 +760,7 @@ export class AgentViewServer {
726
760
  }
727
761
  const fillRef = argNum(req.args, 'ref');
728
762
  if (fillRef === undefined) {
729
- return { ok: false, error: 'fill requires --ref or --filter' };
763
+ return { ok: false, error: 'fill requires --ref, --filter, --testid, or --selector' };
730
764
  }
731
765
  const entry = this.refStore.get(fillRef);
732
766
  if (!entry) {
@@ -1035,14 +1069,25 @@ export class AgentViewServer {
1035
1069
  }
1036
1070
  async handleWait(req) {
1037
1071
  const filter = argStr(req.args, 'filter');
1038
- if (!filter) {
1039
- return { ok: false, error: 'wait requires --filter' };
1072
+ const locator = locatorFromArgs(req.args);
1073
+ if (!filter && !locator) {
1074
+ return { ok: false, error: 'wait requires --filter, --testid, or --selector' };
1040
1075
  }
1041
1076
  const timeout = argNum(req.args, 'timeout') ?? 10;
1042
1077
  const interval = 500;
1043
1078
  const maxAttempts = Math.ceil((timeout * 1000) / interval);
1044
1079
  const { targetId } = await this.resolveWindow(req);
1045
1080
  const conn = await this.getPageSession(req, targetId);
1081
+ if (locator) {
1082
+ let found = await findByLocator(conn, locator);
1083
+ for (let i = 1; i < maxAttempts && 'error' in found; i++) {
1084
+ await new Promise(resolve => setTimeout(resolve, interval));
1085
+ found = await findByLocator(conn, locator);
1086
+ }
1087
+ return 'error' in found
1088
+ ? { ok: false, error: `Timeout after ${timeout}s: ${found.error}` }
1089
+ : { ok: true, data: `Found ${found.label}` };
1090
+ }
1046
1091
  for (let i = 0; i < maxAttempts; i++) {
1047
1092
  const nodes = await conn.getAccessibilityTree();
1048
1093
  const { refs, text, nextRef } = formatAccessibilityTree(nodes, {
@@ -1069,20 +1114,33 @@ export class AgentViewServer {
1069
1114
  const conn = await this.getPageSession(req, targetId);
1070
1115
  const scale = argNum(req.args, 'scale');
1071
1116
  const cropFilter = argStr(req.args, 'crop');
1117
+ const locator = locatorFromArgs(req.args);
1072
1118
  let warning;
1073
1119
  let clip;
1074
- if (cropFilter !== undefined) {
1120
+ let cropNode;
1121
+ if (locator) {
1122
+ // Unlike a text filter, an exact locator that misses is a wrong locator: a
1123
+ // full-window fallback would spend ~19k vision tokens on the wrong question.
1124
+ const found = await findByLocator(conn, locator);
1125
+ if ('error' in found)
1126
+ return { ok: false, error: found.error };
1127
+ cropNode = found.backendDOMNodeId;
1128
+ }
1129
+ else if (cropFilter !== undefined) {
1075
1130
  const found = await this.findByFilter(conn, cropFilter, req, targetId);
1076
- if (!found) {
1077
- warning = `crop filter '${cropFilter}' matched nothing — capturing full window`;
1131
+ if (found) {
1132
+ cropNode = found.backendDOMNodeId;
1078
1133
  }
1079
1134
  else {
1080
- const cropUp = argNum(req.args, 'cropUp') ?? 0;
1081
- clip = await conn.getBoxRect(found.backendDOMNodeId, { scrollIntoView: true, ancestorLevels: cropUp });
1082
- if (clip.height < TEXT_LINE_HEIGHT_PX && cropUp === 0) {
1083
- warning = `crop matched a text-sized box (${Math.round(clip.width)}×${Math.round(clip.height)}) — `
1084
- + `pass --crop-up 1 (or 2) to capture the surrounding container instead`;
1085
- }
1135
+ warning = `crop filter '${cropFilter}' matched nothing — capturing full window`;
1136
+ }
1137
+ }
1138
+ if (cropNode !== undefined) {
1139
+ const cropUp = argNum(req.args, 'cropUp') ?? 0;
1140
+ clip = await conn.getBoxRect(cropNode, { scrollIntoView: true, ancestorLevels: cropUp });
1141
+ if (clip.height < TEXT_LINE_HEIGHT_PX && cropUp === 0) {
1142
+ warning = `crop matched a text-sized box (${Math.round(clip.width)}×${Math.round(clip.height)}) — `
1143
+ + `pass --crop-up 1 (or 2) to capture the surrounding container instead`;
1086
1144
  }
1087
1145
  }
1088
1146
  const filepath = await this.captureScreenshotToFile(conn, { scale, clip });