@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,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — tools/navigate.ts
|
|
3
|
+
* URL navigation with domain blocklist enforcement and post-navigation DOM stability wait.
|
|
4
|
+
* Weavetab by fy2ne
|
|
5
|
+
*/
|
|
6
|
+
import { checkDomain } from "../security/blacklist.js";
|
|
7
|
+
import { logAction, logDebug } from "../audit/logger.js";
|
|
8
|
+
import { awaitNavigation } from "../sensors/guard.js";
|
|
9
|
+
import { clearSnapshot } from "../state/delta.js";
|
|
10
|
+
import { resetLoopCounter, clearToolCallWindow } from "../intelligence/trail.js";
|
|
11
|
+
import { resetFallbackIdCounter, clearShadowDomCache, clearAXTreeCache } from "../cdp/walker.js";
|
|
12
|
+
import { clearTabCache } from "../state/cache.js";
|
|
13
|
+
import { bumpVisitCount } from "../intelligence/sessionMemory.js";
|
|
14
|
+
import { sessionRegistry } from "../state/session.js";
|
|
15
|
+
export async function weaveNavigate(session, url, config, targetId = "default", force = false, fast = false) {
|
|
16
|
+
logDebug(`[weaveNavigate] Attempting navigation to: ${url}`);
|
|
17
|
+
let parsed;
|
|
18
|
+
if (url.startsWith('Weavetab://')) {
|
|
19
|
+
const page = url.replace('Weavetab://', '').split('?')[0] || 'dashboard';
|
|
20
|
+
try {
|
|
21
|
+
const { getFromExtension } = await import("../cdp/helpers.js");
|
|
22
|
+
const extUrl = await getFromExtension(session, { type: 'RESOLVE_EXTENSION_URL', page });
|
|
23
|
+
if (extUrl) {
|
|
24
|
+
parsed = new URL(extUrl);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return { success: false, error_code: "EXTENSION_URL_FAILED", message: `Could not resolve Weavetab://${page}` };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { success: false, error_code: "EXTENSION_NOT_AVAILABLE", message: "Extension runtime not available for Weavetab:// navigation" };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
try {
|
|
36
|
+
parsed = new URL(url);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
logDebug(`[weaveNavigate] Invalid URL: ${url}`);
|
|
40
|
+
return { success: false, error_code: "INVALID_URL", message: `[!] Invalid URL: ${url}` };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Allow http(s), file (for local HTML debugging), and chrome-extension (handled below).
|
|
44
|
+
// Block script-execution protocols (javascript:, data:) that bypass the domain policy.
|
|
45
|
+
const proto = parsed.protocol.toLowerCase();
|
|
46
|
+
if (proto !== "http:" && proto !== "https:" && proto !== "file:" && proto !== "chrome-extension:") {
|
|
47
|
+
return { success: false, error_code: "BLOCKED_PROTOCOL", message: `[!] Blocked protocol '${proto}'. Only http, https, and file are allowed.` };
|
|
48
|
+
}
|
|
49
|
+
const domainCheck = checkDomain(parsed.href, config);
|
|
50
|
+
logDebug(`[weaveNavigate] Domain check for ${parsed.hostname}: ${JSON.stringify(domainCheck)}`);
|
|
51
|
+
if (!domainCheck.allowed) {
|
|
52
|
+
return { success: false, error_code: "DOMAIN_BLOCKED", message: `[!] Weave blocked: ${domainCheck.reason}` };
|
|
53
|
+
}
|
|
54
|
+
// Extension pages: open in new tab instead of navigating current tab
|
|
55
|
+
// (chrome-extension:// pages use a different process context — Page.navigate
|
|
56
|
+
// can break the CDP session or not fire loadEventFired properly)
|
|
57
|
+
if (parsed.protocol === 'chrome-extension:') {
|
|
58
|
+
logDebug(`[weaveNavigate] Extension URL detected — opening in new tab: ${parsed.href}`);
|
|
59
|
+
const { sendToExtension } = await import("../cdp/helpers.js");
|
|
60
|
+
await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `opening extension page...` } });
|
|
61
|
+
const { WeavetabOpen } = await import("./tabs.js");
|
|
62
|
+
const openRes = await WeavetabOpen(parsed.href, config, true);
|
|
63
|
+
// Ensure overlay is installed on new tab
|
|
64
|
+
try {
|
|
65
|
+
const { ensureVisualLayer } = await import("../overlay/injector.js");
|
|
66
|
+
const newClient = sessionRegistry.getAll().find(c => c.targetId === openRes.tabId);
|
|
67
|
+
if (newClient) {
|
|
68
|
+
for (let i = 0; i < 10; i++) {
|
|
69
|
+
newClient.overlayInstalled = false;
|
|
70
|
+
await ensureVisualLayer(newClient.session, config, newClient);
|
|
71
|
+
const status = await newClient.session.Runtime.evaluate({
|
|
72
|
+
expression: `!!document.getElementById('wt-chat-icon')`,
|
|
73
|
+
returnByValue: true
|
|
74
|
+
}).catch(() => ({ result: { value: false } }));
|
|
75
|
+
if (status?.result?.value === true)
|
|
76
|
+
break;
|
|
77
|
+
await new Promise(r => setTimeout(r, 500));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch { /* overlay install is best-effort */ }
|
|
82
|
+
logAction("browser_go", url, `opened extension tab ${openRes.tabId}`);
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
url: openRes.url,
|
|
86
|
+
title: 'Weavetab Dashboard',
|
|
87
|
+
tab_id: openRes.tabId
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Resolve active tab id
|
|
91
|
+
const active = sessionRegistry.getActive(config.agentId);
|
|
92
|
+
const resolvedTargetId = active?.targetId ?? targetId;
|
|
93
|
+
// 1. Anchor Tab Protection
|
|
94
|
+
let isAnchor = false;
|
|
95
|
+
try {
|
|
96
|
+
const res = await session.Runtime.evaluate({
|
|
97
|
+
expression: `new Promise((resolve) => {
|
|
98
|
+
let done = false;
|
|
99
|
+
const finish = (val) => { if (!done) { done = true; resolve(val); } };
|
|
100
|
+
setTimeout(() => finish(false), 500);
|
|
101
|
+
try {
|
|
102
|
+
const browserAPI = typeof browser !== 'undefined' ? browser : (typeof chrome !== 'undefined' ? chrome : null);
|
|
103
|
+
if (browserAPI && browserAPI.runtime && browserAPI.runtime.id) {
|
|
104
|
+
browserAPI.runtime.sendMessage({ type: 'GET_ANCHOR_STATUS' }, (response) => {
|
|
105
|
+
finish(response?.isAnchor || false);
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
finish(false);
|
|
109
|
+
}
|
|
110
|
+
} catch(e) { finish(false); }
|
|
111
|
+
})`,
|
|
112
|
+
awaitPromise: true,
|
|
113
|
+
returnByValue: true
|
|
114
|
+
});
|
|
115
|
+
isAnchor = res?.result?.value === true;
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
logDebug(`[weaveNavigate] Failed to check anchor status: ${e}`);
|
|
119
|
+
}
|
|
120
|
+
if (isAnchor && !force) {
|
|
121
|
+
// Open in new tab instead of clobbering anchor
|
|
122
|
+
const { WeavetabOpen } = await import("./tabs.js");
|
|
123
|
+
const openRes = await WeavetabOpen(parsed.href, config);
|
|
124
|
+
// Map WeavetabOpen result to NavigateResult
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
url: openRes.url,
|
|
128
|
+
title: "New Tab",
|
|
129
|
+
tab_id: openRes.tabId
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const { sendToExtension } = await import("../cdp/helpers.js");
|
|
133
|
+
sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `navigating to ${parsed.hostname}...` } }).catch(() => { });
|
|
134
|
+
sendToExtension(session, { type: 'Weavetab_STATE', cursor: { x: 10, y: 10 }, state: 'thinking' }).catch(() => { });
|
|
135
|
+
await session.Page.navigate({ url: parsed.href });
|
|
136
|
+
// Proper navigation wait — DOM stability after load
|
|
137
|
+
try {
|
|
138
|
+
await awaitNavigation(session, config.navigationTimeout || 30000);
|
|
139
|
+
}
|
|
140
|
+
catch (navErr) {
|
|
141
|
+
logDebug(`[weaveNavigate] Navigation timeout: ${navErr}`);
|
|
142
|
+
// Continue — page may be partially loaded
|
|
143
|
+
}
|
|
144
|
+
// Clear delta snapshot — next weave_read will be a fresh full read
|
|
145
|
+
clearSnapshot(resolvedTargetId);
|
|
146
|
+
resetLoopCounter("navigation");
|
|
147
|
+
clearToolCallWindow(); // prevents cross-page STUCK_LOOP bricks
|
|
148
|
+
resetFallbackIdCounter();
|
|
149
|
+
clearShadowDomCache(resolvedTargetId);
|
|
150
|
+
clearAXTreeCache(resolvedTargetId);
|
|
151
|
+
clearTabCache(resolvedTargetId);
|
|
152
|
+
// Dismiss common cookie banners / popups inline (popups.ts was removed)
|
|
153
|
+
await session.Runtime.evaluate({
|
|
154
|
+
expression: `
|
|
155
|
+
(function(){
|
|
156
|
+
var selectors = [
|
|
157
|
+
'[id*="cookie"] button','[class*="cookie"] button',
|
|
158
|
+
'[id*="consent"] button','[class*="consent"] button',
|
|
159
|
+
'[id*="gdpr"] button','[class*="gdpr"] button',
|
|
160
|
+
'[aria-label*="Accept"]','[aria-label*="Agree"]',
|
|
161
|
+
'button[data-cookiebanner]'
|
|
162
|
+
];
|
|
163
|
+
for(var i=0;i<selectors.length;i++){
|
|
164
|
+
var el=document.querySelector(selectors[i]);
|
|
165
|
+
if(el){try{el.click();}catch(e){}break;}
|
|
166
|
+
}
|
|
167
|
+
})()
|
|
168
|
+
`
|
|
169
|
+
}).catch(() => { });
|
|
170
|
+
const { frameTree } = await session.Page.getFrameTree();
|
|
171
|
+
const finalUrl = frameTree.frame.url;
|
|
172
|
+
let title = parsed.hostname;
|
|
173
|
+
try {
|
|
174
|
+
const titleRes = await session.Runtime.evaluate({ expression: 'document.title', returnByValue: true });
|
|
175
|
+
if (titleRes?.result?.value) {
|
|
176
|
+
title = titleRes.result.value;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch { }
|
|
180
|
+
// Keep session registry in sync with the new URL
|
|
181
|
+
if (active)
|
|
182
|
+
active.url = finalUrl;
|
|
183
|
+
sendToExtension(session, { type: 'Weavetab_STATE', cursor: { x: 10, y: 10 }, state: 'idle', hud: { action: '' } }).catch(() => { });
|
|
184
|
+
logAction("browser_go", url, `arrived at ${title}`);
|
|
185
|
+
// Bump the visit counter for this domain exactly once per navigation
|
|
186
|
+
bumpVisitCount(parsed.hostname).catch(() => { });
|
|
187
|
+
return { success: true, url: finalUrl, title, tab_id: resolvedTargetId };
|
|
188
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — tools/network_intercept.ts
|
|
3
|
+
* browser_network_intercept: Intercept/stub network responses AND retrieve
|
|
4
|
+
* deep network telemetry (TLS/SSL Certificate metadata, microsecond timing breakdown,
|
|
5
|
+
* and precise OS-level network failure reasons).
|
|
6
|
+
* Weavetab by fy2ne
|
|
7
|
+
*/
|
|
8
|
+
import CDP from "chrome-remote-interface";
|
|
9
|
+
import type { Config } from "../config/loader.js";
|
|
10
|
+
export interface InterceptOptions {
|
|
11
|
+
action: "enable" | "disable" | "get_telemetry" | "clear_telemetry";
|
|
12
|
+
pattern?: string;
|
|
13
|
+
mockStatus?: number;
|
|
14
|
+
mockBody?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function cleanupNetworkIntercept(session: CDP.Client): void;
|
|
17
|
+
export declare function browserNetworkIntercept(session: CDP.Client, options: InterceptOptions, _config: Config): Promise<any>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { TelemetryCapture } from "../sensors/network.js";
|
|
2
|
+
// Track handler refs so we can remove them on disable — prevents
|
|
3
|
+
// handler accumulation that causes double-fulfill CDP crashes.
|
|
4
|
+
const activeHandlers = new Map();
|
|
5
|
+
const activeInterceptors = new Map();
|
|
6
|
+
const activeTelemetry = new Map();
|
|
7
|
+
export function cleanupNetworkIntercept(session) {
|
|
8
|
+
activeHandlers.delete(session);
|
|
9
|
+
activeInterceptors.delete(session);
|
|
10
|
+
const tc = activeTelemetry.get(session);
|
|
11
|
+
if (tc) {
|
|
12
|
+
tc.stop().catch(() => { });
|
|
13
|
+
activeTelemetry.delete(session);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function getOrCreateTelemetry(session) {
|
|
17
|
+
let tc = activeTelemetry.get(session);
|
|
18
|
+
if (!tc) {
|
|
19
|
+
tc = new TelemetryCapture(session);
|
|
20
|
+
await tc.start();
|
|
21
|
+
activeTelemetry.set(session, tc);
|
|
22
|
+
}
|
|
23
|
+
return tc;
|
|
24
|
+
}
|
|
25
|
+
export async function browserNetworkIntercept(session, options, _config) {
|
|
26
|
+
// ── Action: get_telemetry ──────────────────────────────────────────────
|
|
27
|
+
if (options.action === "get_telemetry") {
|
|
28
|
+
const tc = activeTelemetry.get(session);
|
|
29
|
+
if (!tc) {
|
|
30
|
+
await getOrCreateTelemetry(session);
|
|
31
|
+
return {
|
|
32
|
+
success: true,
|
|
33
|
+
message: "Network telemetry capture started. No network requests recorded yet. Make network requests and call get_telemetry again.",
|
|
34
|
+
count: 0,
|
|
35
|
+
requests: []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const report = await tc.stop();
|
|
39
|
+
// Restart capture for continuous monitoring
|
|
40
|
+
await tc.start();
|
|
41
|
+
let requests = report.networkRequests;
|
|
42
|
+
if (options.pattern) {
|
|
43
|
+
const regexPattern = options.pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
44
|
+
const reg = new RegExp(regexPattern);
|
|
45
|
+
requests = requests.filter(r => reg.test(r.url));
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
message: `Retrieved ${requests.length} captured network request(s) with raw protocol metadata.`,
|
|
50
|
+
count: requests.length,
|
|
51
|
+
requests,
|
|
52
|
+
navigationOccurred: report.navigationOccurred,
|
|
53
|
+
newUrl: report.newUrl,
|
|
54
|
+
errors: report.errors
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// ── Action: clear_telemetry ────────────────────────────────────────────
|
|
58
|
+
if (options.action === "clear_telemetry") {
|
|
59
|
+
const tc = activeTelemetry.get(session);
|
|
60
|
+
if (tc) {
|
|
61
|
+
await tc.start();
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await getOrCreateTelemetry(session);
|
|
65
|
+
}
|
|
66
|
+
return { success: true, message: "Network telemetry buffer cleared and capture reset." };
|
|
67
|
+
}
|
|
68
|
+
// Auto-start telemetry background listener when enable/disable is called
|
|
69
|
+
await getOrCreateTelemetry(session).catch(() => { });
|
|
70
|
+
const { sendToExtension } = await import("../cdp/helpers.js");
|
|
71
|
+
const hasExt = await session.Runtime.evaluate({
|
|
72
|
+
expression: `!!window.__Weavetab`,
|
|
73
|
+
returnByValue: true
|
|
74
|
+
}).catch(() => null);
|
|
75
|
+
if (hasExt?.result?.value) {
|
|
76
|
+
// ── Extension path (preferred) ────────────────────────────────────────
|
|
77
|
+
if (options.action === "enable") {
|
|
78
|
+
if (!options.pattern)
|
|
79
|
+
throw new Error("enable requires 'pattern'");
|
|
80
|
+
await sendToExtension(session, {
|
|
81
|
+
type: 'NETWORK_INTERCEPT_ENABLE',
|
|
82
|
+
pattern: options.pattern,
|
|
83
|
+
mockStatus: options.mockStatus || 200,
|
|
84
|
+
mockBody: options.mockBody || '{}'
|
|
85
|
+
});
|
|
86
|
+
activeInterceptors.set(session, { pattern: options.pattern, mockStatus: options.mockStatus || 200, mockBody: options.mockBody || '{}' });
|
|
87
|
+
return { success: true, message: `Extension interception enabled for ${options.pattern}` };
|
|
88
|
+
}
|
|
89
|
+
if (options.action === "disable") {
|
|
90
|
+
await sendToExtension(session, { type: 'NETWORK_INTERCEPT_DISABLE' });
|
|
91
|
+
activeInterceptors.delete(session);
|
|
92
|
+
return { success: true, message: "Extension interception disabled" };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// ── CDP Fetch domain fallback ───────────────────────────────────────────
|
|
96
|
+
if (options.action === "enable") {
|
|
97
|
+
if (!options.pattern)
|
|
98
|
+
throw new Error("enable requires 'pattern'");
|
|
99
|
+
const mockStatus = options.mockStatus || 200;
|
|
100
|
+
const mockBody = options.mockBody || '{}';
|
|
101
|
+
const regexPattern = options.pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
102
|
+
// Remove stale handler from previous enable() to prevent accumulation
|
|
103
|
+
const oldHandler = activeHandlers.get(session);
|
|
104
|
+
if (oldHandler) {
|
|
105
|
+
try {
|
|
106
|
+
session.removeListener('Fetch.requestPaused', oldHandler);
|
|
107
|
+
}
|
|
108
|
+
catch { }
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
await session.Fetch.enable({ patterns: [{ urlPattern: options.pattern, requestStage: 'Response' }] });
|
|
112
|
+
const handler = async (params) => {
|
|
113
|
+
if (params.request?.url?.match(new RegExp(regexPattern))) {
|
|
114
|
+
await session.Fetch.fulfillRequest({
|
|
115
|
+
requestId: params.requestId,
|
|
116
|
+
responseCode: mockStatus,
|
|
117
|
+
responseHeaders: [{ name: 'Content-Type', value: 'application/json' }],
|
|
118
|
+
body: Buffer.from(mockBody).toString('base64'),
|
|
119
|
+
}).catch(() => { });
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
await session.Fetch.continueRequest({ requestId: params.requestId }).catch(() => { });
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
session.on('Fetch.requestPaused', handler);
|
|
126
|
+
activeHandlers.set(session, handler);
|
|
127
|
+
activeInterceptors.set(session, { pattern: options.pattern, mockStatus, mockBody });
|
|
128
|
+
return { success: true, message: `CDP Fetch interception enabled for ${options.pattern} (fallback mode)` };
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
throw new Error(`Failed to enable CDP Fetch interception: ${e.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (options.action === "disable") {
|
|
135
|
+
try {
|
|
136
|
+
const existingHandler = activeHandlers.get(session);
|
|
137
|
+
if (existingHandler) {
|
|
138
|
+
try {
|
|
139
|
+
session.removeListener('Fetch.requestPaused', existingHandler);
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
activeHandlers.delete(session);
|
|
143
|
+
}
|
|
144
|
+
await session.Fetch.disable().catch(() => { });
|
|
145
|
+
}
|
|
146
|
+
catch { /* ignore */ }
|
|
147
|
+
activeInterceptors.delete(session);
|
|
148
|
+
return { success: true, message: "CDP Fetch interception disabled" };
|
|
149
|
+
}
|
|
150
|
+
throw new Error(`Unknown action: ${options.action}`);
|
|
151
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — tools/office/index.ts
|
|
3
|
+
* Re-exports office document tools for agent consumption.
|
|
4
|
+
* Weavetab by fy2ne
|
|
5
|
+
*/
|
|
6
|
+
export { browserOfficeParse } from "./parse.js";
|
|
7
|
+
export type { OfficeParseOptions, OfficeParseResult } from "./parse.js";
|
|
8
|
+
export { browserOfficeList } from "./list.js";
|
|
9
|
+
export type { OfficeListResult } from "./list.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import CDP from "chrome-remote-interface";
|
|
2
|
+
import type { Config } from "../../config/loader.js";
|
|
3
|
+
export interface OfficeListOptions {
|
|
4
|
+
}
|
|
5
|
+
export interface OfficeListResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
files: {
|
|
8
|
+
filename: string;
|
|
9
|
+
size: number;
|
|
10
|
+
created_at: string;
|
|
11
|
+
extension: string;
|
|
12
|
+
}[];
|
|
13
|
+
directory: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function browserOfficeList(session: CDP.Client, options: OfficeListOptions, config: Config): Promise<OfficeListResult>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getOutputDir } from "../../config/loader.js";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { logAction } from "../../audit/logger.js";
|
|
5
|
+
export async function browserOfficeList(session, options, config) {
|
|
6
|
+
const downloadDir = getOutputDir("downloads", config);
|
|
7
|
+
if (!fs.existsSync(downloadDir)) {
|
|
8
|
+
return {
|
|
9
|
+
success: true,
|
|
10
|
+
files: [],
|
|
11
|
+
directory: downloadDir,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const files = fs.readdirSync(downloadDir);
|
|
15
|
+
const resultFiles = [];
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
const fullPath = path.join(downloadDir, file);
|
|
18
|
+
try {
|
|
19
|
+
const stats = fs.statSync(fullPath);
|
|
20
|
+
if (stats.isFile()) {
|
|
21
|
+
resultFiles.push({
|
|
22
|
+
filename: file,
|
|
23
|
+
size: stats.size,
|
|
24
|
+
created_at: stats.birthtime.toISOString(),
|
|
25
|
+
extension: path.extname(file).toLowerCase(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Ignore files that can't be statted
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Sort by newest first
|
|
34
|
+
resultFiles.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
35
|
+
logAction("browser_office_list", "read", `Listed ${resultFiles.length} files in downloads`);
|
|
36
|
+
return {
|
|
37
|
+
success: true,
|
|
38
|
+
files: resultFiles,
|
|
39
|
+
directory: downloadDir,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import CDP from "chrome-remote-interface";
|
|
2
|
+
import type { Config } from "../../config/loader.js";
|
|
3
|
+
export interface OfficeParseOptions {
|
|
4
|
+
/** URL to a remote document (PDF, XLSX, DOCX, CSV). Fetched in-browser using the user's active cookies. */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Exact filename from the local downloads directory (legacy mode). */
|
|
7
|
+
filename?: string;
|
|
8
|
+
max_rows?: number;
|
|
9
|
+
max_chars?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface OfficeParseResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
source: string;
|
|
14
|
+
type: string;
|
|
15
|
+
content?: string;
|
|
16
|
+
data?: any;
|
|
17
|
+
error?: string;
|
|
18
|
+
truncated?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function browserOfficeParse(session: CDP.Client, options: OfficeParseOptions, config: Config): Promise<OfficeParseResult>;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { getOutputDir } from "../../config/loader.js";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { logAction, logDebug } from "../../audit/logger.js";
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import { parse as parseCsv } from "csv-parse/sync";
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import pdfParse from "pdf-parse";
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
import mammoth from "mammoth";
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
import * as xlsx from "xlsx";
|
|
13
|
+
/** Fetch a URL inside the browser's context using fetch(), return base64-encoded bytes + MIME type. */
|
|
14
|
+
async function cdpFetchAsBase64(session, url) {
|
|
15
|
+
const expression = `(async () => {
|
|
16
|
+
try {
|
|
17
|
+
const resp = await fetch(${JSON.stringify(url)}, { credentials: 'include' });
|
|
18
|
+
if (!resp.ok) return JSON.stringify({ error: 'HTTP ' + resp.status + ' ' + resp.statusText });
|
|
19
|
+
const mime = resp.headers.get('content-type') || 'application/octet-stream';
|
|
20
|
+
const buf = await resp.arrayBuffer();
|
|
21
|
+
const bytes = new Uint8Array(buf);
|
|
22
|
+
let binary = '';
|
|
23
|
+
for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
|
|
24
|
+
const b64 = btoa(binary);
|
|
25
|
+
return JSON.stringify({ base64: b64, mimeType: mime });
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return JSON.stringify({ error: e.message || String(e) });
|
|
28
|
+
}
|
|
29
|
+
})()`;
|
|
30
|
+
try {
|
|
31
|
+
const { result } = await session.Runtime.evaluate({
|
|
32
|
+
expression,
|
|
33
|
+
awaitPromise: true,
|
|
34
|
+
returnByValue: true,
|
|
35
|
+
});
|
|
36
|
+
const raw = result?.value;
|
|
37
|
+
if (!raw)
|
|
38
|
+
return { error: "No response from in-browser fetch" };
|
|
39
|
+
return JSON.parse(raw);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return { error: `CDP evaluate failed: ${e.message}` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Infer file extension from URL path or MIME type. */
|
|
46
|
+
function inferExtension(url, mimeType) {
|
|
47
|
+
// Try URL path first
|
|
48
|
+
try {
|
|
49
|
+
const pathname = new URL(url).pathname;
|
|
50
|
+
const ext = path.extname(pathname).toLowerCase();
|
|
51
|
+
if ([".pdf", ".xlsx", ".xls", ".docx", ".csv"].includes(ext))
|
|
52
|
+
return ext;
|
|
53
|
+
}
|
|
54
|
+
catch { /* ignore */ }
|
|
55
|
+
// Fall back to MIME type
|
|
56
|
+
if (mimeType.includes("pdf"))
|
|
57
|
+
return ".pdf";
|
|
58
|
+
if (mimeType.includes("spreadsheetml") || mimeType.includes("excel"))
|
|
59
|
+
return ".xlsx";
|
|
60
|
+
if (mimeType.includes("wordprocessingml") || mimeType.includes("msword"))
|
|
61
|
+
return ".docx";
|
|
62
|
+
if (mimeType.includes("csv") || mimeType.includes("text/plain"))
|
|
63
|
+
return ".csv";
|
|
64
|
+
return ".bin";
|
|
65
|
+
}
|
|
66
|
+
async function parseBuffer(buffer, ext, source, max_rows, max_chars) {
|
|
67
|
+
try {
|
|
68
|
+
if (ext === ".csv") {
|
|
69
|
+
const fileContent = buffer.toString("utf-8");
|
|
70
|
+
const records = parseCsv(fileContent, { columns: true, skip_empty_lines: true });
|
|
71
|
+
const truncated = records.length > max_rows;
|
|
72
|
+
const data = truncated ? records.slice(0, max_rows) : records;
|
|
73
|
+
return { success: true, source, type: "csv", data, truncated };
|
|
74
|
+
}
|
|
75
|
+
if (ext === ".pdf") {
|
|
76
|
+
const data = await pdfParse(buffer);
|
|
77
|
+
let text = data.text;
|
|
78
|
+
let truncated = false;
|
|
79
|
+
if (text.length > max_chars) {
|
|
80
|
+
text = text.substring(0, max_chars) + "\n...[TRUNCATED]...";
|
|
81
|
+
truncated = true;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
source,
|
|
86
|
+
type: "pdf",
|
|
87
|
+
content: text.trim(),
|
|
88
|
+
truncated,
|
|
89
|
+
data: { numpages: data.numpages, info: data.info },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (ext === ".docx") {
|
|
93
|
+
const result = await mammoth.extractRawText({ buffer });
|
|
94
|
+
let text = result.value;
|
|
95
|
+
let truncated = false;
|
|
96
|
+
if (text.length > max_chars) {
|
|
97
|
+
text = text.substring(0, max_chars) + "\n...[TRUNCATED]...";
|
|
98
|
+
truncated = true;
|
|
99
|
+
}
|
|
100
|
+
const warnings = result.messages.map((m) => m.message);
|
|
101
|
+
if (warnings.length > 0)
|
|
102
|
+
logDebug(`[Office] Mammoth warnings: ${warnings.join(", ")}`);
|
|
103
|
+
return { success: true, source, type: "docx", content: text.trim(), truncated };
|
|
104
|
+
}
|
|
105
|
+
if (ext === ".xlsx" || ext === ".xls") {
|
|
106
|
+
const workbook = xlsx.read(buffer, { type: "buffer" });
|
|
107
|
+
const resultData = {};
|
|
108
|
+
let truncated = false;
|
|
109
|
+
for (const sheetName of workbook.SheetNames) {
|
|
110
|
+
const sheet = workbook.Sheets[sheetName];
|
|
111
|
+
const records = xlsx.utils.sheet_to_json(sheet);
|
|
112
|
+
if (records.length > max_rows) {
|
|
113
|
+
resultData[sheetName] = records.slice(0, max_rows);
|
|
114
|
+
truncated = true;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
resultData[sheetName] = records;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { success: true, source, type: "excel", data: resultData, truncated };
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
source,
|
|
125
|
+
type: ext,
|
|
126
|
+
error: `Unsupported file type: ${ext}. Supported: .csv, .pdf, .docx, .xlsx, .xls`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return { success: false, source, type: ext, error: `Failed to parse: ${error.message}` };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export async function browserOfficeParse(session, options, config) {
|
|
134
|
+
const { url, filename, max_rows = 500, max_chars = 50000 } = options;
|
|
135
|
+
if (!url && !filename) {
|
|
136
|
+
return { success: false, source: "", type: "unknown", error: "Provide either 'url' (remote document) or 'filename' (local download)." };
|
|
137
|
+
}
|
|
138
|
+
// ── URL mode: fetch in-browser via CDP Runtime.evaluate ───────────────────
|
|
139
|
+
if (url) {
|
|
140
|
+
logAction("browser_office_parse", "url", `Fetching ${url}`);
|
|
141
|
+
const fetchResult = await cdpFetchAsBase64(session, url);
|
|
142
|
+
if ("error" in fetchResult) {
|
|
143
|
+
return { success: false, source: url, type: "unknown", error: `Fetch failed: ${fetchResult.error}` };
|
|
144
|
+
}
|
|
145
|
+
const { base64, mimeType } = fetchResult;
|
|
146
|
+
const ext = inferExtension(url, mimeType);
|
|
147
|
+
const buffer = Buffer.from(base64, "base64");
|
|
148
|
+
logDebug(`[Office] URL fetch: ${url} → ${ext} (${buffer.length} bytes, mime=${mimeType})`);
|
|
149
|
+
logAction("browser_office_parse", ext, `Parsing ${url} (${buffer.length} bytes)`);
|
|
150
|
+
return parseBuffer(buffer, ext, url, max_rows, max_chars);
|
|
151
|
+
}
|
|
152
|
+
// ── Filename mode: legacy local file parsing ───────────────────────────────
|
|
153
|
+
if (filename) {
|
|
154
|
+
// Security: prevent path traversal
|
|
155
|
+
if (filename.includes("..") ||
|
|
156
|
+
filename.includes("/") ||
|
|
157
|
+
filename.includes("\\") ||
|
|
158
|
+
path.basename(filename) !== filename) {
|
|
159
|
+
return { success: false, source: filename, type: "unknown", error: "Invalid filename — must be a bare filename, not a path" };
|
|
160
|
+
}
|
|
161
|
+
const downloadDir = getOutputDir("downloads", config);
|
|
162
|
+
const filePath = path.join(downloadDir, filename);
|
|
163
|
+
if (!fs.existsSync(filePath)) {
|
|
164
|
+
return { success: false, source: filename, type: "unknown", error: `File not found: ${filename}` };
|
|
165
|
+
}
|
|
166
|
+
const ext = path.extname(filename).toLowerCase();
|
|
167
|
+
logAction("browser_office_parse", ext, `Parsing local file ${filename}`);
|
|
168
|
+
const buffer = fs.readFileSync(filePath);
|
|
169
|
+
return parseBuffer(buffer, ext, filename, max_rows, max_chars);
|
|
170
|
+
}
|
|
171
|
+
return { success: false, source: "", type: "unknown", error: "Unreachable" };
|
|
172
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — tools/pdf.ts
|
|
3
|
+
* browser_pdf: Generate a PDF of the current page.
|
|
4
|
+
* Weavetab by fy2ne
|
|
5
|
+
*/
|
|
6
|
+
import CDP from "chrome-remote-interface";
|
|
7
|
+
import type { Config } from "../config/loader.js";
|
|
8
|
+
export interface PdfOptions {
|
|
9
|
+
landscape?: boolean;
|
|
10
|
+
scale?: number;
|
|
11
|
+
pageRanges?: string;
|
|
12
|
+
filename?: string;
|
|
13
|
+
/** If false, keeps the WeaveTab UI overlay visible in the PDF.
|
|
14
|
+
* Default: true — overlay (cursor, thought bubble, HUD) is always hidden from PDFs. */
|
|
15
|
+
hideOverlay?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function browserPdf(session: CDP.Client, options: PdfOptions, config: Config): Promise<{
|
|
18
|
+
success: boolean;
|
|
19
|
+
path: string;
|
|
20
|
+
size: string;
|
|
21
|
+
filename: string;
|
|
22
|
+
}>;
|