zan-browser 3.0.65 → 3.0.67
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/dist/agent/agent.d.ts +0 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +44 -123
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/candidate-evaluator.d.ts +13 -0
- package/dist/agent/candidate-evaluator.d.ts.map +1 -0
- package/dist/agent/candidate-evaluator.js +179 -0
- package/dist/agent/candidate-evaluator.js.map +1 -0
- package/dist/agent/enforcement.d.ts +11 -0
- package/dist/agent/enforcement.d.ts.map +1 -0
- package/dist/agent/enforcement.js +46 -0
- package/dist/agent/enforcement.js.map +1 -0
- package/dist/agent/extraction.d.ts +11 -0
- package/dist/agent/extraction.d.ts.map +1 -0
- package/dist/agent/extraction.js +96 -0
- package/dist/agent/extraction.js.map +1 -0
- package/dist/agent/goal-builder.d.ts +11 -0
- package/dist/agent/goal-builder.d.ts.map +1 -0
- package/dist/agent/goal-builder.js +57 -0
- package/dist/agent/goal-builder.js.map +1 -0
- package/dist/agent/orchestrator.d.ts +0 -6
- package/dist/agent/orchestrator.d.ts.map +1 -1
- package/dist/agent/orchestrator.js +23 -347
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/pre-validator.d.ts +19 -0
- package/dist/agent/pre-validator.d.ts.map +1 -0
- package/dist/agent/pre-validator.js +123 -0
- package/dist/agent/pre-validator.js.map +1 -0
- package/dist/agent/state-updater.d.ts +19 -0
- package/dist/agent/state-updater.d.ts.map +1 -0
- package/dist/agent/state-updater.js +101 -0
- package/dist/agent/state-updater.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Seed page extraction ────────────────────────────────────────────────────
|
|
3
|
+
// Multi-strategy extraction for pages whose URL contains a seed value.
|
|
4
|
+
// Tries scrape → dismiss modal → scrape again → eval_js for SSR data.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.tryExtraction = tryExtraction;
|
|
7
|
+
const DISMISS_KEYWORDS = /cookie|accept|agree|close|dismiss|got it|i understand|consent/i;
|
|
8
|
+
const SSR_GLOBALS = [
|
|
9
|
+
"window.__NEXT_DATA__",
|
|
10
|
+
"window.__NUXT__",
|
|
11
|
+
"window.__APP_STATE__",
|
|
12
|
+
"window.__INITIAL_STATE__",
|
|
13
|
+
];
|
|
14
|
+
/** Try to extract data from the current page using multiple strategies. */
|
|
15
|
+
async function tryExtraction(pageUrl, _seedValues, registry, ctx, conversation) {
|
|
16
|
+
// Strategy 1: scrape directly
|
|
17
|
+
const scrape1 = await tryScrape(registry, ctx);
|
|
18
|
+
if (scrape1 && scrape1.length > 200) {
|
|
19
|
+
return { data: scrape1, url: pageUrl };
|
|
20
|
+
}
|
|
21
|
+
// Strategy 2: dismiss modal/banner if present, then scrape again
|
|
22
|
+
const observeRes = await tryObserve(registry, ctx);
|
|
23
|
+
if (observeRes) {
|
|
24
|
+
const dismissId = findDismissButton(observeRes);
|
|
25
|
+
if (dismissId) {
|
|
26
|
+
try {
|
|
27
|
+
await registry.execute("click", { elementId: dismissId }, ctx);
|
|
28
|
+
// Brief wait for modal animation
|
|
29
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
30
|
+
}
|
|
31
|
+
catch { /* non-fatal */ }
|
|
32
|
+
const scrape2 = await tryScrape(registry, ctx);
|
|
33
|
+
if (scrape2 && scrape2.length > 200) {
|
|
34
|
+
return { data: scrape2, url: pageUrl };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Strategy 3: eval_js for SSR embedded data
|
|
39
|
+
const ssrData = await trySSRExtraction(registry, ctx);
|
|
40
|
+
if (ssrData && ssrData.length > 200) {
|
|
41
|
+
return { data: ssrData, url: pageUrl };
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
async function tryScrape(registry, ctx) {
|
|
46
|
+
try {
|
|
47
|
+
const res = await registry.execute("scrape", {}, ctx);
|
|
48
|
+
if (res.success) {
|
|
49
|
+
const data = res.data;
|
|
50
|
+
return data?.text ?? (typeof res.data === "string" ? res.data : null);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch { /* non-fatal */ }
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
async function tryObserve(registry, ctx) {
|
|
57
|
+
try {
|
|
58
|
+
const res = await registry.execute("observe", {}, ctx);
|
|
59
|
+
if (res.success) {
|
|
60
|
+
return res.summary;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch { /* non-fatal */ }
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function findDismissButton(observeSummary) {
|
|
67
|
+
// Parse the ActionSpace output looking for buttons/links with dismiss keywords
|
|
68
|
+
const lines = observeSummary.split("\n");
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
if (DISMISS_KEYWORDS.test(line)) {
|
|
71
|
+
// Look for element IDs like B1, L2, etc.
|
|
72
|
+
const idMatch = line.match(/\b([BLO]\d+)\b/);
|
|
73
|
+
if (idMatch) {
|
|
74
|
+
return idMatch[1];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
async function trySSRExtraction(registry, ctx) {
|
|
81
|
+
const expr = SSR_GLOBALS
|
|
82
|
+
.map((g) => `(typeof ${g} !== 'undefined' ? JSON.stringify(${g}).slice(0, 5000) : null)`)
|
|
83
|
+
.join(" || ");
|
|
84
|
+
try {
|
|
85
|
+
const res = await registry.execute("eval_js", { expression: expr }, ctx);
|
|
86
|
+
if (res.success && res.data) {
|
|
87
|
+
const text = typeof res.data === "string" ? res.data : JSON.stringify(res.data);
|
|
88
|
+
if (text && text !== "null" && text.length > 10) {
|
|
89
|
+
return text;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch { /* non-fatal */ }
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=extraction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extraction.js","sourceRoot":"","sources":["../../src/agent/extraction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SeedValue } from "../types";
|
|
2
|
+
import type { Rejection } from "./orchestrator";
|
|
3
|
+
import type { DiscoveryState } from "./discovery-state";
|
|
4
|
+
export interface GoalBuilderInput {
|
|
5
|
+
originalGoal: string;
|
|
6
|
+
seedValues: SeedValue[];
|
|
7
|
+
rejections: Rejection[];
|
|
8
|
+
state: DiscoveryState;
|
|
9
|
+
}
|
|
10
|
+
export declare function buildEnrichedGoal(input: GoalBuilderInput): string;
|
|
11
|
+
//# sourceMappingURL=goal-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-builder.d.ts","sourceRoot":"","sources":["../../src/agent/goal-builder.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,KAAK,EAAE,cAAc,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CA4DjE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Goal builder ────────────────────────────────────────────────────────────
|
|
3
|
+
// Builds the enriched goal string injected into each agent attempt.
|
|
4
|
+
// Contains scrape hints, success criteria, seed values, and accumulated context.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildEnrichedGoal = buildEnrichedGoal;
|
|
7
|
+
function buildEnrichedGoal(input) {
|
|
8
|
+
const { originalGoal, seedValues, rejections, state } = input;
|
|
9
|
+
const lines = [];
|
|
10
|
+
// What counts as success — permissive if a prior rejection suggested scrape
|
|
11
|
+
const scrapeHinted = rejections.some((r) => r.reason.toLowerCase().includes("scrape"));
|
|
12
|
+
// If a prior rejection identified a seed page, inject immediate action directive
|
|
13
|
+
if (scrapeHinted) {
|
|
14
|
+
const seedPageMatch = rejections
|
|
15
|
+
.map((r) => r.reason.match(/The page (https?:\/\/\S+) has the seed value/))
|
|
16
|
+
.filter((m) => m !== null).pop();
|
|
17
|
+
if (seedPageMatch) {
|
|
18
|
+
const seedPageUrl = seedPageMatch[1];
|
|
19
|
+
lines.push(`IMMEDIATE ACTION: The previous attempt found that ${seedPageUrl} contains the data rendered in HTML. ` +
|
|
20
|
+
`You are already on ${seedPageUrl}. Observe the page immediately. If the DOM shows the data you need, ` +
|
|
21
|
+
"use scrape as your first action — do not navigate away, do not read network logs first.");
|
|
22
|
+
lines.push("");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Core goal
|
|
26
|
+
lines.push(`GOAL: ${originalGoal}`);
|
|
27
|
+
lines.push("");
|
|
28
|
+
if (scrapeHinted) {
|
|
29
|
+
lines.push("SUCCESS CRITERIA: Find a reproducible URL that returns the requested data. " +
|
|
30
|
+
"A JSON API endpoint is preferred, but if the data is only available via HTML rendering, " +
|
|
31
|
+
"scraping a parameterizable page URL (one that contains the seed value in the path) is also " +
|
|
32
|
+
"valid — set data_found=true and declare done.");
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
lines.push("SUCCESS CRITERIA: Find an HTTP endpoint (API, JSON feed, or structured data URL) " +
|
|
36
|
+
"that can be called directly with fetch/curl — no browser session required at runtime. " +
|
|
37
|
+
"DOM extractions, scraped HTML text, and framework data-transport routes (/_next/data/, " +
|
|
38
|
+
"__data.json) do NOT count. The result must be a reproducible URL with a real HTTP response.");
|
|
39
|
+
}
|
|
40
|
+
// Seed values context
|
|
41
|
+
if (seedValues.length > 0) {
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push("SEED VALUES (use these verbatim when searching/filling):");
|
|
44
|
+
for (const s of seedValues) {
|
|
45
|
+
lines.push(` • ${s.paramName} (${s.description}): "${s.exampleValue}"`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Accumulated discovery context from shared state
|
|
49
|
+
const stateSummary = state.summarize();
|
|
50
|
+
if (stateSummary) {
|
|
51
|
+
lines.push("");
|
|
52
|
+
lines.push("ACCUMULATED CONTEXT:");
|
|
53
|
+
lines.push(stateSummary);
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=goal-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goal-builder.js","sourceRoot":"","sources":["../../src/agent/goal-builder.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,oEAAoE;AACpE,iFAAiF;;AAajF,8CA4DC;AA5DD,SAAgB,iBAAiB,CAAC,KAAuB;IACvD,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC9D,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,4EAA4E;IAC5E,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEvF,iFAAiF;IACjF,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,aAAa,GAAG,UAAU;aAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;aAC1E,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;QACnC,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;YACrC,KAAK,CAAC,IAAI,CACR,qDAAqD,WAAW,uCAAuC;gBACvG,sBAAsB,WAAW,sEAAsE;gBACvG,yFAAyF,CAC1F,CAAC;YACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAED,YAAY;IACZ,KAAK,CAAC,IAAI,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;IACpC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CACR,6EAA6E;YAC7E,0FAA0F;YAC1F,6FAA6F;YAC7F,+CAA+C,CAChD,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CACR,mFAAmF;YACnF,wFAAwF;YACxF,yFAAyF;YACzF,6FAA6F,CAC9F,CAAC;IACJ,CAAC;IAED,sBAAsB;IACtB,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;QACvE,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,WAAW,OAAO,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED,kDAAkD;IAClD,MAAM,YAAY,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;IACvC,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -55,13 +55,7 @@ export declare class AgentOrchestrator {
|
|
|
55
55
|
run(config: OrchestratorConfig): Promise<OrchestratorResult>;
|
|
56
56
|
/** Inject external feedback from the pipeline (validator, request-selector, etc.) */
|
|
57
57
|
injectFeedback(rejection: Rejection): void;
|
|
58
|
-
private buildEnrichedGoal;
|
|
59
|
-
private evaluateResult;
|
|
60
|
-
private preValidateEndpoint;
|
|
61
|
-
private buildUrlTemplate;
|
|
62
|
-
private explainReproducibility;
|
|
63
58
|
private diagnoseFailure;
|
|
64
|
-
private recordFailedEndpoints;
|
|
65
59
|
private buildFailureSummary;
|
|
66
60
|
}
|
|
67
61
|
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/agent/orchestrator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/agent/orchestrator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAO3D,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,WAAW,GAAG,kBAAkB,GAAG,UAAU,CAAC;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,GAAG,CAAc;IACzB,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,KAAK,CAAiB;IAG9B,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,SAAS,CAAK;gBAEV,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM;IAOrF,GAAG,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAuJlE,qFAAqF;IACrF,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAM1C,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,mBAAmB;CAe5B"}
|
|
@@ -7,14 +7,13 @@
|
|
|
7
7
|
// The core insight: the agent's real goal is not "find data" but "find a
|
|
8
8
|
// reproducible source that works without a browser at runtime". The orchestrator
|
|
9
9
|
// communicates that distinction and feeds back why previous attempts failed.
|
|
10
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
-
};
|
|
13
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
11
|
exports.AgentOrchestrator = void 0;
|
|
15
|
-
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
16
12
|
const agent_1 = require("./agent");
|
|
17
13
|
const conversation_1 = require("./conversation");
|
|
14
|
+
const goal_builder_1 = require("./goal-builder");
|
|
15
|
+
const candidate_evaluator_1 = require("./candidate-evaluator");
|
|
16
|
+
const pre_validator_1 = require("./pre-validator");
|
|
18
17
|
// ─── Orchestrator ────────────────────────────────────────────────────────────
|
|
19
18
|
class AgentOrchestrator {
|
|
20
19
|
registry;
|
|
@@ -53,7 +52,12 @@ class AgentOrchestrator {
|
|
|
53
52
|
this.ctx.interceptor?.clear();
|
|
54
53
|
}
|
|
55
54
|
// 1. Build enriched goal with accumulated context
|
|
56
|
-
const enrichedGoal =
|
|
55
|
+
const enrichedGoal = (0, goal_builder_1.buildEnrichedGoal)({
|
|
56
|
+
originalGoal: this.originalGoal,
|
|
57
|
+
seedValues: this.seedValues,
|
|
58
|
+
rejections: this.state.rejections,
|
|
59
|
+
state: this.state,
|
|
60
|
+
});
|
|
57
61
|
// 2. Fresh conversation for each attempt
|
|
58
62
|
const conversation = new conversation_1.ConversationHistory();
|
|
59
63
|
// 2b. If a prior rejection identified a seed page, navigate there before the loop starts
|
|
@@ -79,7 +83,12 @@ class AgentOrchestrator {
|
|
|
79
83
|
priorContext: attempt > 0 ? this.state.summarize() : undefined,
|
|
80
84
|
});
|
|
81
85
|
// 4. Evaluate result — is it a reproducible endpoint?
|
|
82
|
-
const endpoint =
|
|
86
|
+
const endpoint = (0, candidate_evaluator_1.evaluateResult)({
|
|
87
|
+
agentResult,
|
|
88
|
+
state: this.state,
|
|
89
|
+
seedValues: this.seedValues,
|
|
90
|
+
ctx: this.ctx,
|
|
91
|
+
});
|
|
83
92
|
const record = {
|
|
84
93
|
agentResult,
|
|
85
94
|
goalGiven: enrichedGoal,
|
|
@@ -102,7 +111,14 @@ class AgentOrchestrator {
|
|
|
102
111
|
}
|
|
103
112
|
// Pre-validate: confirm endpoint returns data for different inputs
|
|
104
113
|
progress(`Pre-validating endpoint: ${endpoint.urlTemplate}`);
|
|
105
|
-
const validation = await
|
|
114
|
+
const validation = await (0, pre_validator_1.preValidateEndpoint)({
|
|
115
|
+
endpoint,
|
|
116
|
+
seedValues: this.seedValues,
|
|
117
|
+
originalGoal: this.originalGoal,
|
|
118
|
+
ctx: this.ctx,
|
|
119
|
+
state: this.state,
|
|
120
|
+
progress,
|
|
121
|
+
});
|
|
106
122
|
if (validation.valid) {
|
|
107
123
|
endpoint.goodExamples = validation.goodExamples;
|
|
108
124
|
progress(`Attempt ${attempt + 1}: endpoint validated → ${endpoint.urlTemplate}`);
|
|
@@ -148,333 +164,6 @@ class AgentOrchestrator {
|
|
|
148
164
|
this.state.rejections.push(rejection);
|
|
149
165
|
}
|
|
150
166
|
// ─── Private ───────────────────────────────────────────────────────────────
|
|
151
|
-
buildEnrichedGoal() {
|
|
152
|
-
const lines = [];
|
|
153
|
-
// What counts as success — permissive if a prior rejection suggested scrape
|
|
154
|
-
const scrapeHinted = this.state.rejections.some((r) => r.reason.toLowerCase().includes("scrape"));
|
|
155
|
-
// If a prior rejection identified a seed page, inject immediate action directive
|
|
156
|
-
if (scrapeHinted) {
|
|
157
|
-
const seedPageMatch = this.state.rejections
|
|
158
|
-
.map((r) => r.reason.match(/The page (https?:\/\/\S+) has the seed value/))
|
|
159
|
-
.filter((m) => m !== null).pop();
|
|
160
|
-
if (seedPageMatch) {
|
|
161
|
-
const seedPageUrl = seedPageMatch[1];
|
|
162
|
-
lines.push(`IMMEDIATE ACTION: The previous attempt found that ${seedPageUrl} contains the data rendered in HTML. ` +
|
|
163
|
-
`You are already on ${seedPageUrl}. Observe the page immediately. If the DOM shows the data you need, ` +
|
|
164
|
-
"use scrape as your first action — do not navigate away, do not read network logs first.");
|
|
165
|
-
lines.push("");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
// Core goal
|
|
169
|
-
lines.push(`GOAL: ${this.originalGoal}`);
|
|
170
|
-
lines.push("");
|
|
171
|
-
if (scrapeHinted) {
|
|
172
|
-
lines.push("SUCCESS CRITERIA: Find a reproducible URL that returns the requested data. " +
|
|
173
|
-
"A JSON API endpoint is preferred, but if the data is only available via HTML rendering, " +
|
|
174
|
-
"scraping a parameterizable page URL (one that contains the seed value in the path) is also " +
|
|
175
|
-
"valid — set data_found=true and declare done.");
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
lines.push("SUCCESS CRITERIA: Find an HTTP endpoint (API, JSON feed, or structured data URL) " +
|
|
179
|
-
"that can be called directly with fetch/curl — no browser session required at runtime. " +
|
|
180
|
-
"DOM extractions, scraped HTML text, and framework data-transport routes (/_next/data/, " +
|
|
181
|
-
"__data.json) do NOT count. The result must be a reproducible URL with a real HTTP response.");
|
|
182
|
-
}
|
|
183
|
-
// Seed values context
|
|
184
|
-
if (this.seedValues.length > 0) {
|
|
185
|
-
lines.push("");
|
|
186
|
-
lines.push("SEED VALUES (use these verbatim when searching/filling):");
|
|
187
|
-
for (const s of this.seedValues) {
|
|
188
|
-
lines.push(` • ${s.paramName} (${s.description}): "${s.exampleValue}"`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
// Accumulated discovery context from shared state
|
|
192
|
-
const stateSummary = this.state.summarize();
|
|
193
|
-
if (stateSummary) {
|
|
194
|
-
lines.push("");
|
|
195
|
-
lines.push("ACCUMULATED CONTEXT:");
|
|
196
|
-
lines.push(stateSummary);
|
|
197
|
-
}
|
|
198
|
-
return lines.join("\n");
|
|
199
|
-
}
|
|
200
|
-
evaluateResult(agentResult) {
|
|
201
|
-
// Build set of URLs the agent explicitly tested via fetch_url with success
|
|
202
|
-
const verifiedUrls = new Set();
|
|
203
|
-
for (const step of agentResult.steps) {
|
|
204
|
-
if (step.toolName === "fetch_url" && step.result.success && step.params.url) {
|
|
205
|
-
verifiedUrls.add(String(step.params.url));
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
const candidates = [];
|
|
209
|
-
const allCaptured = [
|
|
210
|
-
...agentResult.capturedRequests,
|
|
211
|
-
...(this.ctx.interceptor?.getAll() ?? []),
|
|
212
|
-
];
|
|
213
|
-
// Deduplicate by URL
|
|
214
|
-
const seenUrls = new Set();
|
|
215
|
-
// Path 1: fetch_url verified endpoints with score >= 50
|
|
216
|
-
for (const url of verifiedUrls) {
|
|
217
|
-
const match = allCaptured.find((r) => r.url === url && !r.isDomExtraction && !r.isAsset && r.score >= 50);
|
|
218
|
-
if (match && !seenUrls.has(url)) {
|
|
219
|
-
candidates.push(match);
|
|
220
|
-
seenUrls.add(url);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// Path 2: high-score XHRs from browser navigation (THE CRITICAL FIX)
|
|
224
|
-
// Accepts XHRs captured by navigating and interacting — not just fetch_url verified
|
|
225
|
-
for (const r of allCaptured) {
|
|
226
|
-
if (r.score >= 60 &&
|
|
227
|
-
!r.isDomExtraction &&
|
|
228
|
-
!r.isAsset &&
|
|
229
|
-
r.isXHR &&
|
|
230
|
-
!this.state.triedEndpoints.has(r.url) &&
|
|
231
|
-
!seenUrls.has(r.url)) {
|
|
232
|
-
candidates.push(r);
|
|
233
|
-
seenUrls.add(r.url);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
// Path 3: successful fetch_url in the last 5 steps of a successful run
|
|
237
|
-
// fetch_url bypasses the browser — its results never appear in the interceptor.
|
|
238
|
-
// If the agent succeeded and a recent fetch_url returned data, accept it directly.
|
|
239
|
-
if (agentResult.success) {
|
|
240
|
-
const recentSteps = agentResult.steps.slice(-5).reverse();
|
|
241
|
-
for (const step of recentSteps) {
|
|
242
|
-
if (step.toolName === "fetch_url" &&
|
|
243
|
-
step.result.success &&
|
|
244
|
-
step.params.url &&
|
|
245
|
-
!seenUrls.has(String(step.params.url)) &&
|
|
246
|
-
!this.state.triedEndpoints.has(String(step.params.url))) {
|
|
247
|
-
const fetchUrl = String(step.params.url);
|
|
248
|
-
const fetchData = step.result.data;
|
|
249
|
-
const bodyLen = fetchData?.body?.length ?? 0;
|
|
250
|
-
// Only accept if it returned substantial content
|
|
251
|
-
if (bodyLen > 100) {
|
|
252
|
-
const synthetic = {
|
|
253
|
-
id: `fetch_url_${Date.now()}`,
|
|
254
|
-
timestamp: Date.now(),
|
|
255
|
-
method: String(step.params.method ?? "GET"),
|
|
256
|
-
url: fetchUrl,
|
|
257
|
-
requestHeaders: {},
|
|
258
|
-
requestBody: step.params.body ? String(step.params.body) : undefined,
|
|
259
|
-
status: fetchData?.status ?? 200,
|
|
260
|
-
responseHeaders: {},
|
|
261
|
-
responseBody: fetchData?.body,
|
|
262
|
-
contentType: fetchData?.contentType ?? "",
|
|
263
|
-
isXHR: false,
|
|
264
|
-
isAsset: false,
|
|
265
|
-
score: 75,
|
|
266
|
-
isDomExtraction: false,
|
|
267
|
-
};
|
|
268
|
-
candidates.push(synthetic);
|
|
269
|
-
seenUrls.add(fetchUrl);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
// Path 4: successful scrape on a page whose URL contains a seed value
|
|
275
|
-
const seeds = this.seedValues.map((s) => s.exampleValue.toLowerCase());
|
|
276
|
-
for (let i = 0; i < agentResult.steps.length; i++) {
|
|
277
|
-
const step = agentResult.steps[i];
|
|
278
|
-
if (step.toolName === "scrape" && step.result.success) {
|
|
279
|
-
// Find the most recent navigate before this scrape
|
|
280
|
-
const pageUrl = [...agentResult.steps]
|
|
281
|
-
.slice(0, i)
|
|
282
|
-
.reverse()
|
|
283
|
-
.find((s) => s.toolName === "navigate" && s.result.success && s.params.url)
|
|
284
|
-
?.params.url;
|
|
285
|
-
if (pageUrl && seeds.some((sv) => pageUrl.toLowerCase().includes(sv))) {
|
|
286
|
-
const scrapeData = step.result.data;
|
|
287
|
-
if (scrapeData?.text && scrapeData.text.length > 100) {
|
|
288
|
-
const syntheticUrl = String(pageUrl);
|
|
289
|
-
if (!seenUrls.has(syntheticUrl)) {
|
|
290
|
-
candidates.push({
|
|
291
|
-
id: `scrape_${Date.now()}`,
|
|
292
|
-
timestamp: Date.now(),
|
|
293
|
-
method: "GET",
|
|
294
|
-
url: syntheticUrl,
|
|
295
|
-
requestHeaders: {},
|
|
296
|
-
status: 200,
|
|
297
|
-
responseHeaders: {},
|
|
298
|
-
responseBody: scrapeData.text,
|
|
299
|
-
contentType: "text/html",
|
|
300
|
-
isXHR: false,
|
|
301
|
-
isAsset: false,
|
|
302
|
-
score: 70,
|
|
303
|
-
isDomExtraction: true,
|
|
304
|
-
});
|
|
305
|
-
seenUrls.add(syntheticUrl);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
// Exclude endpoints that already failed or were pre-validated in previous attempts
|
|
312
|
-
const viable = candidates.filter((r) => !this.state.triedEndpoints.has(r.url) &&
|
|
313
|
-
!this.state.preValidatedEndpoints.has(r.url));
|
|
314
|
-
if (viable.length === 0) {
|
|
315
|
-
this.recordFailedEndpoints(agentResult);
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
// Prefer endpoints where seed value appears in URL (parameterizable), then by score
|
|
319
|
-
viable.sort((a, b) => {
|
|
320
|
-
const aHasSeed = seeds.some((sv) => a.url.toLowerCase().includes(sv));
|
|
321
|
-
const bHasSeed = seeds.some((sv) => b.url.toLowerCase().includes(sv));
|
|
322
|
-
if (aHasSeed && !bHasSeed)
|
|
323
|
-
return -1;
|
|
324
|
-
if (!aHasSeed && bHasSeed)
|
|
325
|
-
return 1;
|
|
326
|
-
return b.score - a.score;
|
|
327
|
-
});
|
|
328
|
-
const best = viable[0];
|
|
329
|
-
// Build URL template by replacing seed values
|
|
330
|
-
const urlTemplate = this.buildUrlTemplate(best.url);
|
|
331
|
-
return {
|
|
332
|
-
request: best,
|
|
333
|
-
urlTemplate,
|
|
334
|
-
score: best.score,
|
|
335
|
-
reproducible: true,
|
|
336
|
-
whyReproducible: this.explainReproducibility(best),
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
async preValidateEndpoint(endpoint, progress) {
|
|
340
|
-
this.state.preValidatedEndpoints.add(endpoint.request.url);
|
|
341
|
-
const template = endpoint.urlTemplate;
|
|
342
|
-
const hasParams = template.includes("{");
|
|
343
|
-
// Build test URL sets from seed values
|
|
344
|
-
const paramSets = [];
|
|
345
|
-
if (hasParams && this.seedValues.length > 0) {
|
|
346
|
-
// Primary set: exampleValues
|
|
347
|
-
const primary = new Map();
|
|
348
|
-
for (const s of this.seedValues) {
|
|
349
|
-
primary.set(s.paramName, s.exampleValue);
|
|
350
|
-
}
|
|
351
|
-
paramSets.push({ label: this.seedValues.map((s) => s.exampleValue).join(", "), values: primary });
|
|
352
|
-
// Alternative sets from alternativeExamples
|
|
353
|
-
const maxAlternatives = 3;
|
|
354
|
-
// Find the max number of alternative examples across all seeds
|
|
355
|
-
const altCount = Math.min(maxAlternatives, Math.max(...this.seedValues.map((s) => s.alternativeExamples?.length ?? 0)));
|
|
356
|
-
for (let i = 0; i < altCount; i++) {
|
|
357
|
-
const alt = new Map();
|
|
358
|
-
for (const s of this.seedValues) {
|
|
359
|
-
const altVal = s.alternativeExamples?.[i];
|
|
360
|
-
alt.set(s.paramName, altVal ?? s.exampleValue);
|
|
361
|
-
}
|
|
362
|
-
paramSets.push({ label: [...alt.values()].join(", "), values: alt });
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
// No params or no seeds — test the URL as-is
|
|
367
|
-
paramSets.push({ label: "original", values: new Map() });
|
|
368
|
-
}
|
|
369
|
-
// Limit to 4 sets total
|
|
370
|
-
const testSets = paramSets.slice(0, 4);
|
|
371
|
-
// Fetch each URL
|
|
372
|
-
const results = [];
|
|
373
|
-
for (let i = 0; i < testSets.length; i++) {
|
|
374
|
-
const set = testSets[i];
|
|
375
|
-
let testUrl = template;
|
|
376
|
-
for (const [param, value] of set.values) {
|
|
377
|
-
testUrl = testUrl.split(`{${param}}`).join(value);
|
|
378
|
-
}
|
|
379
|
-
progress(`Pre-validate [${i + 1}/${testSets.length}]: ${set.label}`);
|
|
380
|
-
try {
|
|
381
|
-
const resp = await fetch(testUrl, {
|
|
382
|
-
signal: AbortSignal.timeout(10_000),
|
|
383
|
-
headers: { "User-Agent": "Mozilla/5.0 (compatible; ZanBrowser/1.0)" },
|
|
384
|
-
});
|
|
385
|
-
const body = await resp.text();
|
|
386
|
-
results.push({
|
|
387
|
-
label: set.label,
|
|
388
|
-
url: testUrl,
|
|
389
|
-
status: resp.status,
|
|
390
|
-
bodyPreview: body.slice(0, 500),
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
catch (err) {
|
|
394
|
-
results.push({
|
|
395
|
-
label: set.label,
|
|
396
|
-
url: testUrl,
|
|
397
|
-
status: 0,
|
|
398
|
-
bodyPreview: "",
|
|
399
|
-
error: err.message,
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
// Rate limit pause between calls (skip after last)
|
|
403
|
-
if (i < testSets.length - 1) {
|
|
404
|
-
await new Promise((r) => setTimeout(r, 5000));
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
// LLM reasoning call (Haiku — lightweight)
|
|
408
|
-
const client = new sdk_1.default({ apiKey: this.ctx.anthropicApiKey });
|
|
409
|
-
const llmPrompt = `You are validating whether an API endpoint is generic and returns real data for different inputs.
|
|
410
|
-
|
|
411
|
-
GOAL: ${this.originalGoal}
|
|
412
|
-
ENDPOINT TEMPLATE: ${template}
|
|
413
|
-
|
|
414
|
-
TEST RESULTS:
|
|
415
|
-
${results.map((r) => `- Input: ${r.label}\n URL: ${r.url}\n Status: ${r.status}${r.error ? ` (error: ${r.error})` : ""}\n Body preview: ${r.bodyPreview.slice(0, 300)}`).join("\n\n")}
|
|
416
|
-
|
|
417
|
-
Answer these questions:
|
|
418
|
-
1. Do the results confirm this endpoint returns real data relevant to the goal?
|
|
419
|
-
2. Are null/empty responses expected (e.g. a product that doesn't exist) or do they indicate the endpoint is wrong?
|
|
420
|
-
3. Is there enough evidence that the endpoint is generic (works for different inputs)?
|
|
421
|
-
|
|
422
|
-
Respond ONLY with valid JSON:
|
|
423
|
-
{"valid": true/false, "reason": "one sentence explanation", "goodExamples": ["values that returned real data"]}`;
|
|
424
|
-
try {
|
|
425
|
-
const resp = await client.messages.create({
|
|
426
|
-
model: "claude-sonnet-4-6",
|
|
427
|
-
max_tokens: 400,
|
|
428
|
-
messages: [{ role: "user", content: llmPrompt }],
|
|
429
|
-
});
|
|
430
|
-
const text = resp.content[0].type === "text" ? resp.content[0].text : "{}";
|
|
431
|
-
// Extract JSON from response (handle possible markdown wrapping)
|
|
432
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
433
|
-
if (jsonMatch) {
|
|
434
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
435
|
-
return {
|
|
436
|
-
valid: parsed.valid === true,
|
|
437
|
-
reason: String(parsed.reason ?? "unknown"),
|
|
438
|
-
goodExamples: Array.isArray(parsed.goodExamples) ? parsed.goodExamples : [],
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
catch (err) {
|
|
443
|
-
progress(`Pre-validation LLM error: ${err.message}`);
|
|
444
|
-
}
|
|
445
|
-
// If LLM fails, check if at least one fetch returned 200 with substantial body
|
|
446
|
-
const anySuccess = results.some((r) => r.status >= 200 && r.status < 400 && r.bodyPreview.length > 100);
|
|
447
|
-
return {
|
|
448
|
-
valid: anySuccess,
|
|
449
|
-
reason: anySuccess ? "LLM validation failed but at least one fetch returned data" : "Could not validate endpoint",
|
|
450
|
-
goodExamples: results.filter((r) => r.status >= 200 && r.status < 400 && r.bodyPreview.length > 100).map((r) => r.label),
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
buildUrlTemplate(url) {
|
|
454
|
-
let template = url;
|
|
455
|
-
for (const seed of this.seedValues) {
|
|
456
|
-
if (template.includes(seed.exampleValue)) {
|
|
457
|
-
template = template.split(seed.exampleValue).join(`{${seed.paramName}}`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
return template;
|
|
461
|
-
}
|
|
462
|
-
explainReproducibility(req) {
|
|
463
|
-
const parts = [];
|
|
464
|
-
if (req.isXHR)
|
|
465
|
-
parts.push("real XHR/fetch request");
|
|
466
|
-
if (req.contentType?.includes("json"))
|
|
467
|
-
parts.push("returns JSON");
|
|
468
|
-
if (req.score >= 80)
|
|
469
|
-
parts.push("high relevance score");
|
|
470
|
-
if (req.method === "GET")
|
|
471
|
-
parts.push("stateless GET — no session dependency");
|
|
472
|
-
if (req.method === "POST" && req.requestBody)
|
|
473
|
-
parts.push("POST with known payload structure");
|
|
474
|
-
return parts.length > 0
|
|
475
|
-
? `Reproducible: ${parts.join(", ")}`
|
|
476
|
-
: "Captured as a real network request with structured response";
|
|
477
|
-
}
|
|
478
167
|
diagnoseFailure(agentResult) {
|
|
479
168
|
if (!agentResult.success) {
|
|
480
169
|
return agentResult.reason;
|
|
@@ -494,19 +183,6 @@ Respond ONLY with valid JSON:
|
|
|
494
183
|
}
|
|
495
184
|
return "Captured requests did not meet reproducibility criteria";
|
|
496
185
|
}
|
|
497
|
-
recordFailedEndpoints(agentResult) {
|
|
498
|
-
// Record fetch_url attempts that didn't produce a good result
|
|
499
|
-
for (const step of agentResult.steps) {
|
|
500
|
-
if (step.toolName === "fetch_url" && step.params.url) {
|
|
501
|
-
const url = String(step.params.url);
|
|
502
|
-
if (!this.state.triedEndpoints.has(url)) {
|
|
503
|
-
this.state.triedEndpoints.set(url, step.result.success
|
|
504
|
-
? "returned data but not structured/useful enough"
|
|
505
|
-
: String(step.result.error ?? "request failed"));
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
186
|
buildFailureSummary() {
|
|
511
187
|
const parts = [];
|
|
512
188
|
parts.push(`Exhausted ${this.state.attempts.length} attempts (${this.state.totalSteps} total steps)`);
|