@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,36 @@
|
|
|
1
|
+
import CDP from "chrome-remote-interface";
|
|
2
|
+
export type GroupState = "idle" | "weaving" | "stuck";
|
|
3
|
+
interface AgentStateCallbacks {
|
|
4
|
+
onStateChange: (session: CDP.Client | null, state: GroupState, word?: string) => void;
|
|
5
|
+
onUpdateTabTitle: (session: CDP.Client, state: GroupState) => void;
|
|
6
|
+
}
|
|
7
|
+
declare class AgentStateManager {
|
|
8
|
+
private state;
|
|
9
|
+
private word;
|
|
10
|
+
private idleTimeoutMs;
|
|
11
|
+
private idleTimer;
|
|
12
|
+
private callbacks;
|
|
13
|
+
private session;
|
|
14
|
+
private errorStreak;
|
|
15
|
+
constructor(idleTimeoutMs?: number);
|
|
16
|
+
setCallbacks(cb: AgentStateCallbacks): void;
|
|
17
|
+
setSession(session: CDP.Client): void;
|
|
18
|
+
setIdleTimeout(ms: number): void;
|
|
19
|
+
reportToolCall(): void;
|
|
20
|
+
reportToolSuccess(): void;
|
|
21
|
+
reportToolError(): void;
|
|
22
|
+
reportStuck(word: string): void;
|
|
23
|
+
reportDone(): void;
|
|
24
|
+
reportTransportClosed(): void;
|
|
25
|
+
getState(): {
|
|
26
|
+
state: GroupState;
|
|
27
|
+
word: string;
|
|
28
|
+
};
|
|
29
|
+
isStuck(): boolean;
|
|
30
|
+
resetErrorStreak(): void;
|
|
31
|
+
private transition;
|
|
32
|
+
private startIdleTimer;
|
|
33
|
+
private clearIdleTimer;
|
|
34
|
+
}
|
|
35
|
+
export declare const agentState: AgentStateManager;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
class AgentStateManager {
|
|
2
|
+
state = "idle";
|
|
3
|
+
word = "";
|
|
4
|
+
idleTimeoutMs;
|
|
5
|
+
idleTimer = null;
|
|
6
|
+
callbacks = null;
|
|
7
|
+
session = null;
|
|
8
|
+
errorStreak = 0;
|
|
9
|
+
constructor(idleTimeoutMs = 60000) {
|
|
10
|
+
this.idleTimeoutMs = idleTimeoutMs;
|
|
11
|
+
}
|
|
12
|
+
setCallbacks(cb) {
|
|
13
|
+
this.callbacks = cb;
|
|
14
|
+
}
|
|
15
|
+
setSession(session) {
|
|
16
|
+
this.session = session;
|
|
17
|
+
}
|
|
18
|
+
setIdleTimeout(ms) {
|
|
19
|
+
this.idleTimeoutMs = ms;
|
|
20
|
+
}
|
|
21
|
+
reportToolCall() {
|
|
22
|
+
this.clearIdleTimer();
|
|
23
|
+
if (this.state !== "weaving") {
|
|
24
|
+
this.transition("weaving");
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// Always fire onStateChange so activeSessionTabId stays in sync
|
|
28
|
+
this.callbacks?.onStateChange(this.session, this.state, this.word);
|
|
29
|
+
}
|
|
30
|
+
this.startIdleTimer();
|
|
31
|
+
}
|
|
32
|
+
reportToolSuccess() {
|
|
33
|
+
this.errorStreak = 0;
|
|
34
|
+
}
|
|
35
|
+
reportToolError() {
|
|
36
|
+
this.errorStreak++;
|
|
37
|
+
if (this.errorStreak >= 5) {
|
|
38
|
+
this.reportStuck("error_streak");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
reportStuck(word) {
|
|
42
|
+
this.clearIdleTimer();
|
|
43
|
+
this.word = word;
|
|
44
|
+
this.transition("stuck", word);
|
|
45
|
+
}
|
|
46
|
+
reportDone() {
|
|
47
|
+
this.clearIdleTimer();
|
|
48
|
+
this.word = "";
|
|
49
|
+
this.state = "idle";
|
|
50
|
+
// Always broadcast — bypasses guard to ensure favicon/tab cleanup
|
|
51
|
+
this.callbacks?.onStateChange(this.session, "idle");
|
|
52
|
+
if (this.session)
|
|
53
|
+
this.callbacks?.onUpdateTabTitle(this.session, "idle");
|
|
54
|
+
}
|
|
55
|
+
reportTransportClosed() {
|
|
56
|
+
this.clearIdleTimer();
|
|
57
|
+
this.word = "";
|
|
58
|
+
this.state = "idle";
|
|
59
|
+
this.callbacks?.onStateChange(this.session, "idle");
|
|
60
|
+
}
|
|
61
|
+
getState() {
|
|
62
|
+
return { state: this.state, word: this.word };
|
|
63
|
+
}
|
|
64
|
+
isStuck() {
|
|
65
|
+
return this.state === "stuck";
|
|
66
|
+
}
|
|
67
|
+
resetErrorStreak() {
|
|
68
|
+
this.errorStreak = 0;
|
|
69
|
+
}
|
|
70
|
+
transition(newState, word) {
|
|
71
|
+
// Always fire the callback for 'idle' so favicon dots get cleared on every tab
|
|
72
|
+
const isRedundant = this.state === newState && (!word || word === this.word);
|
|
73
|
+
if (isRedundant && newState !== "idle")
|
|
74
|
+
return;
|
|
75
|
+
this.state = newState;
|
|
76
|
+
if (word !== undefined)
|
|
77
|
+
this.word = word;
|
|
78
|
+
this.callbacks?.onStateChange(this.session, this.state, this.word);
|
|
79
|
+
if (this.session) {
|
|
80
|
+
this.callbacks?.onUpdateTabTitle(this.session, this.state);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
startIdleTimer() {
|
|
84
|
+
this.clearIdleTimer();
|
|
85
|
+
this.idleTimer = setTimeout(() => {
|
|
86
|
+
this.word = "";
|
|
87
|
+
this.transition("idle");
|
|
88
|
+
}, this.idleTimeoutMs);
|
|
89
|
+
// unref() so this timer does not pin the Node.js event loop. Without this,
|
|
90
|
+
// the process stays alive indefinitely between tool calls even when the IDE
|
|
91
|
+
// is idle and nothing else is running.
|
|
92
|
+
this.idleTimer.unref();
|
|
93
|
+
}
|
|
94
|
+
clearIdleTimer() {
|
|
95
|
+
if (this.idleTimer) {
|
|
96
|
+
clearTimeout(this.idleTimer);
|
|
97
|
+
this.idleTimer = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export const agentState = new AgentStateManager();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — state/cache.ts
|
|
3
|
+
* Per-tab action map and box model cache with mutation-based TTL invalidation.
|
|
4
|
+
* Weavetab by fy2ne
|
|
5
|
+
*/
|
|
6
|
+
import CDP from "chrome-remote-interface";
|
|
7
|
+
import type { ActionMap } from "../cdp/walker.js";
|
|
8
|
+
export declare function getCachedActionMap(targetId: string, currentUrl?: string): ActionMap | null;
|
|
9
|
+
export declare function getRawCachedActionMap(targetId: string, currentUrl?: string): ActionMap | null;
|
|
10
|
+
export declare function cacheActionMap(targetId: string, map: ActionMap): void;
|
|
11
|
+
export declare function cacheBoxModel(targetId: string, backendNodeId: number, boxModel: any): void;
|
|
12
|
+
export declare function getCachedBoxModel(targetId: string, backendNodeId: number): any | null;
|
|
13
|
+
export declare function cacheNode(targetId: string, backendNodeId: number, node: any): void;
|
|
14
|
+
export declare function getCachedNode(targetId: string, backendNodeId: number): any | null;
|
|
15
|
+
export declare function clearTabCache(targetId: string): void;
|
|
16
|
+
export declare function registerMutationListener(session: CDP.Client, targetId: string): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — state/cache.ts
|
|
3
|
+
* Per-tab action map and box model cache with mutation-based TTL invalidation.
|
|
4
|
+
* Weavetab by fy2ne
|
|
5
|
+
*/
|
|
6
|
+
const tabCache = new Map();
|
|
7
|
+
export function getCachedActionMap(targetId, currentUrl) {
|
|
8
|
+
const state = tabCache.get(targetId);
|
|
9
|
+
if (!state)
|
|
10
|
+
return null;
|
|
11
|
+
// Ref ID Caching Layer: validity TTL based on DOM mutations
|
|
12
|
+
if (state.lastMutationTime > state.lastReadTime) {
|
|
13
|
+
return null; // Mutated since last read, cache invalid
|
|
14
|
+
}
|
|
15
|
+
// URL validation to prevent stale cache across page loads/navigations
|
|
16
|
+
if (currentUrl && state.map && state.map.url !== currentUrl) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return state.map;
|
|
20
|
+
}
|
|
21
|
+
export function getRawCachedActionMap(targetId, currentUrl) {
|
|
22
|
+
const state = tabCache.get(targetId);
|
|
23
|
+
if (!state)
|
|
24
|
+
return null;
|
|
25
|
+
if (currentUrl && state.map && state.map.url !== currentUrl) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return state.map;
|
|
29
|
+
}
|
|
30
|
+
export function cacheActionMap(targetId, map) {
|
|
31
|
+
const state = tabCache.get(targetId) || { lastMutationTime: 0, lastReadTime: 0, map: null, url: null, boxModels: new Map(), domNodes: new Map() };
|
|
32
|
+
state.map = map;
|
|
33
|
+
state.url = map.url;
|
|
34
|
+
state.lastReadTime = Date.now();
|
|
35
|
+
tabCache.set(targetId, state);
|
|
36
|
+
}
|
|
37
|
+
export function cacheBoxModel(targetId, backendNodeId, boxModel) {
|
|
38
|
+
const state = tabCache.get(targetId);
|
|
39
|
+
if (state)
|
|
40
|
+
state.boxModels.set(backendNodeId, boxModel);
|
|
41
|
+
}
|
|
42
|
+
export function getCachedBoxModel(targetId, backendNodeId) {
|
|
43
|
+
const state = tabCache.get(targetId);
|
|
44
|
+
if (!state || state.lastMutationTime > state.lastReadTime)
|
|
45
|
+
return null;
|
|
46
|
+
return state.boxModels.get(backendNodeId) || null;
|
|
47
|
+
}
|
|
48
|
+
export function cacheNode(targetId, backendNodeId, node) {
|
|
49
|
+
const state = tabCache.get(targetId);
|
|
50
|
+
if (state)
|
|
51
|
+
state.domNodes.set(backendNodeId, node);
|
|
52
|
+
}
|
|
53
|
+
export function getCachedNode(targetId, backendNodeId) {
|
|
54
|
+
const state = tabCache.get(targetId);
|
|
55
|
+
if (!state || state.lastMutationTime > state.lastReadTime)
|
|
56
|
+
return null;
|
|
57
|
+
return state.domNodes.get(backendNodeId) || null;
|
|
58
|
+
}
|
|
59
|
+
export function clearTabCache(targetId) {
|
|
60
|
+
tabCache.delete(targetId);
|
|
61
|
+
}
|
|
62
|
+
// Smarter Idle Detection: Event-driven MutationObserver
|
|
63
|
+
// Guard: only register listeners once per CDP session object to prevent
|
|
64
|
+
// accumulation when the same session is passed through multiple init paths.
|
|
65
|
+
const registeredSessions = new WeakSet();
|
|
66
|
+
export function registerMutationListener(session, targetId) {
|
|
67
|
+
// Bail out early if this session already has listeners attached.
|
|
68
|
+
// Without this guard, connectToTab + initSession could both call this function
|
|
69
|
+
// for the same session, stacking duplicate listeners that are never removed.
|
|
70
|
+
if (registeredSessions.has(session))
|
|
71
|
+
return;
|
|
72
|
+
registeredSessions.add(session);
|
|
73
|
+
let debounceTimer;
|
|
74
|
+
const markMutated = () => {
|
|
75
|
+
// Debounce to avoid flooding the cache invalidation
|
|
76
|
+
if (debounceTimer)
|
|
77
|
+
clearTimeout(debounceTimer);
|
|
78
|
+
debounceTimer = setTimeout(() => {
|
|
79
|
+
debounceTimer = undefined;
|
|
80
|
+
const state = tabCache.get(targetId);
|
|
81
|
+
if (state) {
|
|
82
|
+
state.lastMutationTime = Date.now();
|
|
83
|
+
// Clear box models and dom nodes as they are no longer reliable
|
|
84
|
+
state.boxModels.clear();
|
|
85
|
+
state.domNodes.clear();
|
|
86
|
+
}
|
|
87
|
+
}, 50); // 50ms debounced quiet window
|
|
88
|
+
};
|
|
89
|
+
session.DOM.on("childNodeCountUpdated", markMutated);
|
|
90
|
+
session.DOM.on("attributeModified", markMutated);
|
|
91
|
+
session.DOM.on("characterDataModified", markMutated);
|
|
92
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cache.test.ts — Phase 1.1
|
|
3
|
+
* Unit tests for action map and box model caching with mutation-based TTL invalidation.
|
|
4
|
+
*/
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { getCachedActionMap, cacheActionMap, cacheBoxModel, getCachedBoxModel, cacheNode, getCachedNode, clearTabCache, } from "./cache.js";
|
|
8
|
+
test("cache: basic action map storage and retrieval", () => {
|
|
9
|
+
const targetId = "tab-1";
|
|
10
|
+
const mockMap = {
|
|
11
|
+
url: "https://example.com",
|
|
12
|
+
elements: [],
|
|
13
|
+
};
|
|
14
|
+
clearTabCache(targetId);
|
|
15
|
+
assert.equal(getCachedActionMap(targetId), null, "Initial cache miss");
|
|
16
|
+
cacheActionMap(targetId, mockMap);
|
|
17
|
+
const retrieved = getCachedActionMap(targetId);
|
|
18
|
+
assert.ok(retrieved !== null, "Cache hit after storage");
|
|
19
|
+
assert.equal(retrieved?.url, "https://example.com");
|
|
20
|
+
clearTabCache(targetId);
|
|
21
|
+
});
|
|
22
|
+
test("cache: URL validation prevents stale cache", () => {
|
|
23
|
+
const targetId = "tab-2";
|
|
24
|
+
const mockMap = {
|
|
25
|
+
url: "https://example.com",
|
|
26
|
+
elements: [],
|
|
27
|
+
};
|
|
28
|
+
cacheActionMap(targetId, mockMap);
|
|
29
|
+
// Same URL → cache hit
|
|
30
|
+
assert.ok(getCachedActionMap(targetId, "https://example.com") !== null);
|
|
31
|
+
// Different URL → cache miss
|
|
32
|
+
assert.equal(getCachedActionMap(targetId, "https://other.com"), null);
|
|
33
|
+
clearTabCache(targetId);
|
|
34
|
+
});
|
|
35
|
+
test("cache: box model storage and retrieval", () => {
|
|
36
|
+
const targetId = "tab-3";
|
|
37
|
+
const backendNodeId = 12345;
|
|
38
|
+
const mockBoxModel = { model: { content: [0, 0, 100, 100] } };
|
|
39
|
+
// First cache an action map to initialize the state
|
|
40
|
+
cacheActionMap(targetId, { url: "https://example.com", elements: [] });
|
|
41
|
+
cacheBoxModel(targetId, backendNodeId, mockBoxModel);
|
|
42
|
+
const retrieved = getCachedBoxModel(targetId, backendNodeId);
|
|
43
|
+
assert.ok(retrieved !== null, "Box model cached");
|
|
44
|
+
assert.deepEqual(retrieved, mockBoxModel);
|
|
45
|
+
clearTabCache(targetId);
|
|
46
|
+
});
|
|
47
|
+
test("cache: node storage and retrieval", () => {
|
|
48
|
+
const targetId = "tab-4";
|
|
49
|
+
const backendNodeId = 67890;
|
|
50
|
+
const mockNode = { nodeId: 67890, nodeName: "DIV" };
|
|
51
|
+
// First cache an action map to initialize the state
|
|
52
|
+
cacheActionMap(targetId, { url: "https://example.com", elements: [] });
|
|
53
|
+
cacheNode(targetId, backendNodeId, mockNode);
|
|
54
|
+
const retrieved = getCachedNode(targetId, backendNodeId);
|
|
55
|
+
assert.ok(retrieved !== null, "Node cached");
|
|
56
|
+
assert.deepEqual(retrieved, mockNode);
|
|
57
|
+
clearTabCache(targetId);
|
|
58
|
+
});
|
|
59
|
+
test("cache: clearTabCache removes all cached data", () => {
|
|
60
|
+
const targetId = "tab-5";
|
|
61
|
+
const mockMap = { url: "https://example.com", elements: [] };
|
|
62
|
+
const mockBoxModel = { model: { content: [0, 0, 100, 100] } };
|
|
63
|
+
cacheActionMap(targetId, mockMap);
|
|
64
|
+
cacheBoxModel(targetId, 12345, mockBoxModel);
|
|
65
|
+
assert.ok(getCachedActionMap(targetId) !== null);
|
|
66
|
+
assert.ok(getCachedBoxModel(targetId, 12345) !== null);
|
|
67
|
+
clearTabCache(targetId);
|
|
68
|
+
assert.equal(getCachedActionMap(targetId), null);
|
|
69
|
+
assert.equal(getCachedBoxModel(targetId, 12345), null);
|
|
70
|
+
});
|
|
71
|
+
test("cache: missing cache returns null without error", () => {
|
|
72
|
+
assert.equal(getCachedActionMap("nonexistent-tab"), null);
|
|
73
|
+
assert.equal(getCachedBoxModel("nonexistent-tab", 999), null);
|
|
74
|
+
assert.equal(getCachedNode("nonexistent-tab", 999), null);
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=cache.test.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ActionElement as _ActionElement } from "../cdp/walker.js";
|
|
2
|
+
export type ActionElement = _ActionElement;
|
|
3
|
+
export interface RefEntry {
|
|
4
|
+
id: string;
|
|
5
|
+
role: string;
|
|
6
|
+
label: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
value?: string;
|
|
9
|
+
href?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
fingerprint: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SnapshotDiff {
|
|
14
|
+
added: RefEntry[];
|
|
15
|
+
removed: string[];
|
|
16
|
+
changed: RefEntry[];
|
|
17
|
+
stable_count: number;
|
|
18
|
+
_refs_truncated?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PropertyDiff {
|
|
21
|
+
[propName: string]: {
|
|
22
|
+
from: string;
|
|
23
|
+
to: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface RichChangedRef {
|
|
27
|
+
ref: string;
|
|
28
|
+
role: string;
|
|
29
|
+
label: string;
|
|
30
|
+
changes: PropertyDiff;
|
|
31
|
+
actionable?: boolean;
|
|
32
|
+
recommended_action?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface IframeChange {
|
|
35
|
+
frameUrl: string;
|
|
36
|
+
status: "added" | "removed" | "changed";
|
|
37
|
+
elementCount: number;
|
|
38
|
+
}
|
|
39
|
+
export interface RichDelta {
|
|
40
|
+
added: RefEntry[];
|
|
41
|
+
removed: string[];
|
|
42
|
+
changed: RichChangedRef[];
|
|
43
|
+
stable_count: number;
|
|
44
|
+
iframes?: IframeChange[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Store a full snapshot for a tab target.
|
|
48
|
+
*/
|
|
49
|
+
export declare function storeSnapshot(targetId: string, url: string, refs: RefEntry[]): void;
|
|
50
|
+
/**
|
|
51
|
+
* Eagerly initialize the property snapshot cache for a tab.
|
|
52
|
+
*/
|
|
53
|
+
export declare function seedRichSnapshot(targetId: string, url: string, elements: ActionElement[]): void;
|
|
54
|
+
/**
|
|
55
|
+
* Compute basic delta.
|
|
56
|
+
*/
|
|
57
|
+
export declare function computeDelta(targetId: string, url: string, newRefs: RefEntry[]): SnapshotDiff | null;
|
|
58
|
+
/**
|
|
59
|
+
* Rich delta: wraps computeDelta with property-level change details.
|
|
60
|
+
*/
|
|
61
|
+
export declare function computeRichDelta(targetId: string, url: string, newElements: ActionElement[]): Promise<RichDelta | {
|
|
62
|
+
delta_timeout: true;
|
|
63
|
+
} | null>;
|
|
64
|
+
/**
|
|
65
|
+
* Clear snapshot for a tab (called on hard navigation).
|
|
66
|
+
*/
|
|
67
|
+
export declare function clearSnapshot(targetId: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Clear all snapshots (called on CDP disconnect).
|
|
70
|
+
*/
|
|
71
|
+
export declare function clearAllSnapshots(): void;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// src/state/delta.ts
|
|
2
|
+
// Per-tab snapshot diffing for delta reads (Rule 11).
|
|
3
|
+
// weave_read first visit → full snapshot stored here.
|
|
4
|
+
// Subsequent calls → diff against stored snapshot → return only changed refs.
|
|
5
|
+
import { SessionSecretTracker } from "../security/secrets.js";
|
|
6
|
+
const snapshots = new Map();
|
|
7
|
+
const propSnapshots = new Map();
|
|
8
|
+
const frameSnapshots = new Map();
|
|
9
|
+
const INTERACTIVE_ROLES = new Set([
|
|
10
|
+
'button', 'link', 'textbox', 'searchbox', 'combobox',
|
|
11
|
+
'checkbox', 'radio', 'menuitem', 'tab', 'select',
|
|
12
|
+
]);
|
|
13
|
+
function serializeProps(el, targetId) {
|
|
14
|
+
const isSecret = (el.backendNodeId !== undefined && targetId &&
|
|
15
|
+
SessionSecretTracker.isNodeSecret(targetId, el.backendNodeId)) ||
|
|
16
|
+
el.label === '[REDACTED]';
|
|
17
|
+
return {
|
|
18
|
+
label: isSecret ? '[REDACTED]' : (el.label ?? ''),
|
|
19
|
+
value: isSecret ? '[REDACTED]' : (el.value ?? ''),
|
|
20
|
+
disabled: String(!!el.disabled),
|
|
21
|
+
role: el.role,
|
|
22
|
+
href: el.href ?? '',
|
|
23
|
+
placeholder: el.placeholder ?? '',
|
|
24
|
+
textContent: isSecret ? '[REDACTED]' : (el.textContent ?? ''),
|
|
25
|
+
className: (el.className ?? '').split(' ').filter(Boolean).sort().join(' '),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Store a full snapshot for a tab target.
|
|
30
|
+
*/
|
|
31
|
+
export function storeSnapshot(targetId, url, refs) {
|
|
32
|
+
const map = new Map();
|
|
33
|
+
for (const r of refs)
|
|
34
|
+
map.set(r.id, r);
|
|
35
|
+
snapshots.set(targetId, { url, refs: map });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Eagerly initialize the property snapshot cache for a tab.
|
|
39
|
+
*/
|
|
40
|
+
export function seedRichSnapshot(targetId, url, elements) {
|
|
41
|
+
const propMap = new Map();
|
|
42
|
+
const snapMap = new Map();
|
|
43
|
+
const frameMap = new Map();
|
|
44
|
+
for (const el of elements) {
|
|
45
|
+
propMap.set(el.id, serializeProps(el, targetId));
|
|
46
|
+
snapMap.set(el.id, {
|
|
47
|
+
id: el.id,
|
|
48
|
+
role: el.role,
|
|
49
|
+
label: el.label,
|
|
50
|
+
type: el.type,
|
|
51
|
+
href: el.href,
|
|
52
|
+
disabled: el.disabled,
|
|
53
|
+
value: el.value,
|
|
54
|
+
fingerprint: el.fingerprint,
|
|
55
|
+
});
|
|
56
|
+
const fk = el.frameUrl || '__top__';
|
|
57
|
+
frameMap.set(fk, (frameMap.get(fk) || 0) + 1);
|
|
58
|
+
}
|
|
59
|
+
propSnapshots.set(targetId, { url, props: propMap });
|
|
60
|
+
snapshots.set(targetId, { url, refs: snapMap });
|
|
61
|
+
frameSnapshots.set(targetId, frameMap);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Compute basic delta.
|
|
65
|
+
*/
|
|
66
|
+
export function computeDelta(targetId, url, newRefs) {
|
|
67
|
+
const prevWrapper = snapshots.get(targetId);
|
|
68
|
+
if (!prevWrapper || prevWrapper.url !== url)
|
|
69
|
+
return null;
|
|
70
|
+
const prev = prevWrapper.refs;
|
|
71
|
+
const next = new Map();
|
|
72
|
+
for (const r of newRefs)
|
|
73
|
+
next.set(r.id, r);
|
|
74
|
+
// Short-circuit: for maps with 500+ refs, return count-only diff
|
|
75
|
+
// to avoid O(n) comparison hanging on large pages.
|
|
76
|
+
if (prev.size > 500) {
|
|
77
|
+
const addedCount = next.size - prev.size;
|
|
78
|
+
const removedCount = Array.from(prev.keys()).filter(id => !next.has(id)).length;
|
|
79
|
+
snapshots.set(targetId, { url, refs: next });
|
|
80
|
+
return { added: [], removed: [], changed: [], stable_count: Math.min(prev.size, next.size), _refs_truncated: prev.size };
|
|
81
|
+
}
|
|
82
|
+
const added = [];
|
|
83
|
+
const removed = [];
|
|
84
|
+
const changed = [];
|
|
85
|
+
let stable_count = 0;
|
|
86
|
+
for (const [id, ref] of next) {
|
|
87
|
+
const old = prev.get(id);
|
|
88
|
+
if (!old) {
|
|
89
|
+
added.push(ref);
|
|
90
|
+
}
|
|
91
|
+
else if (old.label !== ref.label ||
|
|
92
|
+
old.disabled !== ref.disabled ||
|
|
93
|
+
old.type !== ref.type ||
|
|
94
|
+
(ref.value !== undefined && old.value !== ref.value)) {
|
|
95
|
+
changed.push(ref);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
stable_count++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const id of prev.keys()) {
|
|
102
|
+
if (!next.has(id))
|
|
103
|
+
removed.push(id);
|
|
104
|
+
}
|
|
105
|
+
snapshots.set(targetId, { url, refs: next });
|
|
106
|
+
return { added, removed, changed, stable_count };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Rich delta: wraps computeDelta with property-level change details.
|
|
110
|
+
*/
|
|
111
|
+
export async function computeRichDelta(targetId, url, newElements) {
|
|
112
|
+
// Wrap computation in a 5-second race against a timeout to avoid O(n^2) hangs
|
|
113
|
+
const computePromise = new Promise((resolve) => {
|
|
114
|
+
if (!propSnapshots.has(targetId) || propSnapshots.get(targetId).url !== url) {
|
|
115
|
+
seedRichSnapshot(targetId, url, newElements);
|
|
116
|
+
resolve(null);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const oldPropMap = propSnapshots.get(targetId).props;
|
|
120
|
+
const newRefs = newElements
|
|
121
|
+
.filter(el => !el.selector?.includes('data-wt-internal'))
|
|
122
|
+
.map(el => ({
|
|
123
|
+
id: el.id,
|
|
124
|
+
role: el.role,
|
|
125
|
+
label: el.label,
|
|
126
|
+
type: el.type,
|
|
127
|
+
href: el.href,
|
|
128
|
+
disabled: el.disabled,
|
|
129
|
+
value: el.value,
|
|
130
|
+
fingerprint: el.fingerprint,
|
|
131
|
+
}));
|
|
132
|
+
const basicDiff = computeDelta(targetId, url, newRefs);
|
|
133
|
+
if (!basicDiff) {
|
|
134
|
+
resolve(null);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Compute rich changes with property diffs
|
|
138
|
+
const richChanged = [];
|
|
139
|
+
for (const changedRef of basicDiff.changed) {
|
|
140
|
+
const oldProps = oldPropMap.get(changedRef.id);
|
|
141
|
+
const newEl = newElements.find(e => e.id === changedRef.id);
|
|
142
|
+
if (!oldProps || !newEl) {
|
|
143
|
+
richChanged.push({
|
|
144
|
+
ref: changedRef.id,
|
|
145
|
+
role: changedRef.role,
|
|
146
|
+
label: changedRef.label,
|
|
147
|
+
changes: {},
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const newProps = serializeProps(newEl, targetId);
|
|
152
|
+
const changes = {};
|
|
153
|
+
for (const key of [...new Set([...Object.keys(oldProps), ...Object.keys(newProps)])]) {
|
|
154
|
+
if (oldProps[key] !== newProps[key]) {
|
|
155
|
+
changes[key] = { from: oldProps[key] ?? '', to: newProps[key] ?? '' };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const isActionable = INTERACTIVE_ROLES.has(newEl.role);
|
|
159
|
+
let recommendedAction;
|
|
160
|
+
if (isActionable) {
|
|
161
|
+
recommendedAction = newEl.role === 'link' ? 'navigate' : 'click';
|
|
162
|
+
}
|
|
163
|
+
richChanged.push({
|
|
164
|
+
ref: changedRef.id,
|
|
165
|
+
role: changedRef.role,
|
|
166
|
+
label: changedRef.label,
|
|
167
|
+
changes,
|
|
168
|
+
actionable: isActionable,
|
|
169
|
+
recommended_action: recommendedAction,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// ── Phase 5d: Iframe-aware content tracking ──────────────────────────────
|
|
173
|
+
const newFrameCounts = new Map();
|
|
174
|
+
for (const el of newElements) {
|
|
175
|
+
const fk = el.frameUrl || '__top__';
|
|
176
|
+
newFrameCounts.set(fk, (newFrameCounts.get(fk) || 0) + 1);
|
|
177
|
+
}
|
|
178
|
+
const oldFrames = frameSnapshots.get(targetId) || new Map();
|
|
179
|
+
const iframeChanges = [];
|
|
180
|
+
// Detect added/changed frames
|
|
181
|
+
for (const [fk, count] of newFrameCounts) {
|
|
182
|
+
if (fk === '__top__')
|
|
183
|
+
continue;
|
|
184
|
+
const oldCount = oldFrames.get(fk);
|
|
185
|
+
if (oldCount === undefined) {
|
|
186
|
+
iframeChanges.push({ frameUrl: fk, status: "added", elementCount: count });
|
|
187
|
+
}
|
|
188
|
+
else if (oldCount !== count) {
|
|
189
|
+
iframeChanges.push({ frameUrl: fk, status: "changed", elementCount: count });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Detect removed frames
|
|
193
|
+
for (const [fk] of oldFrames) {
|
|
194
|
+
if (fk === '__top__')
|
|
195
|
+
continue;
|
|
196
|
+
if (!newFrameCounts.has(fk)) {
|
|
197
|
+
iframeChanges.push({ frameUrl: fk, status: "removed", elementCount: 0 });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
frameSnapshots.set(targetId, newFrameCounts);
|
|
201
|
+
// Update property snapshot
|
|
202
|
+
const newPropMap = new Map();
|
|
203
|
+
for (const el of newElements) {
|
|
204
|
+
newPropMap.set(el.id, serializeProps(el, targetId));
|
|
205
|
+
}
|
|
206
|
+
propSnapshots.set(targetId, { url, props: newPropMap });
|
|
207
|
+
resolve({
|
|
208
|
+
added: basicDiff.added,
|
|
209
|
+
removed: basicDiff.removed,
|
|
210
|
+
changed: richChanged,
|
|
211
|
+
stable_count: basicDiff.stable_count,
|
|
212
|
+
...(iframeChanges.length > 0 ? { iframes: iframeChanges } : {}),
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
216
|
+
setTimeout(() => resolve({ delta_timeout: true }), 5000);
|
|
217
|
+
});
|
|
218
|
+
return Promise.race([computePromise, timeoutPromise]);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Clear snapshot for a tab (called on hard navigation).
|
|
222
|
+
*/
|
|
223
|
+
export function clearSnapshot(targetId) {
|
|
224
|
+
snapshots.delete(targetId);
|
|
225
|
+
frameSnapshots.delete(targetId);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Clear all snapshots (called on CDP disconnect).
|
|
229
|
+
*/
|
|
230
|
+
export function clearAllSnapshots() {
|
|
231
|
+
snapshots.clear();
|
|
232
|
+
propSnapshots.clear();
|
|
233
|
+
frameSnapshots.clear();
|
|
234
|
+
}
|