lsd-pi 1.3.2 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
- package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
- package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
- package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
- package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
- package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
- package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
- package/dist/resources/extensions/browser-tools/utils.js +1 -1
- package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
- package/dist/resources/extensions/slash-commands/fast.js +73 -0
- package/dist/resources/extensions/slash-commands/index.js +2 -0
- package/dist/resources/extensions/slash-commands/plan.js +37 -12
- package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
- package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
- package/dist/resources/extensions/subagent/index.js +278 -626
- package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
- package/dist/resources/extensions/voice/index.js +96 -36
- package/dist/resources/extensions/voice/push-to-talk.js +26 -0
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +19 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +16 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +32 -2
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +2 -0
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +5 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
- package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
- package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
- package/packages/pi-ai/src/providers/simple-options.ts +2 -0
- package/packages/pi-ai/src/types.ts +5 -0
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js +12 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +26 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +7 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +26 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +18 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +13 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +49 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +197 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +97 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +35 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
- package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +18 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
- package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
- package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +31 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +8 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +32 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1154 -1136
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +64 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +228 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +494 -398
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
- package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
- package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
- package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
- package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
- package/src/resources/extensions/browser-tools/utils.ts +1 -1
- package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
- package/src/resources/extensions/slash-commands/fast.ts +89 -0
- package/src/resources/extensions/slash-commands/index.ts +2 -0
- package/src/resources/extensions/slash-commands/plan.ts +42 -12
- package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
- package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
- package/src/resources/extensions/subagent/index.ts +489 -799
- package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
- package/src/resources/extensions/voice/index.ts +308 -238
- package/src/resources/extensions/voice/push-to-talk.ts +42 -0
- package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
|
@@ -62,9 +62,10 @@ export function registerCodegenTools(pi, deps) {
|
|
|
62
62
|
}
|
|
63
63
|
break;
|
|
64
64
|
}
|
|
65
|
-
case "
|
|
65
|
+
case "browser_ref": {
|
|
66
66
|
// Refs are session-specific — add comment
|
|
67
|
-
|
|
67
|
+
const refAction = params.action ?? "click";
|
|
68
|
+
testLines.push(` // browser_ref (${refAction}): ${entry.paramsSummary} — replace with stable selector`);
|
|
68
69
|
actionCount++;
|
|
69
70
|
break;
|
|
70
71
|
}
|
|
@@ -77,9 +78,8 @@ export function registerCodegenTools(pi, deps) {
|
|
|
77
78
|
}
|
|
78
79
|
break;
|
|
79
80
|
}
|
|
80
|
-
case "
|
|
81
|
-
|
|
82
|
-
actionCount++;
|
|
81
|
+
case "browser_fill_ref_handled": {
|
|
82
|
+
// Already handled by browser_ref above
|
|
83
83
|
break;
|
|
84
84
|
}
|
|
85
85
|
case "browser_key_press": {
|
|
@@ -8,201 +8,130 @@ export function registerNavigationTools(pi, deps) {
|
|
|
8
8
|
pi.registerTool({
|
|
9
9
|
name: "browser_navigate",
|
|
10
10
|
label: "Browser Navigate",
|
|
11
|
-
description: "
|
|
11
|
+
description: "Navigate to a URL, go back/forward in history, or reload the page. " +
|
|
12
|
+
"Use ONLY for locally-running web apps (e.g. http://localhost:3000). " +
|
|
13
|
+
"Do NOT use for documentation sites, GitHub, or external URLs — use web_search instead.",
|
|
12
14
|
parameters: Type.Object({
|
|
13
|
-
|
|
15
|
+
action: Type.Union([
|
|
16
|
+
Type.Literal("goto"),
|
|
17
|
+
Type.Literal("go_back"),
|
|
18
|
+
Type.Literal("go_forward"),
|
|
19
|
+
Type.Literal("reload"),
|
|
20
|
+
], { description: "'goto' — navigate to url (default), 'go_back'/'go_forward' — history, 'reload' — refresh page" }),
|
|
21
|
+
url: Type.Optional(Type.String({ description: "URL to navigate to (required for goto action)." })),
|
|
14
22
|
screenshot: Type.Optional(Type.Boolean({ description: "Capture and return a screenshot (default: false)", default: false })),
|
|
15
23
|
}),
|
|
16
24
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
17
|
-
|
|
18
|
-
let beforeState = null;
|
|
25
|
+
const action = params.action ?? "goto";
|
|
19
26
|
try {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
actionId = deps.beginTrackedAction("browser_navigate", params, beforeState.url).id;
|
|
23
|
-
await p.goto(params.url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
24
|
-
await p.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
25
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
26
|
-
const title = await p.title();
|
|
27
|
-
const url = p.url();
|
|
28
|
-
const viewport = p.viewportSize();
|
|
29
|
-
const vpText = viewport ? `${viewport.width}x${viewport.height}` : "unknown";
|
|
30
|
-
const afterState = await deps.captureCompactPageState(p, { includeBodyText: true });
|
|
31
|
-
const summary = deps.formatCompactStateSummary(afterState);
|
|
32
|
-
const jsErrors = deps.getRecentErrors(p.url());
|
|
33
|
-
const diff = diffCompactStates(beforeState, afterState);
|
|
34
|
-
setLastActionBeforeState(beforeState);
|
|
35
|
-
setLastActionAfterState(afterState);
|
|
36
|
-
deps.finishTrackedAction(actionId, {
|
|
37
|
-
status: "success",
|
|
38
|
-
afterUrl: afterState.url,
|
|
39
|
-
warningSummary: jsErrors.trim() || undefined,
|
|
40
|
-
diffSummary: diff.summary,
|
|
41
|
-
changed: diff.changed,
|
|
42
|
-
beforeState,
|
|
43
|
-
afterState,
|
|
44
|
-
});
|
|
45
|
-
let screenshotContent = [];
|
|
46
|
-
if (params.screenshot) {
|
|
47
|
-
try {
|
|
48
|
-
let buf = await p.screenshot({ type: "jpeg", quality: 80, scale: "css" });
|
|
49
|
-
buf = await deps.constrainScreenshot(p, buf, "image/jpeg", 80);
|
|
50
|
-
screenshotContent = [{ type: "image", data: buf.toString("base64"), mimeType: "image/jpeg" }];
|
|
51
|
-
}
|
|
52
|
-
catch { /* non-fatal — screenshot is optional, navigation result is still valid */ }
|
|
27
|
+
if (action === "goto") {
|
|
28
|
+
return await gotoAction(params);
|
|
53
29
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{ type: "text", text: `Navigated to: ${url}\nTitle: ${title}\nViewport: ${vpText}\nAction: ${actionId}${jsErrors}\n\nDiff:\n${deps.formatDiffText(diff)}\n\nPage summary:\n${summary}` },
|
|
57
|
-
...screenshotContent,
|
|
58
|
-
],
|
|
59
|
-
details: { title, url, status: "loaded", viewport: vpText, actionId, diff },
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
if (actionId !== null) {
|
|
64
|
-
deps.finishTrackedAction(actionId, { status: "error", afterUrl: deps.getActivePageOrNull()?.url() ?? "", error: err.message, beforeState: beforeState ?? undefined });
|
|
30
|
+
else if (action === "go_back") {
|
|
31
|
+
return await goBackForward("back");
|
|
65
32
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (errorShot) {
|
|
69
|
-
content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
|
|
33
|
+
else if (action === "go_forward") {
|
|
34
|
+
return await goBackForward("forward");
|
|
70
35
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
details: { status: "error", error: err.message, actionId },
|
|
74
|
-
isError: true,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
// -------------------------------------------------------------------------
|
|
80
|
-
// browser_go_back
|
|
81
|
-
// -------------------------------------------------------------------------
|
|
82
|
-
pi.registerTool({
|
|
83
|
-
name: "browser_go_back",
|
|
84
|
-
label: "Browser Go Back",
|
|
85
|
-
description: "Navigate back in browser history. Returns a compact page summary after navigation.",
|
|
86
|
-
parameters: Type.Object({}),
|
|
87
|
-
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
|
88
|
-
try {
|
|
89
|
-
const { page: p } = await deps.ensureBrowser();
|
|
90
|
-
const response = await p.goBack({ waitUntil: "domcontentloaded", timeout: 10000 });
|
|
91
|
-
if (!response) {
|
|
92
|
-
return {
|
|
93
|
-
content: [{ type: "text", text: "No previous page in history." }],
|
|
94
|
-
details: {},
|
|
95
|
-
isError: true,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
await p.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
99
|
-
const title = await p.title();
|
|
100
|
-
const url = p.url();
|
|
101
|
-
const summary = await deps.postActionSummary(p);
|
|
102
|
-
const jsErrors = deps.getRecentErrors(p.url());
|
|
103
|
-
return {
|
|
104
|
-
content: [{ type: "text", text: `Navigated back to: ${url}\nTitle: ${title}${jsErrors}\n\nPage summary:\n${summary}` }],
|
|
105
|
-
details: { title, url },
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
catch (err) {
|
|
109
|
-
const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
|
|
110
|
-
const content = [{ type: "text", text: `Go back failed: ${err.message}` }];
|
|
111
|
-
if (errorShot) {
|
|
112
|
-
content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
|
|
113
|
-
}
|
|
114
|
-
return { content, details: { error: err.message }, isError: true };
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
// -------------------------------------------------------------------------
|
|
119
|
-
// browser_go_forward
|
|
120
|
-
// -------------------------------------------------------------------------
|
|
121
|
-
pi.registerTool({
|
|
122
|
-
name: "browser_go_forward",
|
|
123
|
-
label: "Browser Go Forward",
|
|
124
|
-
description: "Navigate forward in browser history. Returns a compact page summary after navigation.",
|
|
125
|
-
parameters: Type.Object({}),
|
|
126
|
-
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
|
127
|
-
try {
|
|
128
|
-
const { page: p } = await deps.ensureBrowser();
|
|
129
|
-
const response = await p.goForward({ waitUntil: "domcontentloaded", timeout: 10000 });
|
|
130
|
-
if (!response) {
|
|
131
|
-
return {
|
|
132
|
-
content: [{ type: "text", text: "No forward page in history." }],
|
|
133
|
-
details: {},
|
|
134
|
-
isError: true,
|
|
135
|
-
};
|
|
36
|
+
else {
|
|
37
|
+
return await reloadAction();
|
|
136
38
|
}
|
|
137
|
-
await p.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
138
|
-
const title = await p.title();
|
|
139
|
-
const url = p.url();
|
|
140
|
-
const summary = await deps.postActionSummary(p);
|
|
141
|
-
const jsErrors = deps.getRecentErrors(p.url());
|
|
142
|
-
return {
|
|
143
|
-
content: [{ type: "text", text: `Navigated forward to: ${url}\nTitle: ${title}${jsErrors}\n\nPage summary:\n${summary}` }],
|
|
144
|
-
details: { title, url },
|
|
145
|
-
};
|
|
146
39
|
}
|
|
147
40
|
catch (err) {
|
|
148
41
|
const errorShot = await deps.captureErrorScreenshot(deps.getActivePageOrNull());
|
|
149
|
-
const content = [{ type: "text", text: `
|
|
150
|
-
if (errorShot)
|
|
42
|
+
const content = [{ type: "text", text: `Navigation '${action}' failed: ${err.message}` }];
|
|
43
|
+
if (errorShot)
|
|
151
44
|
content.push({ type: "image", data: errorShot.data, mimeType: errorShot.mimeType });
|
|
152
|
-
}
|
|
153
45
|
return { content, details: { error: err.message }, isError: true };
|
|
154
46
|
}
|
|
155
47
|
},
|
|
156
48
|
});
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
49
|
+
// ── action implementations ──
|
|
50
|
+
async function gotoAction(params) {
|
|
51
|
+
if (!params.url) {
|
|
52
|
+
return { content: [{ type: "text", text: "Goto requires a 'url' parameter." }], details: { error: "missing_url" }, isError: true };
|
|
53
|
+
}
|
|
54
|
+
let actionId = null;
|
|
55
|
+
let beforeState = null;
|
|
56
|
+
const { page: p } = await deps.ensureBrowser();
|
|
57
|
+
beforeState = await deps.captureCompactPageState(p, { includeBodyText: true });
|
|
58
|
+
actionId = deps.beginTrackedAction("browser_navigate", params, beforeState.url).id;
|
|
59
|
+
await p.goto(params.url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
60
|
+
await p.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
62
|
+
const title = await p.title();
|
|
63
|
+
const url = p.url();
|
|
64
|
+
const viewport = p.viewportSize();
|
|
65
|
+
const vpText = viewport ? `${viewport.width}x${viewport.height}` : "unknown";
|
|
66
|
+
const afterState = await deps.captureCompactPageState(p, { includeBodyText: true });
|
|
67
|
+
const summary = deps.formatCompactStateSummary(afterState);
|
|
68
|
+
const jsErrors = deps.getRecentErrors(p.url());
|
|
69
|
+
const diff = diffCompactStates(beforeState, afterState);
|
|
70
|
+
setLastActionBeforeState(beforeState);
|
|
71
|
+
setLastActionAfterState(afterState);
|
|
72
|
+
deps.finishTrackedAction(actionId, {
|
|
73
|
+
status: "success", afterUrl: afterState.url, warningSummary: jsErrors.trim() || undefined,
|
|
74
|
+
diffSummary: diff.summary, changed: diff.changed, beforeState, afterState,
|
|
75
|
+
});
|
|
76
|
+
let screenshotContent = [];
|
|
77
|
+
if (params.screenshot) {
|
|
166
78
|
try {
|
|
167
|
-
|
|
168
|
-
await
|
|
169
|
-
|
|
170
|
-
const title = await p.title();
|
|
171
|
-
const url = p.url();
|
|
172
|
-
const viewport = p.viewportSize();
|
|
173
|
-
const vpText = viewport ? `${viewport.width}x${viewport.height}` : "unknown";
|
|
174
|
-
const summary = await deps.postActionSummary(p);
|
|
175
|
-
const jsErrors = deps.getRecentErrors(p.url());
|
|
176
|
-
let screenshotContent = [];
|
|
177
|
-
try {
|
|
178
|
-
let buf = await p.screenshot({ type: "jpeg", quality: 80, scale: "css" });
|
|
179
|
-
buf = await deps.constrainScreenshot(p, buf, "image/jpeg", 80);
|
|
180
|
-
screenshotContent = [{
|
|
181
|
-
type: "image",
|
|
182
|
-
data: buf.toString("base64"),
|
|
183
|
-
mimeType: "image/jpeg",
|
|
184
|
-
}];
|
|
185
|
-
}
|
|
186
|
-
catch { /* non-fatal — screenshot is optional, reload result is still valid */ }
|
|
187
|
-
return {
|
|
188
|
-
content: [
|
|
189
|
-
{
|
|
190
|
-
type: "text",
|
|
191
|
-
text: `Reloaded: ${url}\nTitle: ${title}\nViewport: ${vpText}${jsErrors}\n\nPage summary:\n${summary}`,
|
|
192
|
-
},
|
|
193
|
-
...screenshotContent,
|
|
194
|
-
],
|
|
195
|
-
details: { title, url, viewport: vpText },
|
|
196
|
-
};
|
|
79
|
+
let buf = await p.screenshot({ type: "jpeg", quality: 80, scale: "css" });
|
|
80
|
+
buf = await deps.constrainScreenshot(p, buf, "image/jpeg", 80);
|
|
81
|
+
screenshotContent = [{ type: "image", data: buf.toString("base64"), mimeType: "image/jpeg" }];
|
|
197
82
|
}
|
|
198
|
-
catch
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
83
|
+
catch { /* non-fatal */ }
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{ type: "text", text: `Navigated to: ${url}\nTitle: ${title}\nViewport: ${vpText}\nAction: ${actionId}${jsErrors}\n\nDiff:\n${deps.formatDiffText(diff)}\n\nPage summary:\n${summary}` },
|
|
88
|
+
...screenshotContent,
|
|
89
|
+
],
|
|
90
|
+
details: { title, url, status: "loaded", viewport: vpText, actionId, diff },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function goBackForward(direction) {
|
|
94
|
+
const { page: p } = await deps.ensureBrowser();
|
|
95
|
+
const response = direction === "back"
|
|
96
|
+
? await p.goBack({ waitUntil: "domcontentloaded", timeout: 10000 })
|
|
97
|
+
: await p.goForward({ waitUntil: "domcontentloaded", timeout: 10000 });
|
|
98
|
+
if (!response) {
|
|
99
|
+
return {
|
|
100
|
+
content: [{ type: "text", text: `No ${direction} page in history.` }],
|
|
101
|
+
details: {},
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
await p.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
106
|
+
const title = await p.title();
|
|
107
|
+
const url = p.url();
|
|
108
|
+
const summary = await deps.postActionSummary(p);
|
|
109
|
+
const jsErrors = deps.getRecentErrors(p.url());
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: "text", text: `Navigated ${direction} to: ${url}\nTitle: ${title}${jsErrors}\n\nPage summary:\n${summary}` }],
|
|
112
|
+
details: { title, url },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function reloadAction() {
|
|
116
|
+
const { page: p } = await deps.ensureBrowser();
|
|
117
|
+
await p.reload({ waitUntil: "domcontentloaded", timeout: 30000 });
|
|
118
|
+
await p.waitForLoadState("networkidle", { timeout: 5000 }).catch(() => { });
|
|
119
|
+
const title = await p.title();
|
|
120
|
+
const url = p.url();
|
|
121
|
+
const viewport = p.viewportSize();
|
|
122
|
+
const vpText = viewport ? `${viewport.width}x${viewport.height}` : "unknown";
|
|
123
|
+
const summary = await deps.postActionSummary(p);
|
|
124
|
+
const jsErrors = deps.getRecentErrors(p.url());
|
|
125
|
+
let screenshotContent = [];
|
|
126
|
+
try {
|
|
127
|
+
let buf = await p.screenshot({ type: "jpeg", quality: 80, scale: "css" });
|
|
128
|
+
buf = await deps.constrainScreenshot(p, buf, "image/jpeg", 80);
|
|
129
|
+
screenshotContent = [{ type: "image", data: buf.toString("base64"), mimeType: "image/jpeg" }];
|
|
130
|
+
}
|
|
131
|
+
catch { /* non-fatal */ }
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: `Reloaded: ${url}\nTitle: ${title}\nViewport: ${vpText}${jsErrors}\n\nPage summary:\n${summary}` }, ...screenshotContent],
|
|
134
|
+
details: { title, url, viewport: vpText },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
208
137
|
}
|
|
@@ -3,192 +3,137 @@ let nextRouteId = 1;
|
|
|
3
3
|
const activeRoutes = [];
|
|
4
4
|
const routeCleanups = new Map();
|
|
5
5
|
export function registerNetworkMockTools(pi, deps) {
|
|
6
|
-
// -------------------------------------------------------------------------
|
|
7
|
-
// browser_mock_route
|
|
8
|
-
// -------------------------------------------------------------------------
|
|
9
6
|
pi.registerTool({
|
|
10
|
-
name: "
|
|
11
|
-
label: "Browser
|
|
12
|
-
description: "
|
|
13
|
-
"
|
|
14
|
-
"Routes survive page navigation within the same context. Use browser_clear_routes to remove all mocks.",
|
|
7
|
+
name: "browser_network",
|
|
8
|
+
label: "Browser Network",
|
|
9
|
+
description: "Manage browser network interception: mock API responses, block URL patterns, or clear active routes. " +
|
|
10
|
+
"Routes survive page navigation within the same context.",
|
|
15
11
|
parameters: Type.Object({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
action: Type.Union([
|
|
13
|
+
Type.Literal("mock"),
|
|
14
|
+
Type.Literal("block"),
|
|
15
|
+
Type.Literal("clear"),
|
|
16
|
+
], { description: "'mock' — intercept and respond, 'block' — abort matching requests, 'clear' — remove all active routes" }),
|
|
17
|
+
url: Type.Optional(Type.String({
|
|
18
|
+
description: "URL pattern to intercept (glob or exact). Required for mock/block.",
|
|
19
|
+
})),
|
|
20
|
+
status: Type.Optional(Type.Number({ description: "HTTP status code for mock response (default: 200)." })),
|
|
21
|
+
body: Type.Optional(Type.String({ description: "Response body string for mock." })),
|
|
22
|
+
contentType: Type.Optional(Type.String({ description: "Content-Type header for mock." })),
|
|
22
23
|
headers: Type.Optional(Type.Record(Type.String(), Type.String(), {
|
|
23
|
-
description: "Additional response headers
|
|
24
|
+
description: "Additional response headers for mock.",
|
|
25
|
+
})),
|
|
26
|
+
delay: Type.Optional(Type.Number({ description: "Delay in ms before sending mock response." })),
|
|
27
|
+
patterns: Type.Optional(Type.Array(Type.String(), {
|
|
28
|
+
description: "URL patterns to block (for block action). Glob syntax.",
|
|
24
29
|
})),
|
|
25
|
-
delay: Type.Optional(Type.Number({ description: "Delay in milliseconds before sending the response. Simulates slow responses." })),
|
|
26
30
|
}),
|
|
27
31
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
28
32
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const status = params.status ?? 200;
|
|
32
|
-
const body = params.body ?? "";
|
|
33
|
-
const delay = params.delay ?? 0;
|
|
34
|
-
// Auto-detect content type
|
|
35
|
-
let contentType = params.contentType;
|
|
36
|
-
if (!contentType) {
|
|
37
|
-
try {
|
|
38
|
-
JSON.parse(body);
|
|
39
|
-
contentType = "application/json";
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
contentType = "text/plain";
|
|
43
|
-
}
|
|
33
|
+
if (params.action === "mock") {
|
|
34
|
+
return await handleMock(deps, params);
|
|
44
35
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const handler = async (route) => {
|
|
51
|
-
if (delay > 0) {
|
|
52
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
53
|
-
}
|
|
54
|
-
await route.fulfill({
|
|
55
|
-
status,
|
|
56
|
-
body,
|
|
57
|
-
headers,
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
await p.route(params.url, handler);
|
|
61
|
-
const cleanup = async () => {
|
|
62
|
-
try {
|
|
63
|
-
await p.unroute(params.url, handler);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// Page may be closed
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
const routeInfo = {
|
|
70
|
-
id: routeId,
|
|
71
|
-
pattern: params.url,
|
|
72
|
-
type: "mock",
|
|
73
|
-
status,
|
|
74
|
-
delay: delay > 0 ? delay : undefined,
|
|
75
|
-
description: `Mock ${params.url} → ${status}${delay > 0 ? ` (${delay}ms delay)` : ""}`,
|
|
76
|
-
};
|
|
77
|
-
activeRoutes.push(routeInfo);
|
|
78
|
-
routeCleanups.set(routeId, cleanup);
|
|
79
|
-
return {
|
|
80
|
-
content: [{
|
|
81
|
-
type: "text",
|
|
82
|
-
text: `Route mocked: ${routeInfo.description}\nRoute ID: ${routeId}\nActive routes: ${activeRoutes.length}`,
|
|
83
|
-
}],
|
|
84
|
-
details: { routeId, ...routeInfo, activeRouteCount: activeRoutes.length },
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
return {
|
|
89
|
-
content: [{ type: "text", text: `Mock route failed: ${err.message}` }],
|
|
90
|
-
details: { error: err.message },
|
|
91
|
-
isError: true,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
// -------------------------------------------------------------------------
|
|
97
|
-
// browser_block_urls
|
|
98
|
-
// -------------------------------------------------------------------------
|
|
99
|
-
pi.registerTool({
|
|
100
|
-
name: "browser_block_urls",
|
|
101
|
-
label: "Browser Block URLs",
|
|
102
|
-
description: "Block network requests matching URL patterns. Useful for blocking analytics, ads, or third-party scripts. " +
|
|
103
|
-
"Accepts glob patterns. Routes survive page navigation.",
|
|
104
|
-
parameters: Type.Object({
|
|
105
|
-
patterns: Type.Array(Type.String(), {
|
|
106
|
-
description: "URL patterns to block (glob syntax, e.g., ['**/analytics*', '**/ads*']).",
|
|
107
|
-
}),
|
|
108
|
-
}),
|
|
109
|
-
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
110
|
-
try {
|
|
111
|
-
const { page: p } = await deps.ensureBrowser();
|
|
112
|
-
const results = [];
|
|
113
|
-
for (const pattern of params.patterns) {
|
|
114
|
-
const routeId = nextRouteId++;
|
|
115
|
-
const handler = async (route) => {
|
|
116
|
-
await route.abort("blockedbyclient");
|
|
117
|
-
};
|
|
118
|
-
await p.route(pattern, handler);
|
|
119
|
-
const cleanup = async () => {
|
|
120
|
-
try {
|
|
121
|
-
await p.unroute(pattern, handler);
|
|
122
|
-
}
|
|
123
|
-
catch { /* cleanup — route may already be removed or page closed */ }
|
|
124
|
-
};
|
|
125
|
-
const routeInfo = {
|
|
126
|
-
id: routeId,
|
|
127
|
-
pattern,
|
|
128
|
-
type: "block",
|
|
129
|
-
description: `Block ${pattern}`,
|
|
130
|
-
};
|
|
131
|
-
activeRoutes.push(routeInfo);
|
|
132
|
-
routeCleanups.set(routeId, cleanup);
|
|
133
|
-
results.push(routeInfo);
|
|
36
|
+
else if (params.action === "block") {
|
|
37
|
+
return await handleBlock(deps, params);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return await handleClear(deps);
|
|
134
41
|
}
|
|
135
|
-
return {
|
|
136
|
-
content: [{
|
|
137
|
-
type: "text",
|
|
138
|
-
text: `Blocked ${results.length} URL pattern(s):\n${results.map((r) => ` - ${r.description} (ID: ${r.id})`).join("\n")}\nActive routes: ${activeRoutes.length}`,
|
|
139
|
-
}],
|
|
140
|
-
details: { blocked: results, activeRouteCount: activeRoutes.length },
|
|
141
|
-
};
|
|
142
42
|
}
|
|
143
43
|
catch (err) {
|
|
144
44
|
return {
|
|
145
|
-
content: [{ type: "text", text: `
|
|
45
|
+
content: [{ type: "text", text: `Network action '${params.action}' failed: ${err.message}` }],
|
|
146
46
|
details: { error: err.message },
|
|
147
47
|
isError: true,
|
|
148
48
|
};
|
|
149
49
|
}
|
|
150
50
|
},
|
|
151
51
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
52
|
+
async function handleMock(deps, params) {
|
|
53
|
+
if (!params.url) {
|
|
54
|
+
return { content: [{ type: "text", text: "Mock requires a 'url' parameter." }], details: { error: "missing_url" }, isError: true };
|
|
55
|
+
}
|
|
56
|
+
const { page: p } = await deps.ensureBrowser();
|
|
57
|
+
const routeId = nextRouteId++;
|
|
58
|
+
const status = params.status ?? 200;
|
|
59
|
+
const body = params.body ?? "";
|
|
60
|
+
const delay = params.delay ?? 0;
|
|
61
|
+
let contentType = params.contentType;
|
|
62
|
+
if (!contentType) {
|
|
161
63
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (count === 0) {
|
|
165
|
-
return {
|
|
166
|
-
content: [{ type: "text", text: "No active routes to clear." }],
|
|
167
|
-
details: { cleared: 0 },
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
const routeDescriptions = activeRoutes.map((r) => r.description);
|
|
171
|
-
// Clean up all routes
|
|
172
|
-
for (const [id, cleanup] of routeCleanups) {
|
|
173
|
-
await cleanup();
|
|
174
|
-
}
|
|
175
|
-
activeRoutes.length = 0;
|
|
176
|
-
routeCleanups.clear();
|
|
177
|
-
return {
|
|
178
|
-
content: [{
|
|
179
|
-
type: "text",
|
|
180
|
-
text: `Cleared ${count} route(s):\n${routeDescriptions.map((d) => ` - ${d}`).join("\n")}`,
|
|
181
|
-
}],
|
|
182
|
-
details: { cleared: count, routes: routeDescriptions },
|
|
183
|
-
};
|
|
64
|
+
JSON.parse(body);
|
|
65
|
+
contentType = "application/json";
|
|
184
66
|
}
|
|
185
|
-
catch
|
|
186
|
-
|
|
187
|
-
content: [{ type: "text", text: `Clear routes failed: ${err.message}` }],
|
|
188
|
-
details: { error: err.message },
|
|
189
|
-
isError: true,
|
|
190
|
-
};
|
|
67
|
+
catch {
|
|
68
|
+
contentType = "text/plain";
|
|
191
69
|
}
|
|
192
|
-
}
|
|
193
|
-
|
|
70
|
+
}
|
|
71
|
+
const respHeaders = {
|
|
72
|
+
"content-type": contentType,
|
|
73
|
+
"access-control-allow-origin": "*",
|
|
74
|
+
...(params.headers ?? {}),
|
|
75
|
+
};
|
|
76
|
+
const handler = async (route) => {
|
|
77
|
+
if (delay > 0)
|
|
78
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
79
|
+
await route.fulfill({ status, body, headers: respHeaders });
|
|
80
|
+
};
|
|
81
|
+
await p.route(params.url, handler);
|
|
82
|
+
const cleanup = async () => { try {
|
|
83
|
+
await p.unroute(params.url, handler);
|
|
84
|
+
}
|
|
85
|
+
catch { /* page may be closed */ } };
|
|
86
|
+
const routeInfo = {
|
|
87
|
+
id: routeId, pattern: params.url, type: "mock", status,
|
|
88
|
+
delay: delay > 0 ? delay : undefined,
|
|
89
|
+
description: `Mock ${params.url} → ${status}${delay > 0 ? ` (${delay}ms delay)` : ""}`,
|
|
90
|
+
};
|
|
91
|
+
activeRoutes.push(routeInfo);
|
|
92
|
+
routeCleanups.set(routeId, cleanup);
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: "text", text: `Route mocked: ${routeInfo.description}\nRoute ID: ${routeId}\nActive routes: ${activeRoutes.length}` }],
|
|
95
|
+
details: { routeId, ...routeInfo, activeRouteCount: activeRoutes.length },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function handleBlock(deps, params) {
|
|
99
|
+
const patterns = params.patterns ?? (params.url ? [params.url] : []);
|
|
100
|
+
if (patterns.length === 0) {
|
|
101
|
+
return { content: [{ type: "text", text: "Block requires 'patterns' or 'url' parameter." }], details: { error: "missing_patterns" }, isError: true };
|
|
102
|
+
}
|
|
103
|
+
const { page: p } = await deps.ensureBrowser();
|
|
104
|
+
const results = [];
|
|
105
|
+
for (const pattern of patterns) {
|
|
106
|
+
const routeId = nextRouteId++;
|
|
107
|
+
const handler = async (route) => { await route.abort("blockedbyclient"); };
|
|
108
|
+
await p.route(pattern, handler);
|
|
109
|
+
const cleanup = async () => { try {
|
|
110
|
+
await p.unroute(pattern, handler);
|
|
111
|
+
}
|
|
112
|
+
catch { /* cleanup */ } };
|
|
113
|
+
const routeInfo = { id: routeId, pattern, type: "block", description: `Block ${pattern}` };
|
|
114
|
+
activeRoutes.push(routeInfo);
|
|
115
|
+
routeCleanups.set(routeId, cleanup);
|
|
116
|
+
results.push(routeInfo);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: `Blocked ${results.length} URL pattern(s):\n${results.map((r) => ` - ${r.description} (ID: ${r.id})`).join("\n")}\nActive routes: ${activeRoutes.length}` }],
|
|
120
|
+
details: { blocked: results, activeRouteCount: activeRoutes.length },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function handleClear(_deps) {
|
|
124
|
+
await _deps.ensureBrowser();
|
|
125
|
+
const count = activeRoutes.length;
|
|
126
|
+
if (count === 0) {
|
|
127
|
+
return { content: [{ type: "text", text: "No active routes to clear." }], details: { cleared: 0 } };
|
|
128
|
+
}
|
|
129
|
+
const descriptions = activeRoutes.map((r) => r.description);
|
|
130
|
+
for (const [, cleanup] of routeCleanups)
|
|
131
|
+
await cleanup();
|
|
132
|
+
activeRoutes.length = 0;
|
|
133
|
+
routeCleanups.clear();
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: `Cleared ${count} route(s):\n${descriptions.map((d) => ` - ${d}`).join("\n")}` }],
|
|
136
|
+
details: { cleared: count, routes: descriptions },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
194
139
|
}
|