browserclaw-agent 0.7.0 → 0.7.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.
- package/dist/agent-loop.d.ts +2 -0
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +127 -24
- package/dist/agent-loop.js.map +1 -1
- package/dist/llm.d.ts +5 -0
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +45 -22
- package/dist/llm.js.map +1 -1
- package/dist/prompt-log.d.ts +8 -0
- package/dist/prompt-log.d.ts.map +1 -1
- package/dist/prompt-log.js.map +1 -1
- package/dist/proxy.d.ts +9 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +85 -0
- package/dist/proxy.js.map +1 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +15 -8
- package/dist/routes.js.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/session-manager.d.ts +1 -7
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +180 -46
- package/dist/session-manager.js.map +1 -1
- package/dist/skills/press-and-hold.d.ts.map +1 -1
- package/dist/skills/press-and-hold.js +20 -14
- package/dist/skills/press-and-hold.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -3
package/dist/agent-loop.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type TerminationJudgment = {
|
|
|
26
26
|
export declare function shouldCheckTermination(step: number): boolean;
|
|
27
27
|
export interface PageHolder {
|
|
28
28
|
page: CrawlPage;
|
|
29
|
+
browser?: BrowserClaw;
|
|
30
|
+
ensureProxyForUrl?: (url: string) => Promise<void>;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* Extension points for consumers of the agent loop.
|
package/dist/agent-loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAmB1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAU9C,OAAO,KAAK,EAAE,WAAW,EAAa,eAAe,EAAiB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAkB/C;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiC5F;
|
|
1
|
+
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../src/agent-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAmB1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAU9C,OAAO,KAAK,EAAE,WAAW,EAAa,eAAe,EAAiB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAkB/C;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiC5F;AAwtBD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAsB1D;AAMD,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAIvE;AAiTD,MAAM,MAAM,mBAAmB,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtG;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG5D;AA2CD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAC5D,aAAa,CAAC,EAAE,MAAM,CACpB,MAAM,EACN,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC,CAC3G,CAAC;IACF,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAC3D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,aAAa,EAAE,MAAM,WAAW,EAAE,CAAC;IACnC,kIAAkI;IAClI,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,SAAS,GAAG,UAAU,EACpC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,EAC5C,MAAM,EAAE,WAAW,EACnB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EACnC,OAAO,CAAC,EAAE,WAAW,EACrB,kBAAkB,CAAC,EAAE,YAAY,GAAG,IAAI,EACxC,QAAQ,CAAC,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC,CAirC1B"}
|
package/dist/agent-loop.js
CHANGED
|
@@ -89,7 +89,7 @@ Respond with valid JSON:
|
|
|
89
89
|
"tab_id": "target ID of the tab to switch to or close (for switch_tab / close_tab)",
|
|
90
90
|
"hold_ms": "optional hold duration in ms (for press_and_hold) — set when the button/UI specifies a hold time or a prior attempt was too short",
|
|
91
91
|
"query": "search query (for web_search) — e.g. 'best apartment listing sites NYC'",
|
|
92
|
-
"answer": "direct answer to the user's question (for done)"
|
|
92
|
+
"answer": "string — direct answer to the user's question (for done). Always a single string; use markdown sections and bullet points to structure findings. Never an array or object."
|
|
93
93
|
}
|
|
94
94
|
]
|
|
95
95
|
}
|
|
@@ -241,7 +241,7 @@ Respond with valid JSON:
|
|
|
241
241
|
"memory": "Your accumulated findings",
|
|
242
242
|
"reasoning": "Final assessment",
|
|
243
243
|
"action": "done" or "fail",
|
|
244
|
-
"answer": "
|
|
244
|
+
"answer": "A SINGLE STRING containing your complete answer with all findings. Use markdown sections and bullet points to structure the prose — but the value must be one string, never a JSON array or object. Only include data you saw on actual pages — never fill gaps with training knowledge."
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
If you gathered useful data, use "done" with a structured answer. If not, use "fail" with what you tried.`;
|
|
@@ -312,6 +312,24 @@ const SCROLL_AT_VIEWPORT_FN = `
|
|
|
312
312
|
window.scrollBy(0, dy);
|
|
313
313
|
})
|
|
314
314
|
`;
|
|
315
|
+
async function safeUrl(page, fallback = '') {
|
|
316
|
+
try {
|
|
317
|
+
return await page.url();
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
logger.warn({ err, fallback }, 'page.url failed — using fallback');
|
|
321
|
+
return fallback;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function safeTitle(page) {
|
|
325
|
+
try {
|
|
326
|
+
return await page.title();
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
logger.warn({ err }, 'page.title failed — returning empty');
|
|
330
|
+
return '';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
315
333
|
async function safeSnapshot(page) {
|
|
316
334
|
let snapshot;
|
|
317
335
|
const t0 = Date.now();
|
|
@@ -637,11 +655,20 @@ function parseActions(parsed) {
|
|
|
637
655
|
if (typeof item.action !== 'string') {
|
|
638
656
|
throw new Error(`Action at index ${String(i)} missing "action" field`);
|
|
639
657
|
}
|
|
658
|
+
const rawAnswer = item.answer;
|
|
659
|
+
const answerIsBadShape = rawAnswer !== undefined && rawAnswer !== null && typeof rawAnswer !== 'string';
|
|
660
|
+
if (item.action === 'done' && answerIsBadShape) {
|
|
661
|
+
return {
|
|
662
|
+
action: 'wait',
|
|
663
|
+
...(i === 0 ? thinking : { reasoning: `Batch action ${String(i + 1)}: wait` }),
|
|
664
|
+
error_feedback: `Your "done.answer" was a ${typeof rawAnswer} (likely an array or object). It MUST be a single string — flatten every finding into markdown with sections and bullet points, then re-emit "done" with that string. Preserve every result you have already gathered.`,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
640
667
|
return {
|
|
641
668
|
action: item.action,
|
|
642
669
|
// Thinking fields only on the first action
|
|
643
670
|
...(i === 0 ? thinking : { reasoning: `Batch action ${String(i + 1)}: ${item.action}` }),
|
|
644
|
-
answer:
|
|
671
|
+
answer: typeof rawAnswer === 'string' ? rawAnswer : undefined,
|
|
645
672
|
ref: item.ref,
|
|
646
673
|
text: item.text,
|
|
647
674
|
url: item.url,
|
|
@@ -1011,7 +1038,20 @@ export async function runAgentLoop(prompt, pageOrHolder, emit, signal, waitForUs
|
|
|
1011
1038
|
: { page: pageOrHolder };
|
|
1012
1039
|
const history = [];
|
|
1013
1040
|
const startTime = Date.now();
|
|
1014
|
-
const
|
|
1041
|
+
const wallsHit = [];
|
|
1042
|
+
const sitesVisited = new Set();
|
|
1043
|
+
let recordsExtracted = 0;
|
|
1044
|
+
let lastKnownUrl = '';
|
|
1045
|
+
let activeBrowser = browser;
|
|
1046
|
+
let tabManager = activeBrowser !== undefined ? new TabManager(holder.page) : null;
|
|
1047
|
+
const refreshBrowserHandle = () => {
|
|
1048
|
+
const current = holder.browser ?? browser;
|
|
1049
|
+
if (current !== activeBrowser) {
|
|
1050
|
+
activeBrowser = current;
|
|
1051
|
+
tabManager = current !== undefined ? new TabManager(holder.page) : null;
|
|
1052
|
+
}
|
|
1053
|
+
return activeBrowser;
|
|
1054
|
+
};
|
|
1015
1055
|
let consecutiveParseFailures = 0;
|
|
1016
1056
|
let crossSiteParseFailures = 0;
|
|
1017
1057
|
let consecutiveApiFailures = 0;
|
|
@@ -1034,12 +1074,12 @@ export async function runAgentLoop(prompt, pageOrHolder, emit, signal, waitForUs
|
|
|
1034
1074
|
reasoning: reason,
|
|
1035
1075
|
answer,
|
|
1036
1076
|
};
|
|
1037
|
-
const currentUrl = await holder.page
|
|
1077
|
+
const currentUrl = await safeUrl(holder.page, lastKnownUrl);
|
|
1038
1078
|
history.push({
|
|
1039
1079
|
step,
|
|
1040
1080
|
action: doneAction,
|
|
1041
1081
|
url: currentUrl,
|
|
1042
|
-
page_title: await holder.page
|
|
1082
|
+
page_title: await safeTitle(holder.page),
|
|
1043
1083
|
timestamp: new Date().toISOString(),
|
|
1044
1084
|
});
|
|
1045
1085
|
emit('step', { step, action: 'done', reasoning: reason, answer });
|
|
@@ -1049,6 +1089,9 @@ export async function runAgentLoop(prompt, pageOrHolder, emit, signal, waitForUs
|
|
|
1049
1089
|
answer,
|
|
1050
1090
|
duration_ms: Date.now() - startTime,
|
|
1051
1091
|
final_url: currentUrl,
|
|
1092
|
+
walls_hit: wallsHit,
|
|
1093
|
+
sites_visited: [...sitesVisited],
|
|
1094
|
+
records_extracted: recordsExtracted,
|
|
1052
1095
|
};
|
|
1053
1096
|
};
|
|
1054
1097
|
let domainSkill = initialDomainSkill ?? null;
|
|
@@ -1158,6 +1201,9 @@ Respond with JSON: {"task": "the SMART task", "plan": "your action plan"}`,
|
|
|
1158
1201
|
steps: history,
|
|
1159
1202
|
error: 'Session aborted',
|
|
1160
1203
|
duration_ms: Date.now() - startTime,
|
|
1204
|
+
walls_hit: wallsHit,
|
|
1205
|
+
sites_visited: [...sitesVisited],
|
|
1206
|
+
records_extracted: recordsExtracted,
|
|
1161
1207
|
};
|
|
1162
1208
|
}
|
|
1163
1209
|
if (!(await isBrowserAlive(holder.page))) {
|
|
@@ -1169,6 +1215,9 @@ Respond with JSON: {"task": "the SMART task", "plan": "your action plan"}`,
|
|
|
1169
1215
|
error: 'Browser connection lost',
|
|
1170
1216
|
duration_ms: Date.now() - startTime,
|
|
1171
1217
|
final_url: history.length > 0 ? history[history.length - 1].url : undefined,
|
|
1218
|
+
walls_hit: wallsHit,
|
|
1219
|
+
sites_visited: [...sitesVisited],
|
|
1220
|
+
records_extracted: recordsExtracted,
|
|
1172
1221
|
};
|
|
1173
1222
|
}
|
|
1174
1223
|
baselineModalSigs ??= await capturePopupSignatures(holder.page);
|
|
@@ -1176,8 +1225,13 @@ Respond with JSON: {"task": "the SMART task", "plan": "your action plan"}`,
|
|
|
1176
1225
|
await dismissPopup(holder.page);
|
|
1177
1226
|
}
|
|
1178
1227
|
let snapshot = await safeSnapshot(holder.page);
|
|
1179
|
-
const url = await holder.page
|
|
1180
|
-
|
|
1228
|
+
const url = await safeUrl(holder.page, lastKnownUrl);
|
|
1229
|
+
if (url !== '')
|
|
1230
|
+
lastKnownUrl = url;
|
|
1231
|
+
const visitedDomain = extractDomain(url);
|
|
1232
|
+
if (visitedDomain !== '')
|
|
1233
|
+
sitesVisited.add(visitedDomain);
|
|
1234
|
+
const title = await safeTitle(holder.page);
|
|
1181
1235
|
if (options?.getSkill !== undefined) {
|
|
1182
1236
|
const currentDomain = extractDomain(url);
|
|
1183
1237
|
if (currentDomain !== '' && currentDomain !== lastLoadedSkillDomain) {
|
|
@@ -1202,6 +1256,10 @@ Respond with JSON: {"task": "the SMART task", "plan": "your action plan"}`,
|
|
|
1202
1256
|
const domText = await getPageText(holder.page);
|
|
1203
1257
|
const antiBotType = detectAntiBot(domText);
|
|
1204
1258
|
if (antiBotType !== null) {
|
|
1259
|
+
const wallDomain = extractDomain(url);
|
|
1260
|
+
if (wallDomain !== '' && !wallsHit.some((w) => w.domain === wallDomain && w.type === antiBotType)) {
|
|
1261
|
+
wallsHit.push({ domain: wallDomain, type: antiBotType });
|
|
1262
|
+
}
|
|
1205
1263
|
snapshot = enrichSnapshot(snapshot, domText, antiBotType);
|
|
1206
1264
|
const currentDomain = extractDomain(url);
|
|
1207
1265
|
const hits = (antiBotHitsByDomain.get(currentDomain) ?? 0) + 1;
|
|
@@ -1373,9 +1431,10 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1373
1431
|
lastStateSig = currentStateSig;
|
|
1374
1432
|
let tabCount;
|
|
1375
1433
|
let tabs;
|
|
1376
|
-
|
|
1434
|
+
const browserForStep = refreshBrowserHandle();
|
|
1435
|
+
if (browserForStep !== undefined) {
|
|
1377
1436
|
try {
|
|
1378
|
-
const allTabs = (await
|
|
1437
|
+
const allTabs = (await browserForStep.tabs());
|
|
1379
1438
|
tabCount = allTabs.length;
|
|
1380
1439
|
if (tabCount > 1)
|
|
1381
1440
|
tabs = allTabs;
|
|
@@ -1573,6 +1632,9 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1573
1632
|
steps: history,
|
|
1574
1633
|
error: userMessage,
|
|
1575
1634
|
duration_ms: Date.now() - startTime,
|
|
1635
|
+
walls_hit: wallsHit,
|
|
1636
|
+
sites_visited: [...sitesVisited],
|
|
1637
|
+
records_extracted: recordsExtracted,
|
|
1576
1638
|
};
|
|
1577
1639
|
}
|
|
1578
1640
|
consecutiveApiFailures++;
|
|
@@ -1589,6 +1651,9 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1589
1651
|
steps: history,
|
|
1590
1652
|
error: `Unable to reach the AI service after ${String(MAX_API_FAILURES)} attempts. Please try again.`,
|
|
1591
1653
|
duration_ms: Date.now() - startTime,
|
|
1654
|
+
walls_hit: wallsHit,
|
|
1655
|
+
sites_visited: [...sitesVisited],
|
|
1656
|
+
records_extracted: recordsExtracted,
|
|
1592
1657
|
};
|
|
1593
1658
|
}
|
|
1594
1659
|
continue;
|
|
@@ -1602,8 +1667,8 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1602
1667
|
const agentStep = {
|
|
1603
1668
|
step,
|
|
1604
1669
|
action,
|
|
1605
|
-
url: await holder.page
|
|
1606
|
-
page_title: await holder.page
|
|
1670
|
+
url: await safeUrl(holder.page, lastKnownUrl),
|
|
1671
|
+
page_title: await safeTitle(holder.page),
|
|
1607
1672
|
timestamp: new Date().toISOString(),
|
|
1608
1673
|
};
|
|
1609
1674
|
history.push(agentStep);
|
|
@@ -1649,6 +1714,9 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1649
1714
|
duration_ms: Date.now() - startTime,
|
|
1650
1715
|
final_url: agentStep.url,
|
|
1651
1716
|
validation_warnings: validationWarnings,
|
|
1717
|
+
walls_hit: wallsHit,
|
|
1718
|
+
sites_visited: [...sitesVisited],
|
|
1719
|
+
records_extracted: recordsExtracted,
|
|
1652
1720
|
};
|
|
1653
1721
|
}
|
|
1654
1722
|
if (action.action === 'fail') {
|
|
@@ -1658,6 +1726,9 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1658
1726
|
error: action.reasoning,
|
|
1659
1727
|
duration_ms: Date.now() - startTime,
|
|
1660
1728
|
final_url: agentStep.url,
|
|
1729
|
+
walls_hit: wallsHit,
|
|
1730
|
+
sites_visited: [...sitesVisited],
|
|
1731
|
+
records_extracted: recordsExtracted,
|
|
1661
1732
|
};
|
|
1662
1733
|
}
|
|
1663
1734
|
if (action.action === 'ask_user') {
|
|
@@ -1687,6 +1758,9 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1687
1758
|
error: `User did not respond to question: "${question}"`,
|
|
1688
1759
|
duration_ms: Date.now() - startTime,
|
|
1689
1760
|
final_url: history.length > 0 ? history[history.length - 1].url : undefined,
|
|
1761
|
+
walls_hit: wallsHit,
|
|
1762
|
+
sites_visited: [...sitesVisited],
|
|
1763
|
+
records_extracted: recordsExtracted,
|
|
1690
1764
|
};
|
|
1691
1765
|
}
|
|
1692
1766
|
emit('step_error', { step, error: message });
|
|
@@ -1695,13 +1769,16 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1695
1769
|
steps: history,
|
|
1696
1770
|
error: message,
|
|
1697
1771
|
duration_ms: Date.now() - startTime,
|
|
1772
|
+
walls_hit: wallsHit,
|
|
1773
|
+
sites_visited: [...sitesVisited],
|
|
1774
|
+
records_extracted: recordsExtracted,
|
|
1698
1775
|
};
|
|
1699
1776
|
}
|
|
1700
1777
|
step++;
|
|
1701
1778
|
break; // Need new snapshot after user response
|
|
1702
1779
|
}
|
|
1703
1780
|
if (action.action === 'press_and_hold') {
|
|
1704
|
-
const pahDomain = extractDomain(await holder.page
|
|
1781
|
+
const pahDomain = extractDomain(await safeUrl(holder.page, lastKnownUrl));
|
|
1705
1782
|
const priorFailures = pahFailuresByDomain.get(pahDomain) ?? 0;
|
|
1706
1783
|
if (priorFailures >= PAH_MAX_FAILURES) {
|
|
1707
1784
|
logger.warn({ step, pahDomain, pahFailureCount: priorFailures }, 'press_and_hold short-circuited — bail limit reached');
|
|
@@ -1756,10 +1833,23 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1756
1833
|
agentStep.action.extract_result = `Error: ${evalErr instanceof Error ? evalErr.message : 'evaluation failed'}`;
|
|
1757
1834
|
}
|
|
1758
1835
|
}
|
|
1759
|
-
else if (action.urls !== undefined && action.urls.length > 0
|
|
1836
|
+
else if (action.urls !== undefined && action.urls.length > 0) {
|
|
1760
1837
|
for (const url of action.urls)
|
|
1761
1838
|
assertNavigateUrlAllowed(url);
|
|
1762
|
-
|
|
1839
|
+
if (holder.ensureProxyForUrl !== undefined) {
|
|
1840
|
+
for (const url of action.urls) {
|
|
1841
|
+
await holder.ensureProxyForUrl(url);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
const extractBrowser = refreshBrowserHandle();
|
|
1845
|
+
if (extractBrowser === undefined) {
|
|
1846
|
+
agentStep.action.extract_result = 'Error: extract with urls requires browser context';
|
|
1847
|
+
recentFailureCount++;
|
|
1848
|
+
step++;
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
const batch = await extractItemsFromUrls(extractBrowser, action.urls);
|
|
1852
|
+
recordsExtracted += batch.count;
|
|
1763
1853
|
const header = batch.count >= 5
|
|
1764
1854
|
? `${String(batch.count)} records fetched across ${String(action.urls.length)} URLs. This is your data — parse the records and respond. Do NOT open individual URLs again; fields the user asked for (price, address, name) are embedded in each record's text or fields.\n`
|
|
1765
1855
|
: '';
|
|
@@ -1777,6 +1867,7 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1777
1867
|
}
|
|
1778
1868
|
else {
|
|
1779
1869
|
const items = await extractItems(holder.page);
|
|
1870
|
+
recordsExtracted += items.count;
|
|
1780
1871
|
const header = items.count >= 5
|
|
1781
1872
|
? `${String(items.count)} records extracted from ${items.source}. This is your data — parse and respond. Before drilling individual detail pages, check the active page-wide filters (URL path/query AND visible filter chips count): any user-prompt constraint that is already enforced by an active filter (price cap, neighborhood, pets allowed, etc.) propagates to every record — re-verifying it per detail page is wasted work. For constraints not enforced by any active filter, you do need to verify per record. Drill ONLY for missing per-record fields the user asked for (e.g. exact bedrooms, address, URL) or for constraints the active filters didn't enforce.\n`
|
|
1782
1873
|
: '';
|
|
@@ -1814,9 +1905,10 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1814
1905
|
}
|
|
1815
1906
|
// #2 Tab switching
|
|
1816
1907
|
if (action.action === 'switch_tab') {
|
|
1817
|
-
|
|
1908
|
+
const tabBrowser = refreshBrowserHandle();
|
|
1909
|
+
if (tabBrowser !== undefined && action.tab_id !== undefined && action.tab_id !== '') {
|
|
1818
1910
|
try {
|
|
1819
|
-
const newPage =
|
|
1911
|
+
const newPage = tabBrowser.page(action.tab_id);
|
|
1820
1912
|
const cdpBase = getCdpBaseUrl(holder.page);
|
|
1821
1913
|
await activateCdpTarget(cdpBase, action.tab_id);
|
|
1822
1914
|
holder.page = newPage;
|
|
@@ -1835,7 +1927,8 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1835
1927
|
break;
|
|
1836
1928
|
}
|
|
1837
1929
|
if (action.action === 'close_tab') {
|
|
1838
|
-
|
|
1930
|
+
const closeBrowser = refreshBrowserHandle();
|
|
1931
|
+
if (closeBrowser !== undefined && action.tab_id !== undefined && action.tab_id !== '') {
|
|
1839
1932
|
if (action.tab_id === holder.page.id) {
|
|
1840
1933
|
agentStep.action.error_feedback =
|
|
1841
1934
|
'close_tab: cannot close the active tab; switch to another tab first, then close this one';
|
|
@@ -1843,7 +1936,7 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1843
1936
|
}
|
|
1844
1937
|
else {
|
|
1845
1938
|
try {
|
|
1846
|
-
await
|
|
1939
|
+
await closeBrowser.close(action.tab_id);
|
|
1847
1940
|
logger.info({ step, tab_id: action.tab_id }, 'Closed tab');
|
|
1848
1941
|
}
|
|
1849
1942
|
catch (tabErr) {
|
|
@@ -1859,7 +1952,7 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1859
1952
|
step++;
|
|
1860
1953
|
break;
|
|
1861
1954
|
}
|
|
1862
|
-
const preActionUrl = await holder.page
|
|
1955
|
+
const preActionUrl = await safeUrl(holder.page, url);
|
|
1863
1956
|
if (action.action === 'navigate' && action.url !== undefined && action.url !== '') {
|
|
1864
1957
|
const targetDomain = extractDomain(action.url);
|
|
1865
1958
|
if (targetDomain !== '' && walledDomains.has(targetDomain)) {
|
|
@@ -1870,9 +1963,12 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1870
1963
|
}
|
|
1871
1964
|
}
|
|
1872
1965
|
try {
|
|
1966
|
+
if (action.action === 'navigate' && action.url !== undefined && holder.ensureProxyForUrl !== undefined) {
|
|
1967
|
+
await holder.ensureProxyForUrl(action.url);
|
|
1968
|
+
}
|
|
1873
1969
|
const customHandler = options?.customActions?.[action.action];
|
|
1874
1970
|
if (customHandler !== undefined) {
|
|
1875
|
-
const result = await customHandler(action, holder.page,
|
|
1971
|
+
const result = await customHandler(action, holder.page, refreshBrowserHandle());
|
|
1876
1972
|
if (result?.outcome !== undefined)
|
|
1877
1973
|
agentStep.outcome = result.outcome;
|
|
1878
1974
|
}
|
|
@@ -1898,7 +1994,7 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1898
1994
|
// Validate action outcome — provide natural language feedback
|
|
1899
1995
|
let postActionUrl = preActionUrl;
|
|
1900
1996
|
try {
|
|
1901
|
-
postActionUrl = await holder.page
|
|
1997
|
+
postActionUrl = await safeUrl(holder.page, preActionUrl);
|
|
1902
1998
|
const postSnapshot = (await holder.page.snapshot({ interactive: true, compact: true, timeoutMs: SNAPSHOT_TIMEOUT_MS })).snapshot;
|
|
1903
1999
|
const outcome = validateAction(action, preActionUrl, postActionUrl, snapshot.length, postSnapshot.length);
|
|
1904
2000
|
if (outcome !== undefined) {
|
|
@@ -1969,8 +2065,9 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
1969
2065
|
break; // Break batch on failure — need new snapshot
|
|
1970
2066
|
}
|
|
1971
2067
|
// Check for new tabs after click
|
|
1972
|
-
|
|
1973
|
-
|
|
2068
|
+
const tabClickBrowser = refreshBrowserHandle();
|
|
2069
|
+
if (tabManager !== null && tabClickBrowser !== undefined && action.action === 'click') {
|
|
2070
|
+
const newPage = await tabManager.checkForNewTab(tabClickBrowser);
|
|
1974
2071
|
if (newPage !== null) {
|
|
1975
2072
|
try {
|
|
1976
2073
|
const newUrl = await newPage.url();
|
|
@@ -2015,6 +2112,9 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
2015
2112
|
answer: judgment.answer,
|
|
2016
2113
|
duration_ms: Date.now() - startTime,
|
|
2017
2114
|
final_url: history.length > 0 ? history[history.length - 1].url : undefined,
|
|
2115
|
+
walls_hit: wallsHit,
|
|
2116
|
+
sites_visited: [...sitesVisited],
|
|
2117
|
+
records_extracted: recordsExtracted,
|
|
2018
2118
|
};
|
|
2019
2119
|
}
|
|
2020
2120
|
const fallback = await getFinalSummary(refinedPrompt, history);
|
|
@@ -2025,6 +2125,9 @@ Respond with JSON: {"plan": "your revised plan here"}`,
|
|
|
2025
2125
|
error: `Reached maximum step limit (${String(maxSteps)}); still missing: ${judgment.missing}`,
|
|
2026
2126
|
duration_ms: Date.now() - startTime,
|
|
2027
2127
|
final_url: history.length > 0 ? history[history.length - 1].url : undefined,
|
|
2128
|
+
walls_hit: wallsHit,
|
|
2129
|
+
sites_visited: [...sitesVisited],
|
|
2130
|
+
records_extracted: recordsExtracted,
|
|
2028
2131
|
};
|
|
2029
2132
|
}
|
|
2030
2133
|
//# sourceMappingURL=agent-loop.js.map
|