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 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
- } else if (roundEndFingerprint !== roundStartFingerprint) consecutiveNoProtocolRounds = 0;
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() || "任务已完成。";