agentpage 0.0.51 → 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 +59 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -738,6 +738,45 @@ function detectIdleLoop(toolCalls, consecutiveReadOnlyRounds) {
|
|
|
738
738
|
}
|
|
739
739
|
return 0;
|
|
740
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
|
+
}
|
|
741
780
|
|
|
742
781
|
//#endregion
|
|
743
782
|
//#region src/core/agent-loop/index.ts
|
|
@@ -841,6 +880,7 @@ async function executeAgentLoop(params) {
|
|
|
841
880
|
};
|
|
842
881
|
let recoveryCount = 0;
|
|
843
882
|
let redundantInterceptCount = 0;
|
|
883
|
+
const ineffectiveClickSelectors = /* @__PURE__ */ new Set();
|
|
844
884
|
let pendingNotFoundRetry;
|
|
845
885
|
let snapshotReadCount = 0;
|
|
846
886
|
let snapshotSizeTotal = 0;
|
|
@@ -1025,6 +1065,7 @@ async function executeAgentLoop(params) {
|
|
|
1025
1065
|
let roundHasConfirmedProgress = false;
|
|
1026
1066
|
const executedTaskCalls = [];
|
|
1027
1067
|
const roundMissingTasks = [];
|
|
1068
|
+
const roundClickSelectors = [];
|
|
1028
1069
|
for (const tc of response.toolCalls) {
|
|
1029
1070
|
if (tc.name === "dom" && getToolAction(tc.input) === "scroll") {
|
|
1030
1071
|
const ref = extractHashSelectorRef(tc.input);
|
|
@@ -1037,6 +1078,15 @@ async function executeAgentLoop(params) {
|
|
|
1037
1078
|
callbacks?.onToolResult?.(tc.name, redundant);
|
|
1038
1079
|
continue;
|
|
1039
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
|
+
}
|
|
1040
1090
|
callbacks?.onToolCall?.(tc.name, tc.input);
|
|
1041
1091
|
let result = await registry.dispatch(tc.name, tc.input);
|
|
1042
1092
|
const debounced = applySnapshotDebounce(tc.name, tc.input, result, consecutiveSnapshotCalls);
|
|
@@ -1050,6 +1100,10 @@ async function executeAgentLoop(params) {
|
|
|
1050
1100
|
name: tc.name,
|
|
1051
1101
|
input: tc.input
|
|
1052
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
|
+
}
|
|
1053
1107
|
const missingTask = collectMissingTask(tc.name, tc.input, result);
|
|
1054
1108
|
if (missingTask) roundMissingTasks.push(missingTask);
|
|
1055
1109
|
if (result.details && typeof result.details === "object") roundHasError = roundHasError || Boolean(result.details.error);
|
|
@@ -1108,7 +1162,11 @@ async function executeAgentLoop(params) {
|
|
|
1108
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."
|
|
1109
1163
|
].join("\n");
|
|
1110
1164
|
protocolViolationHint = protocolViolationHint ? protocolViolationHint + "\n\n" + unchangedHint : unchangedHint;
|
|
1111
|
-
|
|
1165
|
+
for (const sel of roundClickSelectors) ineffectiveClickSelectors.add(sel);
|
|
1166
|
+
} else if (roundEndFingerprint !== roundStartFingerprint) {
|
|
1167
|
+
consecutiveNoProtocolRounds = 0;
|
|
1168
|
+
ineffectiveClickSelectors.clear();
|
|
1169
|
+
}
|
|
1112
1170
|
}
|
|
1113
1171
|
if (consecutiveNoProtocolRounds >= 5) {
|
|
1114
1172
|
finalReply = response.text?.trim() || "任务已完成。";
|