@weavetab/mcp 2.5.0-beta.0
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/AGENTS.md +48 -0
- package/ARCHITECTURE.md +590 -0
- package/LICENSE +631 -0
- package/README.md +909 -0
- package/SECURITY.md +196 -0
- package/TROUBLESHOOTING.md +365 -0
- package/dist/anticaptcha/detector.d.ts +49 -0
- package/dist/anticaptcha/detector.js +465 -0
- package/dist/anticaptcha/integration.d.ts +36 -0
- package/dist/anticaptcha/integration.js +123 -0
- package/dist/anticaptcha/solver.d.ts +27 -0
- package/dist/anticaptcha/solver.js +552 -0
- package/dist/audit/logger.d.ts +13 -0
- package/dist/audit/logger.js +177 -0
- package/dist/audit/telemetry.d.ts +36 -0
- package/dist/audit/telemetry.js +162 -0
- package/dist/cdp/bridge.d.ts +16 -0
- package/dist/cdp/bridge.js +826 -0
- package/dist/cdp/confirm.d.ts +26 -0
- package/dist/cdp/confirm.js +79 -0
- package/dist/cdp/connector.d.ts +37 -0
- package/dist/cdp/connector.js +661 -0
- package/dist/cdp/extension.d.ts +36 -0
- package/dist/cdp/extension.js +118 -0
- package/dist/cdp/finder.d.ts +1 -0
- package/dist/cdp/finder.js +2 -0
- package/dist/cdp/helpers.d.ts +11 -0
- package/dist/cdp/helpers.js +195 -0
- package/dist/cdp/label_cache.d.ts +12 -0
- package/dist/cdp/label_cache.js +51 -0
- package/dist/cdp/lock.d.ts +33 -0
- package/dist/cdp/lock.js +206 -0
- package/dist/cdp/omni_broker.d.ts +17 -0
- package/dist/cdp/omni_broker.js +119 -0
- package/dist/cdp/profiles.d.ts +1 -0
- package/dist/cdp/profiles.js +87 -0
- package/dist/cdp/pruner.d.ts +41 -0
- package/dist/cdp/pruner.js +263 -0
- package/dist/cdp/ref_cache.d.ts +101 -0
- package/dist/cdp/ref_cache.js +234 -0
- package/dist/cdp/retry.d.ts +5 -0
- package/dist/cdp/retry.js +39 -0
- package/dist/cdp/retry_enhanced.d.ts +44 -0
- package/dist/cdp/retry_enhanced.js +102 -0
- package/dist/cdp/security.d.ts +50 -0
- package/dist/cdp/security.js +237 -0
- package/dist/cdp/verify.d.ts +39 -0
- package/dist/cdp/verify.js +143 -0
- package/dist/cdp/walker.d.ts +91 -0
- package/dist/cdp/walker.js +896 -0
- package/dist/cdp/walker.test.d.ts +2 -0
- package/dist/cdp/walker.test.js +172 -0
- package/dist/cdp/websocket_transport.d.ts +15 -0
- package/dist/cdp/websocket_transport.js +118 -0
- package/dist/cli/plugin.d.ts +9 -0
- package/dist/cli/plugin.js +1134 -0
- package/dist/cli/updater.d.ts +1 -0
- package/dist/cli/updater.js +72 -0
- package/dist/config/loader.d.ts +120 -0
- package/dist/config/loader.js +704 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1248 -0
- package/dist/intelligence/hints.d.ts +11 -0
- package/dist/intelligence/hints.js +39 -0
- package/dist/intelligence/intent.d.ts +33 -0
- package/dist/intelligence/intent.js +171 -0
- package/dist/intelligence/sessionMemory.d.ts +66 -0
- package/dist/intelligence/sessionMemory.js +284 -0
- package/dist/intelligence/sessionMemory.test.d.ts +2 -0
- package/dist/intelligence/sessionMemory.test.js +62 -0
- package/dist/intelligence/trail.d.ts +64 -0
- package/dist/intelligence/trail.js +233 -0
- package/dist/intelligence/trail.test.d.ts +2 -0
- package/dist/intelligence/trail.test.js +110 -0
- package/dist/intelligence/vision.d.ts +24 -0
- package/dist/intelligence/vision.js +151 -0
- package/dist/lifecycle/mission_manager.d.ts +16 -0
- package/dist/lifecycle/mission_manager.js +44 -0
- package/dist/overlay/bubble.d.ts +16 -0
- package/dist/overlay/bubble.js +37 -0
- package/dist/overlay/ghost.d.ts +35 -0
- package/dist/overlay/ghost.js +508 -0
- package/dist/overlay/injector.d.ts +22 -0
- package/dist/overlay/injector.js +198 -0
- package/dist/plugin/interface.d.ts +134 -0
- package/dist/plugin/interface.js +1 -0
- package/dist/plugin/loader.d.ts +18 -0
- package/dist/plugin/loader.js +155 -0
- package/dist/plugin/registry.d.ts +50 -0
- package/dist/plugin/registry.js +234 -0
- package/dist/security/blacklist.d.ts +24 -0
- package/dist/security/blacklist.js +80 -0
- package/dist/security/filter.d.ts +2 -0
- package/dist/security/filter.js +20 -0
- package/dist/security/guard.d.ts +8 -0
- package/dist/security/guard.js +35 -0
- package/dist/security/ratelimit.d.ts +11 -0
- package/dist/security/ratelimit.js +57 -0
- package/dist/security/secrets.d.ts +75 -0
- package/dist/security/secrets.js +365 -0
- package/dist/sensors/actionability.d.ts +81 -0
- package/dist/sensors/actionability.js +303 -0
- package/dist/sensors/guard.d.ts +25 -0
- package/dist/sensors/guard.js +168 -0
- package/dist/sensors/mutations.d.ts +23 -0
- package/dist/sensors/mutations.js +93 -0
- package/dist/sensors/network.d.ts +67 -0
- package/dist/sensors/network.js +193 -0
- package/dist/sensors/operation_monitor.d.ts +80 -0
- package/dist/sensors/operation_monitor.js +203 -0
- package/dist/sensors/pacing.d.ts +44 -0
- package/dist/sensors/pacing.js +111 -0
- package/dist/sensors/thoughts.d.ts +2 -0
- package/dist/sensors/thoughts.js +59 -0
- package/dist/sensors/wait.d.ts +12 -0
- package/dist/sensors/wait.js +44 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +1687 -0
- package/dist/state/agent.d.ts +36 -0
- package/dist/state/agent.js +101 -0
- package/dist/state/cache.d.ts +16 -0
- package/dist/state/cache.js +92 -0
- package/dist/state/cache.test.d.ts +2 -0
- package/dist/state/cache.test.js +76 -0
- package/dist/state/delta.d.ts +71 -0
- package/dist/state/delta.js +234 -0
- package/dist/state/delta.test.d.ts +2 -0
- package/dist/state/delta.test.js +279 -0
- package/dist/state/groups.d.ts +15 -0
- package/dist/state/groups.js +80 -0
- package/dist/state/session.d.ts +30 -0
- package/dist/state/session.js +155 -0
- package/dist/tools/accessibility.d.ts +14 -0
- package/dist/tools/accessibility.js +32 -0
- package/dist/tools/ask.d.ts +14 -0
- package/dist/tools/ask.js +49 -0
- package/dist/tools/automation.d.ts +36 -0
- package/dist/tools/automation.js +218 -0
- package/dist/tools/browser_detect.d.ts +13 -0
- package/dist/tools/browser_detect.js +73 -0
- package/dist/tools/burst.d.ts +35 -0
- package/dist/tools/burst.js +344 -0
- package/dist/tools/canvas.d.ts +25 -0
- package/dist/tools/canvas.js +132 -0
- package/dist/tools/captcha.d.ts +38 -0
- package/dist/tools/captcha.js +209 -0
- package/dist/tools/click.d.ts +36 -0
- package/dist/tools/click.js +561 -0
- package/dist/tools/click_and_wait.d.ts +33 -0
- package/dist/tools/click_and_wait.js +149 -0
- package/dist/tools/clipboard.d.ts +29 -0
- package/dist/tools/clipboard.js +279 -0
- package/dist/tools/console.d.ts +21 -0
- package/dist/tools/console.js +107 -0
- package/dist/tools/cookies.d.ts +67 -0
- package/dist/tools/cookies.js +121 -0
- package/dist/tools/design_extract.d.ts +109 -0
- package/dist/tools/design_extract.js +641 -0
- package/dist/tools/dialog.d.ts +24 -0
- package/dist/tools/dialog.js +109 -0
- package/dist/tools/drag.d.ts +15 -0
- package/dist/tools/drag.js +29 -0
- package/dist/tools/emulation.d.ts +33 -0
- package/dist/tools/emulation.js +69 -0
- package/dist/tools/eval.d.ts +20 -0
- package/dist/tools/eval.js +92 -0
- package/dist/tools/fill.d.ts +41 -0
- package/dist/tools/fill.js +667 -0
- package/dist/tools/find.d.ts +30 -0
- package/dist/tools/find.js +60 -0
- package/dist/tools/geo.d.ts +19 -0
- package/dist/tools/geo.js +29 -0
- package/dist/tools/github/analyze.d.ts +4 -0
- package/dist/tools/github/analyze.js +113 -0
- package/dist/tools/github/cache.d.ts +21 -0
- package/dist/tools/github/cache.js +28 -0
- package/dist/tools/github/index.d.ts +4 -0
- package/dist/tools/github/index.js +4 -0
- package/dist/tools/github/issues.d.ts +9 -0
- package/dist/tools/github/issues.js +88 -0
- package/dist/tools/github/pr.d.ts +5 -0
- package/dist/tools/github/pr.js +88 -0
- package/dist/tools/github/rateLimiter.d.ts +4 -0
- package/dist/tools/github/rateLimiter.js +46 -0
- package/dist/tools/github/read.d.ts +34 -0
- package/dist/tools/github/read.js +74 -0
- package/dist/tools/github/repoContext.d.ts +20 -0
- package/dist/tools/github/repoContext.js +129 -0
- package/dist/tools/highlight.d.ts +20 -0
- package/dist/tools/highlight.js +140 -0
- package/dist/tools/hover.d.ts +19 -0
- package/dist/tools/hover.js +57 -0
- package/dist/tools/inspect.d.ts +17 -0
- package/dist/tools/inspect.js +125 -0
- package/dist/tools/integration.test.d.ts +2 -0
- package/dist/tools/integration.test.js +96 -0
- package/dist/tools/key.d.ts +25 -0
- package/dist/tools/key.js +173 -0
- package/dist/tools/macro_compiler.d.ts +7 -0
- package/dist/tools/macro_compiler.js +82 -0
- package/dist/tools/mouse.d.ts +21 -0
- package/dist/tools/mouse.js +62 -0
- package/dist/tools/navigate.d.ts +16 -0
- package/dist/tools/navigate.js +188 -0
- package/dist/tools/network_intercept.d.ts +17 -0
- package/dist/tools/network_intercept.js +151 -0
- package/dist/tools/office/index.d.ts +9 -0
- package/dist/tools/office/index.js +7 -0
- package/dist/tools/office/list.d.ts +15 -0
- package/dist/tools/office/list.js +41 -0
- package/dist/tools/office/parse.d.ts +20 -0
- package/dist/tools/office/parse.js +172 -0
- package/dist/tools/pdf.d.ts +22 -0
- package/dist/tools/pdf.js +42 -0
- package/dist/tools/performance.d.ts +12 -0
- package/dist/tools/performance.js +47 -0
- package/dist/tools/performance_test.d.ts +70 -0
- package/dist/tools/performance_test.js +128 -0
- package/dist/tools/plan.d.ts +39 -0
- package/dist/tools/plan.js +161 -0
- package/dist/tools/pointer.d.ts +35 -0
- package/dist/tools/pointer.js +522 -0
- package/dist/tools/read.d.ts +30 -0
- package/dist/tools/read.js +488 -0
- package/dist/tools/recording.d.ts +45 -0
- package/dist/tools/recording.js +124 -0
- package/dist/tools/response.d.ts +129 -0
- package/dist/tools/response.js +5 -0
- package/dist/tools/scrape.d.ts +24 -0
- package/dist/tools/scrape.js +140 -0
- package/dist/tools/screenshot.d.ts +40 -0
- package/dist/tools/screenshot.js +79 -0
- package/dist/tools/scroll.d.ts +23 -0
- package/dist/tools/scroll.js +196 -0
- package/dist/tools/select.d.ts +13 -0
- package/dist/tools/select.js +217 -0
- package/dist/tools/snapshot.d.ts +27 -0
- package/dist/tools/snapshot.js +99 -0
- package/dist/tools/storage.d.ts +18 -0
- package/dist/tools/storage.js +29 -0
- package/dist/tools/tabs.d.ts +77 -0
- package/dist/tools/tabs.js +342 -0
- package/dist/tools/thought.d.ts +16 -0
- package/dist/tools/thought.js +79 -0
- package/dist/tools/type.d.ts +65 -0
- package/dist/tools/type.js +1061 -0
- package/dist/tools/type_secret.d.ts +23 -0
- package/dist/tools/type_secret.js +229 -0
- package/dist/tools/upload.d.ts +19 -0
- package/dist/tools/upload.js +103 -0
- package/dist/tools/video_compiler.d.ts +24 -0
- package/dist/tools/video_compiler.js +199 -0
- package/dist/tools/viewport.d.ts +44 -0
- package/dist/tools/viewport.js +230 -0
- package/dist/tools/wait.d.ts +19 -0
- package/dist/tools/wait.js +202 -0
- package/dist/tools/wait_for.d.ts +23 -0
- package/dist/tools/wait_for.js +30 -0
- package/dist/tools/windows.d.ts +22 -0
- package/dist/tools/windows.js +57 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +12 -0
- package/docs/DEV_MODE.md +68 -0
- package/docs/DOCUMENTATION.md +532 -0
- package/docs/PLUGINS.md +538 -0
- package/docs/TOOLS.md +104 -0
- package/docs/dev_plugin.md +310 -0
- package/ext-dist/background.js +467 -0
- package/ext-dist/content.js +3089 -0
- package/ext-dist/dashboard.html +583 -0
- package/ext-dist/hud/hud.js +73 -0
- package/ext-dist/hud/index.html +20 -0
- package/ext-dist/manifest.json +77 -0
- package/ext-dist/sidepanel/sidepanel.js +25 -0
- package/ext-dist/sidepanel.html +547 -0
- package/ext-dist/styles.css +218 -0
- package/icon.png +0 -0
- package/mcp.json +796 -0
- package/package.json +113 -0
- package/server.json +23 -0
- package/smithery.yaml +11 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type TrailEntry = {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
timestamp: string;
|
|
4
|
+
tool: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
tabId?: string;
|
|
7
|
+
input: Record<string, unknown>;
|
|
8
|
+
result: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
export declare function recordAction(entry: Omit<TrailEntry, "sessionId">): void;
|
|
11
|
+
export declare function getLastAction(): TrailEntry | null;
|
|
12
|
+
export declare function getTrail(): TrailEntry[];
|
|
13
|
+
export declare function clearTrail(): void;
|
|
14
|
+
export interface LoopDiagnostics {
|
|
15
|
+
looped: boolean;
|
|
16
|
+
strikes: number;
|
|
17
|
+
severity: "none" | "warning" | "critical";
|
|
18
|
+
}
|
|
19
|
+
export declare function detectLoop(tool: string, ref: string): boolean;
|
|
20
|
+
export declare function getLoopDiagnostics(tool: string, ref: string): LoopDiagnostics;
|
|
21
|
+
export declare function resetLoopCounter(reason?: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Reset loop detection for a single tool+ref pair only.
|
|
24
|
+
* Does not affect other refs or the trail history.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resetLoopCounterForRef(tool: string, ref: string): void;
|
|
27
|
+
export interface ToolCallTracking {
|
|
28
|
+
tool: string;
|
|
29
|
+
target: string;
|
|
30
|
+
timestamp: string;
|
|
31
|
+
result: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Track a tool call for loop detection
|
|
35
|
+
* Maintains a rolling window of the last 5 tool calls
|
|
36
|
+
*/
|
|
37
|
+
export declare function trackToolCall(tool: string, target: string, result: Record<string, unknown>): void;
|
|
38
|
+
/**
|
|
39
|
+
* Check if the agent is stuck in a loop using rolling window detection
|
|
40
|
+
* Returns STUCK signal with diagnosis if loop detected
|
|
41
|
+
*/
|
|
42
|
+
export declare function detectStuckLoop(): {
|
|
43
|
+
stuck: boolean;
|
|
44
|
+
diagnosis?: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Clear the tool call window (useful when starting a new task).
|
|
48
|
+
* Also called by navigate.ts on every navigation to prevent cross-page STUCK_LOOP.
|
|
49
|
+
*/
|
|
50
|
+
export declare function clearToolCallWindow(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Get the current tool call window (for debugging)
|
|
53
|
+
*/
|
|
54
|
+
export declare function getToolCallWindow(): ToolCallTracking[];
|
|
55
|
+
/**
|
|
56
|
+
* Detect cross-tool loops — e.g. click→fill→click→fill repeating with no success.
|
|
57
|
+
* Catches cases where the agent alternates between tools on the same target
|
|
58
|
+
* without making progress, which single-tool detection misses entirely.
|
|
59
|
+
*/
|
|
60
|
+
export declare function detectCrossToolLoop(): {
|
|
61
|
+
stuck: boolean;
|
|
62
|
+
pattern?: string;
|
|
63
|
+
diagnosis?: string;
|
|
64
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { logDebug } from "../audit/logger.js";
|
|
2
|
+
// src/intelligence/trail.ts
|
|
3
|
+
// Lightweight action trail — keeps last 50 actions in memory.
|
|
4
|
+
// Used by tools to record what happened for context and audit.
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import * as crypto from "node:crypto";
|
|
9
|
+
// Generate a unique session ID for this process run
|
|
10
|
+
const SESSION_ID = crypto.randomUUID();
|
|
11
|
+
let trail = [];
|
|
12
|
+
// Per-ref loop reset registry.
|
|
13
|
+
// Maps "tool:ref" or "__global__" → ISO timestamp of the last explicit reset.
|
|
14
|
+
// getLoopDiagnostics / detectLoop only count trail entries recorded AFTER this timestamp.
|
|
15
|
+
// Trail history itself is NEVER wiped by resetLoopCounter — context is preserved.
|
|
16
|
+
const loopResets = new Map();
|
|
17
|
+
const SESSIONS_DIR = path.join(os.homedir(), ".weavetab-system", "sessions");
|
|
18
|
+
const CONTEXT_FILE = path.join(SESSIONS_DIR, "context.json");
|
|
19
|
+
function ensureContextFile() {
|
|
20
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
21
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
if (!fs.existsSync(CONTEXT_FILE)) {
|
|
24
|
+
fs.writeFileSync(CONTEXT_FILE, JSON.stringify([]));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(CONTEXT_FILE)) {
|
|
29
|
+
trail = JSON.parse(fs.readFileSync(CONTEXT_FILE, 'utf-8'));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (e) { }
|
|
33
|
+
export function recordAction(entry) {
|
|
34
|
+
const fullEntry = { ...entry, sessionId: SESSION_ID };
|
|
35
|
+
trail.push(fullEntry);
|
|
36
|
+
// Cap at 20 entries to reduce stale cross-session disk poisoning
|
|
37
|
+
if (trail.length > 20)
|
|
38
|
+
trail.shift();
|
|
39
|
+
try {
|
|
40
|
+
ensureContextFile();
|
|
41
|
+
fs.writeFileSync(CONTEXT_FILE, JSON.stringify(trail, null, 2));
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
logDebug("[Weavetab] Failed to save session context", e);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function getLastAction() {
|
|
48
|
+
return trail.length > 0 ? trail[trail.length - 1] : null;
|
|
49
|
+
}
|
|
50
|
+
export function getTrail() {
|
|
51
|
+
return [...trail];
|
|
52
|
+
}
|
|
53
|
+
export function clearTrail() {
|
|
54
|
+
trail = [];
|
|
55
|
+
try {
|
|
56
|
+
if (fs.existsSync(CONTEXT_FILE)) {
|
|
57
|
+
fs.writeFileSync(CONTEXT_FILE, JSON.stringify([]));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (e) { }
|
|
61
|
+
}
|
|
62
|
+
const LOOP_LIMIT = 10;
|
|
63
|
+
export function detectLoop(tool, ref) {
|
|
64
|
+
const resetAt = loopResets.get(`${tool}:${ref}`) ?? loopResets.get("__global__");
|
|
65
|
+
// Only consider entries after the last reset boundary and from this session
|
|
66
|
+
const relevant = trail.filter(e => e.sessionId === SESSION_ID && (!resetAt || e.timestamp > resetAt));
|
|
67
|
+
if (relevant.length < LOOP_LIMIT)
|
|
68
|
+
return false;
|
|
69
|
+
const lastN = relevant.slice(-LOOP_LIMIT);
|
|
70
|
+
return lastN.every(entry => entry.tool === tool && entry.input.ref === ref &&
|
|
71
|
+
(entry.result?.success === false || entry.result?.stuck === true));
|
|
72
|
+
}
|
|
73
|
+
export function getLoopDiagnostics(tool, ref) {
|
|
74
|
+
// Per-ref reset takes priority over global reset
|
|
75
|
+
const resetAt = loopResets.get(`${tool}:${ref}`) ?? loopResets.get("__global__");
|
|
76
|
+
let strikes = 0;
|
|
77
|
+
for (let i = trail.length - 1; i >= 0; i--) {
|
|
78
|
+
const entry = trail[i];
|
|
79
|
+
// Stop counting if we hit a different session or the reset boundary
|
|
80
|
+
if (entry.sessionId !== SESSION_ID)
|
|
81
|
+
break;
|
|
82
|
+
if (resetAt && entry.timestamp <= resetAt)
|
|
83
|
+
break;
|
|
84
|
+
if (entry.tool === tool && entry.input.ref === ref &&
|
|
85
|
+
(entry.result?.success === false || entry.result?.stuck === true)) {
|
|
86
|
+
strikes++;
|
|
87
|
+
}
|
|
88
|
+
else
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
let severity = "none";
|
|
92
|
+
if (strikes >= LOOP_LIMIT)
|
|
93
|
+
severity = "critical";
|
|
94
|
+
else if (strikes >= Math.ceil(LOOP_LIMIT / 2))
|
|
95
|
+
severity = "warning";
|
|
96
|
+
return { looped: strikes >= LOOP_LIMIT, strikes, severity };
|
|
97
|
+
}
|
|
98
|
+
export function resetLoopCounter(reason = "manual") {
|
|
99
|
+
// Record a global reset timestamp. Loop detection ignores all trail entries
|
|
100
|
+
// recorded before this moment. Trail history is intentionally preserved —
|
|
101
|
+
// burst, delta, and memory all depend on it staying intact.
|
|
102
|
+
loopResets.clear(); // also clears any per-ref markers
|
|
103
|
+
loopResets.set("__global__", new Date().toISOString());
|
|
104
|
+
// Also clear the rolling toolCallWindow — it is session-global and survives
|
|
105
|
+
// navigation, causing false STUCK_LOOP bricks across different pages/tasks.
|
|
106
|
+
toolCallWindow.length = 0;
|
|
107
|
+
logDebug(`[Weavetab] Loop counter reset (reason: ${reason}) — trail history preserved, strike window cleared`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Reset loop detection for a single tool+ref pair only.
|
|
111
|
+
* Does not affect other refs or the trail history.
|
|
112
|
+
*/
|
|
113
|
+
export function resetLoopCounterForRef(tool, ref) {
|
|
114
|
+
loopResets.set(`${tool}:${ref}`, new Date().toISOString());
|
|
115
|
+
logDebug(`[Weavetab] Loop counter reset for ${tool}:${ref}`);
|
|
116
|
+
}
|
|
117
|
+
// Rolling window of last 5 tool calls for loop detection
|
|
118
|
+
const toolCallWindow = [];
|
|
119
|
+
const MAX_WINDOW_SIZE = 5;
|
|
120
|
+
/**
|
|
121
|
+
* Track a tool call for loop detection
|
|
122
|
+
* Maintains a rolling window of the last 5 tool calls
|
|
123
|
+
*/
|
|
124
|
+
export function trackToolCall(tool, target, result) {
|
|
125
|
+
toolCallWindow.push({
|
|
126
|
+
tool,
|
|
127
|
+
target,
|
|
128
|
+
timestamp: new Date().toISOString(),
|
|
129
|
+
result,
|
|
130
|
+
});
|
|
131
|
+
// Keep only the last MAX_WINDOW_SIZE calls
|
|
132
|
+
if (toolCallWindow.length > MAX_WINDOW_SIZE) {
|
|
133
|
+
toolCallWindow.shift();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if the agent is stuck in a loop using rolling window detection
|
|
138
|
+
* Returns STUCK signal with diagnosis if loop detected
|
|
139
|
+
*/
|
|
140
|
+
export function detectStuckLoop() {
|
|
141
|
+
if (toolCallWindow.length < 3) {
|
|
142
|
+
return { stuck: false };
|
|
143
|
+
}
|
|
144
|
+
// Check for 3 consecutive same-tool+target calls without state change
|
|
145
|
+
let consecutiveCount = 0;
|
|
146
|
+
let lastStateChange;
|
|
147
|
+
for (let i = toolCallWindow.length - 1; i >= 0; i--) {
|
|
148
|
+
const call = toolCallWindow[i];
|
|
149
|
+
const currentState = JSON.stringify(call.result);
|
|
150
|
+
// Compare with previous call
|
|
151
|
+
if (i > 0) {
|
|
152
|
+
const prevCall = toolCallWindow[i - 1];
|
|
153
|
+
if (call.tool === prevCall.tool && call.target === prevCall.target) {
|
|
154
|
+
// Check if state changed
|
|
155
|
+
if (currentState === lastStateChange) {
|
|
156
|
+
consecutiveCount++;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
consecutiveCount = 0;
|
|
160
|
+
lastStateChange = currentState;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
consecutiveCount = 0;
|
|
165
|
+
lastStateChange = currentState;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (consecutiveCount >= 2) { // 3 total (current + 2 previous)
|
|
170
|
+
const lastCall = toolCallWindow[toolCallWindow.length - 1];
|
|
171
|
+
return {
|
|
172
|
+
stuck: true,
|
|
173
|
+
diagnosis: `Agent is stuck in a loop: ${consecutiveCount + 1} consecutive calls to ${lastCall.tool} on target "${lastCall.target}" without state change. Consider trying a different approach or resetting the loop counter.`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return { stuck: false };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Clear the tool call window (useful when starting a new task).
|
|
180
|
+
* Also called by navigate.ts on every navigation to prevent cross-page STUCK_LOOP.
|
|
181
|
+
*/
|
|
182
|
+
export function clearToolCallWindow() {
|
|
183
|
+
toolCallWindow.length = 0;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get the current tool call window (for debugging)
|
|
187
|
+
*/
|
|
188
|
+
export function getToolCallWindow() {
|
|
189
|
+
return [...toolCallWindow];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Detect cross-tool loops — e.g. click→fill→click→fill repeating with no success.
|
|
193
|
+
* Catches cases where the agent alternates between tools on the same target
|
|
194
|
+
* without making progress, which single-tool detection misses entirely.
|
|
195
|
+
*/
|
|
196
|
+
export function detectCrossToolLoop() {
|
|
197
|
+
if (toolCallWindow.length < 4)
|
|
198
|
+
return { stuck: false };
|
|
199
|
+
const recent = toolCallWindow.slice(-6); // look at last 6 calls
|
|
200
|
+
const tools = recent.map(c => c.tool);
|
|
201
|
+
const allFailed = recent.every(c => c.result?.success === false || c.result?.stuck === true);
|
|
202
|
+
if (!allFailed)
|
|
203
|
+
return { stuck: false };
|
|
204
|
+
// Check for repeating 2-cycle pattern: A B A B or A B A B A B
|
|
205
|
+
if (recent.length >= 4) {
|
|
206
|
+
const last4Tools = tools.slice(-4);
|
|
207
|
+
if (last4Tools[0] === last4Tools[2] &&
|
|
208
|
+
last4Tools[1] === last4Tools[3] &&
|
|
209
|
+
last4Tools[0] !== last4Tools[1]) {
|
|
210
|
+
const pattern = `${last4Tools[0]} → ${last4Tools[1]}`;
|
|
211
|
+
return {
|
|
212
|
+
stuck: true,
|
|
213
|
+
pattern,
|
|
214
|
+
diagnosis: `Cross-tool loop detected: cycling "${pattern}" (${recent.length} times) without success. Stop this approach entirely. Try: 1) browser_map() to re-read the page, 2) browser_screenshot() to see what's visible, 3) browser_captcha({action:"detect"}) if a CAPTCHA may be blocking.`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Check for repeating 3-cycle: A B C A B C
|
|
219
|
+
if (recent.length >= 6) {
|
|
220
|
+
const last6Tools = tools.slice(-6);
|
|
221
|
+
if (last6Tools[0] === last6Tools[3] &&
|
|
222
|
+
last6Tools[1] === last6Tools[4] &&
|
|
223
|
+
last6Tools[2] === last6Tools[5]) {
|
|
224
|
+
const pattern = `${last6Tools[0]} → ${last6Tools[1]} → ${last6Tools[2]}`;
|
|
225
|
+
return {
|
|
226
|
+
stuck: true,
|
|
227
|
+
pattern,
|
|
228
|
+
diagnosis: `Cross-tool loop detected: cycling "${pattern}" without success. Stop and reassess. Call browser_map() to re-read the page state.`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { stuck: false };
|
|
233
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trail.test.ts — Phase 1.1
|
|
3
|
+
* Unit tests for the Phase 0.1 fix: resetLoopCounter must NOT wipe the trail,
|
|
4
|
+
* loop detection must respect timestamp-based reset boundaries.
|
|
5
|
+
*/
|
|
6
|
+
import { test } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { recordAction, getTrail, clearTrail, getLoopDiagnostics, detectLoop, resetLoopCounter, resetLoopCounterForRef, } from "./trail.js";
|
|
9
|
+
// Helper: record N consecutive failures for a given tool+ref
|
|
10
|
+
function recordFailures(tool, ref, count) {
|
|
11
|
+
for (let i = 0; i < count; i++) {
|
|
12
|
+
recordAction({
|
|
13
|
+
timestamp: new Date().toISOString(),
|
|
14
|
+
tool,
|
|
15
|
+
input: { ref },
|
|
16
|
+
result: { success: false, stuck: true },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Helper: record one success
|
|
21
|
+
function recordSuccess(tool, ref) {
|
|
22
|
+
recordAction({
|
|
23
|
+
timestamp: new Date().toISOString(),
|
|
24
|
+
tool,
|
|
25
|
+
input: { ref },
|
|
26
|
+
result: { success: true, stuck: false },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
test("trail: recordAction appends entries", () => {
|
|
30
|
+
clearTrail();
|
|
31
|
+
recordSuccess("browser_click", "w:1");
|
|
32
|
+
assert.equal(getTrail().length, 1);
|
|
33
|
+
clearTrail();
|
|
34
|
+
});
|
|
35
|
+
test("loop: no warning below threshold (< 5 strikes)", () => {
|
|
36
|
+
clearTrail();
|
|
37
|
+
recordFailures("browser_click", "w:99", 4);
|
|
38
|
+
const diag = getLoopDiagnostics("browser_click", "w:99");
|
|
39
|
+
assert.equal(diag.severity, "none", `Expected none at 4 strikes, got ${diag.severity}`);
|
|
40
|
+
clearTrail();
|
|
41
|
+
});
|
|
42
|
+
test("loop: warning fires at ≥ 5 strikes (half of LOOP_LIMIT=10)", () => {
|
|
43
|
+
clearTrail();
|
|
44
|
+
recordFailures("browser_click", "w:99", 5);
|
|
45
|
+
const diag = getLoopDiagnostics("browser_click", "w:99");
|
|
46
|
+
assert.equal(diag.severity, "warning", `Expected warning at 5 strikes, got ${diag.severity}`);
|
|
47
|
+
clearTrail();
|
|
48
|
+
});
|
|
49
|
+
test("loop: critical fires at ≥ 10 strikes", () => {
|
|
50
|
+
clearTrail();
|
|
51
|
+
recordFailures("browser_click", "w:99", 10);
|
|
52
|
+
const diag = getLoopDiagnostics("browser_click", "w:99");
|
|
53
|
+
assert.equal(diag.severity, "critical", `Expected critical at 10 strikes, got ${diag.severity}`);
|
|
54
|
+
clearTrail();
|
|
55
|
+
});
|
|
56
|
+
test("loop: success resets the consecutive strike count", () => {
|
|
57
|
+
clearTrail();
|
|
58
|
+
recordFailures("browser_click", "w:5", 6);
|
|
59
|
+
recordSuccess("browser_click", "w:5"); // interrupts the consecutive run
|
|
60
|
+
const diag = getLoopDiagnostics("browser_click", "w:5");
|
|
61
|
+
assert.equal(diag.strikes, 0, `Expected 0 strikes after success, got ${diag.strikes}`);
|
|
62
|
+
clearTrail();
|
|
63
|
+
});
|
|
64
|
+
test("Phase 0.1: resetLoopCounter preserves trail history", () => {
|
|
65
|
+
clearTrail();
|
|
66
|
+
recordSuccess("browser_click", "w:1");
|
|
67
|
+
recordSuccess("browser_type", "w:2");
|
|
68
|
+
const countBefore = getTrail().length;
|
|
69
|
+
assert.equal(countBefore, 2);
|
|
70
|
+
resetLoopCounter("test");
|
|
71
|
+
// Trail must still have the same entries — history is preserved
|
|
72
|
+
const countAfter = getTrail().length;
|
|
73
|
+
assert.equal(countAfter, 2, `Trail should still have 2 entries after resetLoopCounter, got ${countAfter}`);
|
|
74
|
+
clearTrail();
|
|
75
|
+
});
|
|
76
|
+
test("Phase 0.1: resetLoopCounter clears strikes without wiping trail", () => {
|
|
77
|
+
clearTrail();
|
|
78
|
+
recordFailures("browser_click", "w:7", 10);
|
|
79
|
+
const before = getLoopDiagnostics("browser_click", "w:7");
|
|
80
|
+
assert.equal(before.severity, "critical");
|
|
81
|
+
resetLoopCounter("test");
|
|
82
|
+
// New failures recorded AFTER reset should count fresh
|
|
83
|
+
recordFailures("browser_click", "w:7", 2);
|
|
84
|
+
const after = getLoopDiagnostics("browser_click", "w:7");
|
|
85
|
+
// Trail still has 12 entries but only 2 are after the reset boundary
|
|
86
|
+
assert.equal(after.strikes, 2, `Expected 2 strikes post-reset, got ${after.strikes}`);
|
|
87
|
+
assert.equal(after.severity, "none", `Expected none at 2 strikes post-reset, got ${after.severity}`);
|
|
88
|
+
clearTrail();
|
|
89
|
+
});
|
|
90
|
+
test("Phase 0.1: resetLoopCounterForRef only affects that ref", () => {
|
|
91
|
+
clearTrail();
|
|
92
|
+
recordFailures("browser_click", "w:A", 10); // critical
|
|
93
|
+
recordFailures("browser_click", "w:B", 10); // critical
|
|
94
|
+
resetLoopCounterForRef("browser_click", "w:A");
|
|
95
|
+
const diagA = getLoopDiagnostics("browser_click", "w:A");
|
|
96
|
+
const diagB = getLoopDiagnostics("browser_click", "w:B");
|
|
97
|
+
assert.equal(diagA.strikes, 0, `w:A should have 0 strikes after per-ref reset, got ${diagA.strikes}`);
|
|
98
|
+
assert.equal(diagB.severity, "critical", `w:B should still be critical, got ${diagB.severity}`);
|
|
99
|
+
clearTrail();
|
|
100
|
+
});
|
|
101
|
+
test("Phase 0.1: detectLoop returns false after resetLoopCounter", () => {
|
|
102
|
+
clearTrail();
|
|
103
|
+
recordFailures("browser_click", "w:X", 10);
|
|
104
|
+
assert.equal(detectLoop("browser_click", "w:X"), true, "Expected loop before reset");
|
|
105
|
+
resetLoopCounter("test");
|
|
106
|
+
recordFailures("browser_click", "w:X", 3); // not enough for loop after reset
|
|
107
|
+
assert.equal(detectLoop("browser_click", "w:X"), false, "Expected no loop after reset with only 3 new strikes");
|
|
108
|
+
clearTrail();
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=trail.test.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — intelligence/vision.ts
|
|
3
|
+
* Local pixel-analysis visual fallback — runs when CDP AX tree returns 0 elements.
|
|
4
|
+
* No external APIs. No ML. Pure image analysis using sharp.
|
|
5
|
+
* Weavetab by fy2ne
|
|
6
|
+
*/
|
|
7
|
+
import CDP from "chrome-remote-interface";
|
|
8
|
+
export interface ClickableZone {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
export interface VisualFallback {
|
|
15
|
+
page_state: "loading" | "blank" | "content" | "error_page";
|
|
16
|
+
text_density: number;
|
|
17
|
+
content_heatmap: string;
|
|
18
|
+
clickable_zones: ClickableZone[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Capture a CDP screenshot, run local pixel analysis, and return
|
|
22
|
+
* structured signal the agent can use when the AX tree is empty.
|
|
23
|
+
*/
|
|
24
|
+
export declare function analyzeVisualFallback(session: CDP.Client): Promise<VisualFallback>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — intelligence/vision.ts
|
|
3
|
+
* Local pixel-analysis visual fallback — runs when CDP AX tree returns 0 elements.
|
|
4
|
+
* No external APIs. No ML. Pure image analysis using sharp.
|
|
5
|
+
* Weavetab by fy2ne
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Capture a CDP screenshot, run local pixel analysis, and return
|
|
9
|
+
* structured signal the agent can use when the AX tree is empty.
|
|
10
|
+
*/
|
|
11
|
+
export async function analyzeVisualFallback(session) {
|
|
12
|
+
// ── 1. Capture screenshot via CDP ─────────────────────────────────────────
|
|
13
|
+
const { data } = await session.Page.captureScreenshot({ format: "png" });
|
|
14
|
+
const buffer = Buffer.from(data, "base64");
|
|
15
|
+
let sharp;
|
|
16
|
+
try {
|
|
17
|
+
sharp = (await import("sharp")).default;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// sharp not available — return a default fallback
|
|
21
|
+
return {
|
|
22
|
+
page_state: "content",
|
|
23
|
+
text_density: 50,
|
|
24
|
+
content_heatmap: "................",
|
|
25
|
+
clickable_zones: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const img = sharp(buffer);
|
|
29
|
+
const { width = 1280, height = 720 } = await img.metadata();
|
|
30
|
+
// ── 2A. Raw pixels for analysis ────────────────────────────────────────────
|
|
31
|
+
const rawBuf = await img
|
|
32
|
+
.raw()
|
|
33
|
+
.toBuffer();
|
|
34
|
+
const pixels = rawBuf; // [R, G, B, R, G, B ...] (no alpha after .raw())
|
|
35
|
+
const totalPixels = width * height;
|
|
36
|
+
function getPixel(x, y) {
|
|
37
|
+
const idx = (y * width + x) * 3;
|
|
38
|
+
return [pixels[idx], pixels[idx + 1], pixels[idx + 2]];
|
|
39
|
+
}
|
|
40
|
+
// ── 2B. Convert to grayscale array ─────────────────────────────────────────
|
|
41
|
+
const gray = new Uint8Array(totalPixels);
|
|
42
|
+
for (let i = 0; i < totalPixels; i++) {
|
|
43
|
+
const r = pixels[i * 3];
|
|
44
|
+
const g = pixels[i * 3 + 1];
|
|
45
|
+
const b = pixels[i * 3 + 2];
|
|
46
|
+
gray[i] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
|
|
47
|
+
}
|
|
48
|
+
// ── 3A. Dominant color regions (4×4 grid) ──────────────────────────────────
|
|
49
|
+
const GRID_COLS = 4;
|
|
50
|
+
const GRID_ROWS = 4;
|
|
51
|
+
const cellW = Math.floor(width / GRID_COLS);
|
|
52
|
+
const cellH = Math.floor(height / GRID_ROWS);
|
|
53
|
+
const cellAvgs = [];
|
|
54
|
+
for (let row = 0; row < GRID_ROWS; row++) {
|
|
55
|
+
for (let col = 0; col < GRID_COLS; col++) {
|
|
56
|
+
let sumR = 0, sumG = 0, sumB = 0, count = 0;
|
|
57
|
+
for (let y = row * cellH; y < (row + 1) * cellH && y < height; y++) {
|
|
58
|
+
for (let x = col * cellW; x < (col + 1) * cellW && x < width; x++) {
|
|
59
|
+
const [r, g, b] = getPixel(x, y);
|
|
60
|
+
sumR += r;
|
|
61
|
+
sumG += g;
|
|
62
|
+
sumB += b;
|
|
63
|
+
count++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
cellAvgs.push({ r: sumR / count, g: sumG / count, b: sumB / count });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Mark cells with significant variation from neighbors
|
|
70
|
+
function colorDist(a, b) {
|
|
71
|
+
return Math.sqrt((a.r - b.r) ** 2 + (a.g - b.g) ** 2 + (a.b - b.b) ** 2);
|
|
72
|
+
}
|
|
73
|
+
const heatmap = cellAvgs.map((cell, idx) => {
|
|
74
|
+
const neighbors = [];
|
|
75
|
+
if (idx % GRID_COLS !== 0)
|
|
76
|
+
neighbors.push(cellAvgs[idx - 1]);
|
|
77
|
+
if (idx % GRID_COLS !== GRID_COLS - 1)
|
|
78
|
+
neighbors.push(cellAvgs[idx + 1]);
|
|
79
|
+
if (idx >= GRID_COLS)
|
|
80
|
+
neighbors.push(cellAvgs[idx - GRID_COLS]);
|
|
81
|
+
if (idx < cellAvgs.length - GRID_COLS)
|
|
82
|
+
neighbors.push(cellAvgs[idx + GRID_COLS]);
|
|
83
|
+
const avgDist = neighbors.reduce((s, n) => s + colorDist(cell, n), 0) / (neighbors.length || 1);
|
|
84
|
+
return avgDist > 30 ? "X" : ".";
|
|
85
|
+
});
|
|
86
|
+
const content_heatmap = heatmap.join("");
|
|
87
|
+
// ── 3B. Text density estimate ───────────────────────────────────────────────
|
|
88
|
+
let textLikePixels = 0;
|
|
89
|
+
for (let i = 0; i < gray.length; i++) {
|
|
90
|
+
const g = gray[i];
|
|
91
|
+
if (g >= 40 && g <= 180)
|
|
92
|
+
textLikePixels++;
|
|
93
|
+
}
|
|
94
|
+
const text_density = Math.round((textLikePixels / totalPixels) * 100);
|
|
95
|
+
// ── 3C. Loading / page state detection ─────────────────────────────────────
|
|
96
|
+
// Check if most pixels are the same brightness (solid color = loading screen)
|
|
97
|
+
let mean = 0;
|
|
98
|
+
for (let i = 0; i < gray.length; i++)
|
|
99
|
+
mean += gray[i];
|
|
100
|
+
mean /= gray.length;
|
|
101
|
+
let variance = 0;
|
|
102
|
+
for (let i = 0; i < gray.length; i++)
|
|
103
|
+
variance += (gray[i] - mean) ** 2;
|
|
104
|
+
variance /= gray.length;
|
|
105
|
+
const stdDev = Math.sqrt(variance);
|
|
106
|
+
let page_state;
|
|
107
|
+
if (stdDev < 10) {
|
|
108
|
+
// Almost solid color
|
|
109
|
+
page_state = mean > 240 ? "blank" : "loading";
|
|
110
|
+
}
|
|
111
|
+
else if (text_density < 5 && heatmap.filter((c) => c === "X").length < 3) {
|
|
112
|
+
page_state = "loading";
|
|
113
|
+
}
|
|
114
|
+
else if (text_density > 15) {
|
|
115
|
+
page_state = "content";
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
page_state = "content";
|
|
119
|
+
}
|
|
120
|
+
// Check for error page heuristic: very bright + low text density
|
|
121
|
+
if (mean > 230 && text_density < 8 && stdDev < 20) {
|
|
122
|
+
page_state = "error_page";
|
|
123
|
+
}
|
|
124
|
+
// ── 3D. Clickable zone detection ───────────────────────────────────────────
|
|
125
|
+
// Scan for rectangular high-contrast bordered regions (buttons have edges).
|
|
126
|
+
// Strategy: find horizontal bands of high contrast as potential button tops/bottoms.
|
|
127
|
+
const clickable_zones = [];
|
|
128
|
+
const ZONE_SAMPLE_STEP = 24;
|
|
129
|
+
for (let y = ZONE_SAMPLE_STEP; y < height - ZONE_SAMPLE_STEP && clickable_zones.length < 5; y += ZONE_SAMPLE_STEP) {
|
|
130
|
+
for (let x = ZONE_SAMPLE_STEP; x < width - ZONE_SAMPLE_STEP; x += ZONE_SAMPLE_STEP) {
|
|
131
|
+
// Sample a 3x3 neighborhood and look for high local contrast
|
|
132
|
+
const center = gray[y * width + x] ?? 128;
|
|
133
|
+
const neighbors = [
|
|
134
|
+
gray[(y - 1) * width + x] ?? center,
|
|
135
|
+
gray[(y + 1) * width + x] ?? center,
|
|
136
|
+
gray[y * width + (x - 1)] ?? center,
|
|
137
|
+
gray[y * width + (x + 1)] ?? center,
|
|
138
|
+
];
|
|
139
|
+
const maxDiff = Math.max(...neighbors.map((n) => Math.abs(n - center)));
|
|
140
|
+
// High contrast border detected — plausible button region
|
|
141
|
+
if (maxDiff > 60) {
|
|
142
|
+
// Check it's not too close to an existing zone
|
|
143
|
+
const tooClose = clickable_zones.some((z) => Math.abs(z.x - x) < 60 && Math.abs(z.y - y) < 40);
|
|
144
|
+
if (!tooClose) {
|
|
145
|
+
clickable_zones.push({ x, y, width: 80, height: 32 });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { page_state, text_density, content_heatmap, clickable_zones };
|
|
151
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — lifecycle/mission_manager.ts
|
|
3
|
+
* Mission Lifecycle Manager (MLM)
|
|
4
|
+
* Handles suspension, hibernation, and resumption of Weavetab missions.
|
|
5
|
+
* Weavetab by fy2ne
|
|
6
|
+
*/
|
|
7
|
+
export interface MissionState {
|
|
8
|
+
missionId: string;
|
|
9
|
+
agentId: string;
|
|
10
|
+
status: "active" | "suspended" | "completed";
|
|
11
|
+
timestamp: number;
|
|
12
|
+
plan: any;
|
|
13
|
+
tabs: any[];
|
|
14
|
+
}
|
|
15
|
+
export declare function suspendMission(agentId: string, missionId: string): Promise<void>;
|
|
16
|
+
export declare function resumeMission(missionId: string): MissionState | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — lifecycle/mission_manager.ts
|
|
3
|
+
* Mission Lifecycle Manager (MLM)
|
|
4
|
+
* Handles suspension, hibernation, and resumption of Weavetab missions.
|
|
5
|
+
* Weavetab by fy2ne
|
|
6
|
+
*/
|
|
7
|
+
import { getActivePlan } from "../tools/plan.js";
|
|
8
|
+
import { getTabList } from "../cdp/connector.js";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { getSystemDir } from "../config/loader.js";
|
|
12
|
+
const stateDir = getSystemDir("missions");
|
|
13
|
+
function getMissionPath(missionId) {
|
|
14
|
+
return path.join(stateDir, `mission_${missionId}.json`);
|
|
15
|
+
}
|
|
16
|
+
export async function suspendMission(agentId, missionId) {
|
|
17
|
+
if (!fs.existsSync(stateDir)) {
|
|
18
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
const plan = getActivePlan();
|
|
21
|
+
const tabs = await getTabList(agentId).catch(() => []);
|
|
22
|
+
const state = {
|
|
23
|
+
missionId,
|
|
24
|
+
agentId,
|
|
25
|
+
status: "suspended",
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
plan,
|
|
28
|
+
tabs
|
|
29
|
+
};
|
|
30
|
+
fs.writeFileSync(getMissionPath(missionId), JSON.stringify(state, null, 2));
|
|
31
|
+
}
|
|
32
|
+
export function resumeMission(missionId) {
|
|
33
|
+
const p = getMissionPath(missionId);
|
|
34
|
+
if (fs.existsSync(p)) {
|
|
35
|
+
try {
|
|
36
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import CDP from "chrome-remote-interface";
|
|
2
|
+
export type ThoughtCategory = "planning" | "acting" | "verifying" | "done" | "error";
|
|
3
|
+
export interface BubbleOptions {
|
|
4
|
+
text: string;
|
|
5
|
+
category?: ThoughtCategory;
|
|
6
|
+
step?: number;
|
|
7
|
+
stepTotal?: number;
|
|
8
|
+
estimatedMs?: number;
|
|
9
|
+
icon?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function showBubble(session: CDP.Client, icon: string, text: string, durationMs?: number, opts?: Partial<BubbleOptions>): Promise<void>;
|
|
12
|
+
export declare function showProgress(session: CDP.Client, text: string, step: number, stepTotal: number, category?: ThoughtCategory, estimatedMs?: number): Promise<void>;
|
|
13
|
+
export declare function hideBubble(session: CDP.Client): Promise<void>;
|
|
14
|
+
export declare function injectBubbleScripts(_session: CDP.Client): Promise<void>;
|
|
15
|
+
export declare function openOverlayTab(_session: CDP.Client): Promise<void>;
|
|
16
|
+
export declare function startBubbleProcess(_humanMode?: boolean): void;
|