@petukhovart/agent-view 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +34 -11
- package/dist/cdp/png.d.ts +15 -0
- package/dist/cdp/png.d.ts.map +1 -0
- package/dist/cdp/png.js +147 -0
- package/dist/cdp/png.js.map +1 -0
- package/dist/cdp/transport.d.ts +2 -0
- package/dist/cdp/transport.d.ts.map +1 -1
- package/dist/cdp/transport.js +233 -50
- package/dist/cdp/transport.js.map +1 -1
- package/dist/cdp/types.d.ts +72 -6
- package/dist/cdp/types.d.ts.map +1 -1
- package/dist/cdp/types.js.map +1 -1
- package/dist/cli/client.d.ts.map +1 -1
- package/dist/cli/client.js +1 -3
- package/dist/cli/client.js.map +1 -1
- package/dist/cli/commands/act.d.ts +3 -0
- package/dist/cli/commands/act.d.ts.map +1 -0
- package/dist/cli/commands/act.js +19 -0
- package/dist/cli/commands/act.js.map +1 -0
- package/dist/cli/commands/click.d.ts +2 -0
- package/dist/cli/commands/click.d.ts.map +1 -1
- package/dist/cli/commands/click.js +9 -4
- package/dist/cli/commands/click.js.map +1 -1
- package/dist/cli/commands/dom.d.ts.map +1 -1
- package/dist/cli/commands/dom.js +1 -0
- package/dist/cli/commands/dom.js.map +1 -1
- package/dist/cli/commands/drag.d.ts +4 -0
- package/dist/cli/commands/drag.d.ts.map +1 -1
- package/dist/cli/commands/drag.js +18 -0
- package/dist/cli/commands/drag.js.map +1 -1
- package/dist/cli/commands/fill.d.ts +2 -0
- package/dist/cli/commands/fill.d.ts.map +1 -1
- package/dist/cli/commands/fill.js +11 -4
- package/dist/cli/commands/fill.js.map +1 -1
- package/dist/cli/commands/screenshot.d.ts +2 -0
- package/dist/cli/commands/screenshot.d.ts.map +1 -1
- package/dist/cli/commands/screenshot.js +10 -1
- package/dist/cli/commands/screenshot.js.map +1 -1
- package/dist/cli/commands/wait.d.ts +3 -1
- package/dist/cli/commands/wait.d.ts.map +1 -1
- package/dist/cli/commands/wait.js +6 -2
- package/dist/cli/commands/wait.js.map +1 -1
- package/dist/cli/commands/watch.js +1 -1
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/cli/index.js +51 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +4 -0
- package/dist/config/manager.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/inspectors/controls/index.d.ts +39 -0
- package/dist/inspectors/controls/index.d.ts.map +1 -0
- package/dist/inspectors/controls/index.js +140 -0
- package/dist/inspectors/controls/index.js.map +1 -0
- package/dist/inspectors/dom/index.d.ts +2 -0
- package/dist/inspectors/dom/index.d.ts.map +1 -1
- package/dist/inspectors/dom/index.js +21 -14
- package/dist/inspectors/dom/index.js.map +1 -1
- package/dist/server/act-replay.d.ts +23 -0
- package/dist/server/act-replay.d.ts.map +1 -0
- package/dist/server/act-replay.js +143 -0
- package/dist/server/act-replay.js.map +1 -0
- package/dist/server/act-script.d.ts +46 -0
- package/dist/server/act-script.d.ts.map +1 -0
- package/dist/server/act-script.js +46 -0
- package/dist/server/act-script.js.map +1 -0
- package/dist/server/act-session.d.ts +23 -0
- package/dist/server/act-session.d.ts.map +1 -0
- package/dist/server/act-session.js +304 -0
- package/dist/server/act-session.js.map +1 -0
- package/dist/server/locator.d.ts +19 -0
- package/dist/server/locator.d.ts.map +1 -0
- package/dist/server/locator.js +33 -0
- package/dist/server/locator.js.map +1 -0
- package/dist/server/port.d.ts +7 -0
- package/dist/server/port.d.ts.map +1 -0
- package/dist/server/port.js +19 -0
- package/dist/server/port.js.map +1 -0
- package/dist/server/server.d.ts +1 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +89 -25
- package/dist/server/server.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/verify/SKILL.md +12 -20
- package/skills/verify/references/commands.md +127 -6
package/dist/server/server.js
CHANGED
|
@@ -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 {
|
|
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(
|
|
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);
|
|
@@ -672,13 +697,20 @@ export class AgentViewServer {
|
|
|
672
697
|
steps: argNum(req.args, 'steps'),
|
|
673
698
|
button: parseMouseButton(argStr(req.args, 'button')),
|
|
674
699
|
holdMs: argNum(req.args, 'holdMs'),
|
|
700
|
+
mode: argStr(req.args, 'mode'),
|
|
701
|
+
cancel: argBool(req.args, 'cancel'),
|
|
702
|
+
mask: argNum(req.args, 'mask'),
|
|
675
703
|
};
|
|
676
|
-
await conn.dragBetweenPositions(from.point, to.point, opts);
|
|
704
|
+
const result = await conn.dragBetweenPositions(from.point, to.point, opts);
|
|
677
705
|
this.axTreeCache.invalidate(cacheKey);
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
706
|
+
const head = `Dragged (${from.point.x.toFixed(0)}, ${from.point.y.toFixed(0)}) → (${to.point.x.toFixed(0)}, ${to.point.y.toFixed(0)})`;
|
|
707
|
+
if (result.path === 'pointer') {
|
|
708
|
+
const warning = result.warning ? `\n warning: ${result.warning}` : '';
|
|
709
|
+
return { ok: true, data: `${head} via pointer${warning}` };
|
|
710
|
+
}
|
|
711
|
+
const items = result.data.items.map(i => ` ${i.mimeType}: ${JSON.stringify(i.data.length > 200 ? i.data.slice(0, 200) + '…' : i.data)}`);
|
|
712
|
+
const summary = `${head} via html5 ${result.ended} (dragOperationsMask=${result.data.dragOperationsMask})`;
|
|
713
|
+
return { ok: true, data: [summary, ...items].join('\n') };
|
|
682
714
|
}
|
|
683
715
|
async resolveDragPoint(req, conn, side, opts) {
|
|
684
716
|
const ref = argNum(req.args, `${side}Ref`);
|
|
@@ -705,6 +737,15 @@ export class AgentViewServer {
|
|
|
705
737
|
if (value === undefined) {
|
|
706
738
|
return { ok: false, error: 'fill requires --value' };
|
|
707
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
|
+
}
|
|
708
749
|
const fillFilter = argStr(req.args, 'filter');
|
|
709
750
|
if (fillFilter) {
|
|
710
751
|
const filter = fillFilter;
|
|
@@ -719,7 +760,7 @@ export class AgentViewServer {
|
|
|
719
760
|
}
|
|
720
761
|
const fillRef = argNum(req.args, 'ref');
|
|
721
762
|
if (fillRef === undefined) {
|
|
722
|
-
return { ok: false, error: 'fill requires --ref or --
|
|
763
|
+
return { ok: false, error: 'fill requires --ref, --filter, --testid, or --selector' };
|
|
723
764
|
}
|
|
724
765
|
const entry = this.refStore.get(fillRef);
|
|
725
766
|
if (!entry) {
|
|
@@ -1028,14 +1069,25 @@ export class AgentViewServer {
|
|
|
1028
1069
|
}
|
|
1029
1070
|
async handleWait(req) {
|
|
1030
1071
|
const filter = argStr(req.args, 'filter');
|
|
1031
|
-
|
|
1032
|
-
|
|
1072
|
+
const locator = locatorFromArgs(req.args);
|
|
1073
|
+
if (!filter && !locator) {
|
|
1074
|
+
return { ok: false, error: 'wait requires --filter, --testid, or --selector' };
|
|
1033
1075
|
}
|
|
1034
1076
|
const timeout = argNum(req.args, 'timeout') ?? 10;
|
|
1035
1077
|
const interval = 500;
|
|
1036
1078
|
const maxAttempts = Math.ceil((timeout * 1000) / interval);
|
|
1037
1079
|
const { targetId } = await this.resolveWindow(req);
|
|
1038
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
|
+
}
|
|
1039
1091
|
for (let i = 0; i < maxAttempts; i++) {
|
|
1040
1092
|
const nodes = await conn.getAccessibilityTree();
|
|
1041
1093
|
const { refs, text, nextRef } = formatAccessibilityTree(nodes, {
|
|
@@ -1052,8 +1104,7 @@ export class AgentViewServer {
|
|
|
1052
1104
|
}
|
|
1053
1105
|
async captureScreenshotToFile(conn, opts = {}) {
|
|
1054
1106
|
const { buffer, format } = await conn.captureScreenshot(opts);
|
|
1055
|
-
const
|
|
1056
|
-
const filename = `agent-view-screenshot-${Date.now()}.${ext}`;
|
|
1107
|
+
const filename = `agent-view-screenshot-${Date.now()}.${format}`;
|
|
1057
1108
|
const filepath = join(tmpdir(), filename);
|
|
1058
1109
|
await writeFile(filepath, buffer);
|
|
1059
1110
|
return filepath;
|
|
@@ -1063,20 +1114,33 @@ export class AgentViewServer {
|
|
|
1063
1114
|
const conn = await this.getPageSession(req, targetId);
|
|
1064
1115
|
const scale = argNum(req.args, 'scale');
|
|
1065
1116
|
const cropFilter = argStr(req.args, 'crop');
|
|
1117
|
+
const locator = locatorFromArgs(req.args);
|
|
1066
1118
|
let warning;
|
|
1067
1119
|
let clip;
|
|
1068
|
-
|
|
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) {
|
|
1069
1130
|
const found = await this.findByFilter(conn, cropFilter, req, targetId);
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1131
|
+
if (found) {
|
|
1132
|
+
cropNode = found.backendDOMNodeId;
|
|
1072
1133
|
}
|
|
1073
1134
|
else {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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`;
|
|
1080
1144
|
}
|
|
1081
1145
|
}
|
|
1082
1146
|
const filepath = await this.captureScreenshotToFile(conn, { scale, clip });
|