agentpage 0.0.50 → 0.0.52
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/index.mjs +63 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -550,6 +550,8 @@ function buildCompactMessages(userMessage, trace, latestSnapshot, currentUrl, hi
|
|
|
550
550
|
});
|
|
551
551
|
const hasErrors = trace.some((e) => hasToolError(e.result));
|
|
552
552
|
const contextParts = [
|
|
553
|
+
`Original Goal: ${userMessage}`,
|
|
554
|
+
"",
|
|
553
555
|
`Remaining: ${activeInstruction}`,
|
|
554
556
|
"",
|
|
555
557
|
"Batch fills per round; clicks end the round — at most ONE click (last). Do NOT call page_info (snapshot is auto-refreshed).",
|
|
@@ -736,6 +738,45 @@ function detectIdleLoop(toolCalls, consecutiveReadOnlyRounds) {
|
|
|
736
738
|
}
|
|
737
739
|
return 0;
|
|
738
740
|
}
|
|
741
|
+
/**
|
|
742
|
+
* 重复无效点击拦截。
|
|
743
|
+
*
|
|
744
|
+
* 场景:模型反复点击同一个 selector 但页面快照从未变化。
|
|
745
|
+
* 框架在每轮结束时通过快照指纹对比发现点击无效后,将 selector 加入
|
|
746
|
+
* `ineffectiveClickSelectors` 集合。下一轮模型再次点击相同 selector 时,
|
|
747
|
+
* 直接拦截并返回错误提示,引导模型换目标。
|
|
748
|
+
*
|
|
749
|
+
* 使用方式:
|
|
750
|
+
* - 在工具执行前调用此函数
|
|
751
|
+
* - 仅对 `dom.click` 动作生效
|
|
752
|
+
* - 返回 null 表示放行,返回 ToolCallResult 表示拦截
|
|
753
|
+
*
|
|
754
|
+
* 集合的维护由 index.ts 负责:
|
|
755
|
+
* - 轮次结束且快照未变:将本轮 click 的 selector 加入集合
|
|
756
|
+
* - 快照变化:清空集合(页面已改变,之前的判定失效)
|
|
757
|
+
*/
|
|
758
|
+
function checkIneffectiveClickRepeat(toolName, toolInput, ineffectiveClickSelectors) {
|
|
759
|
+
if (toolName !== "dom") return null;
|
|
760
|
+
if (getToolAction(toolInput) !== "click") return null;
|
|
761
|
+
const selector = toolInput && typeof toolInput === "object" ? toolInput.selector : void 0;
|
|
762
|
+
if (typeof selector !== "string" || !selector) return null;
|
|
763
|
+
if (!ineffectiveClickSelectors.has(selector)) return null;
|
|
764
|
+
return {
|
|
765
|
+
content: [
|
|
766
|
+
`Click on ${selector} was BLOCKED — this target was already clicked in a previous round with NO visible effect on the page.`,
|
|
767
|
+
"You MUST try a DIFFERENT element. Suggestions:",
|
|
768
|
+
"1) Look INSIDE the clicked container for an <a>, <button>, or child with clk/pdn/mdn listener",
|
|
769
|
+
"2) Try a parent or sibling element with stronger click signal",
|
|
770
|
+
"3) Use evaluate to inspect or trigger navigation programmatically",
|
|
771
|
+
"4) Try a completely different approach (search, sidebar, direct URL navigation)"
|
|
772
|
+
].join("\n"),
|
|
773
|
+
details: {
|
|
774
|
+
error: true,
|
|
775
|
+
code: "INEFFECTIVE_CLICK_BLOCKED",
|
|
776
|
+
selector
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
739
780
|
|
|
740
781
|
//#endregion
|
|
741
782
|
//#region src/core/agent-loop/index.ts
|
|
@@ -839,6 +880,7 @@ async function executeAgentLoop(params) {
|
|
|
839
880
|
};
|
|
840
881
|
let recoveryCount = 0;
|
|
841
882
|
let redundantInterceptCount = 0;
|
|
883
|
+
const ineffectiveClickSelectors = /* @__PURE__ */ new Set();
|
|
842
884
|
let pendingNotFoundRetry;
|
|
843
885
|
let snapshotReadCount = 0;
|
|
844
886
|
let snapshotSizeTotal = 0;
|
|
@@ -1023,6 +1065,7 @@ async function executeAgentLoop(params) {
|
|
|
1023
1065
|
let roundHasConfirmedProgress = false;
|
|
1024
1066
|
const executedTaskCalls = [];
|
|
1025
1067
|
const roundMissingTasks = [];
|
|
1068
|
+
const roundClickSelectors = [];
|
|
1026
1069
|
for (const tc of response.toolCalls) {
|
|
1027
1070
|
if (tc.name === "dom" && getToolAction(tc.input) === "scroll") {
|
|
1028
1071
|
const ref = extractHashSelectorRef(tc.input);
|
|
@@ -1035,6 +1078,15 @@ async function executeAgentLoop(params) {
|
|
|
1035
1078
|
callbacks?.onToolResult?.(tc.name, redundant);
|
|
1036
1079
|
continue;
|
|
1037
1080
|
}
|
|
1081
|
+
const ineffective = checkIneffectiveClickRepeat(tc.name, tc.input, ineffectiveClickSelectors);
|
|
1082
|
+
if (ineffective) {
|
|
1083
|
+
appendToolTrace(round, tc.name, tc.input, ineffective);
|
|
1084
|
+
redundantInterceptCount += 1;
|
|
1085
|
+
callbacks?.onToolCall?.(tc.name, tc.input);
|
|
1086
|
+
callbacks?.onToolResult?.(tc.name, ineffective);
|
|
1087
|
+
roundHasError = true;
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1038
1090
|
callbacks?.onToolCall?.(tc.name, tc.input);
|
|
1039
1091
|
let result = await registry.dispatch(tc.name, tc.input);
|
|
1040
1092
|
const debounced = applySnapshotDebounce(tc.name, tc.input, result, consecutiveSnapshotCalls);
|
|
@@ -1048,6 +1100,10 @@ async function executeAgentLoop(params) {
|
|
|
1048
1100
|
name: tc.name,
|
|
1049
1101
|
input: tc.input
|
|
1050
1102
|
});
|
|
1103
|
+
if (tc.name === "dom" && getToolAction(tc.input) === "click" && !hasToolError(result)) {
|
|
1104
|
+
const sel = tc.input && typeof tc.input === "object" ? tc.input.selector : void 0;
|
|
1105
|
+
if (typeof sel === "string" && sel) roundClickSelectors.push(sel);
|
|
1106
|
+
}
|
|
1051
1107
|
const missingTask = collectMissingTask(tc.name, tc.input, result);
|
|
1052
1108
|
if (missingTask) roundMissingTasks.push(missingTask);
|
|
1053
1109
|
if (result.details && typeof result.details === "object") roundHasError = roundHasError || Boolean(result.details.error);
|
|
@@ -1106,7 +1162,11 @@ async function executeAgentLoop(params) {
|
|
|
1106
1162
|
"- Look INSIDE the target for <a>/<button>/child with clk listener, or try a parent/sibling with stronger signal, or use a completely different approach."
|
|
1107
1163
|
].join("\n");
|
|
1108
1164
|
protocolViolationHint = protocolViolationHint ? protocolViolationHint + "\n\n" + unchangedHint : unchangedHint;
|
|
1109
|
-
|
|
1165
|
+
for (const sel of roundClickSelectors) ineffectiveClickSelectors.add(sel);
|
|
1166
|
+
} else if (roundEndFingerprint !== roundStartFingerprint) {
|
|
1167
|
+
consecutiveNoProtocolRounds = 0;
|
|
1168
|
+
ineffectiveClickSelectors.clear();
|
|
1169
|
+
}
|
|
1110
1170
|
}
|
|
1111
1171
|
if (consecutiveNoProtocolRounds >= 5) {
|
|
1112
1172
|
finalReply = response.text?.trim() || "任务已完成。";
|
|
@@ -2273,6 +2333,8 @@ function buildSystemPrompt(params = {}) {
|
|
|
2273
2333
|
"You are AutoPilot, an AI agent controlling the current web page via tools.",
|
|
2274
2334
|
"",
|
|
2275
2335
|
"## Core Rules",
|
|
2336
|
+
"- **Original Goal Anchor:** The user's original input is provided as `Original Goal` every round. Your plan and each action must NEVER deviate from it. If the current page shows a 'Create X' button but the user said 'go to X', you must navigate INTO existing X, NOT create a new one.",
|
|
2337
|
+
"- **Goal decomposition:** Distinguish the TARGET entity from the ACTION to perform. 'go to X and do Y' = locate X → enter X → do Y inside X. 'create X' = make a new X. 'edit X' = find existing X → modify it. Never confuse navigating to an entity with creating/deleting/modifying it. If the target entity is not visible, search or filter for it first — do not pick the nearest similarly-named button.",
|
|
2276
2338
|
"- Work from CURRENT snapshot + remaining task. Do not restate.",
|
|
2277
2339
|
"- Task reduction: (remaining, prev actions, this-round) → new remaining.",
|
|
2278
2340
|
"- Use #hashID from snapshot as selector. Do not guess CSS selectors.",
|