gsd-pi 2.22.0 → 2.24.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/README.md +25 -1
- package/dist/cli.js +74 -7
- package/dist/headless.d.ts +25 -0
- package/dist/headless.js +454 -0
- package/dist/help-text.js +47 -0
- package/dist/mcp-server.d.ts +20 -3
- package/dist/mcp-server.js +21 -1
- package/dist/models-resolver.d.ts +32 -0
- package/dist/models-resolver.js +50 -0
- package/dist/resource-loader.js +64 -9
- package/dist/resources/extensions/bg-shell/output-formatter.ts +36 -16
- package/dist/resources/extensions/bg-shell/process-manager.ts +6 -4
- package/dist/resources/extensions/bg-shell/types.ts +33 -1
- package/dist/resources/extensions/browser-tools/capture.ts +18 -16
- package/dist/resources/extensions/browser-tools/index.ts +20 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
- package/dist/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
- package/dist/resources/extensions/browser-tools/tools/codegen.ts +274 -0
- package/dist/resources/extensions/browser-tools/tools/device.ts +183 -0
- package/dist/resources/extensions/browser-tools/tools/extract.ts +229 -0
- package/dist/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
- package/dist/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
- package/dist/resources/extensions/browser-tools/tools/pdf.ts +92 -0
- package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
- package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.ts +104 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +51 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/dist/resources/extensions/gsd/auto.ts +560 -52
- package/dist/resources/extensions/gsd/captures.ts +49 -0
- package/dist/resources/extensions/gsd/commands.ts +194 -11
- package/dist/resources/extensions/gsd/complexity.ts +1 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +54 -2
- package/dist/resources/extensions/gsd/diff-context.ts +73 -80
- package/dist/resources/extensions/gsd/doctor.ts +76 -12
- package/dist/resources/extensions/gsd/exit-command.ts +2 -2
- package/dist/resources/extensions/gsd/forensics.ts +95 -52
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +85 -5
- package/dist/resources/extensions/gsd/index.ts +34 -1
- package/dist/resources/extensions/gsd/mcp-server.ts +33 -12
- package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +65 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
- package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/dist/resources/extensions/gsd/roadmap-slices.ts +41 -1
- package/dist/resources/extensions/gsd/session-forensics.ts +36 -2
- package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
- package/dist/resources/extensions/gsd/state.ts +72 -30
- package/dist/resources/extensions/gsd/templates/milestone-validation.md +62 -0
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/dist/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/doctor.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
- package/dist/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
- package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +83 -0
- package/dist/resources/extensions/gsd/types.ts +15 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +8 -1
- package/dist/resources/extensions/gsd/workspace-index.ts +34 -6
- package/dist/resources/extensions/subagent/index.ts +5 -0
- package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
- package/dist/update-check.d.ts +9 -0
- package/dist/update-check.js +97 -0
- package/package.json +6 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +16 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +21 -8
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
- package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
- package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js +79 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +77 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
- package/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +91 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +83 -1
- package/packages/pi-coding-agent/src/core/tools/index.ts +1 -0
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/scripts/postinstall.js +7 -109
- package/src/resources/extensions/bg-shell/output-formatter.ts +36 -16
- package/src/resources/extensions/bg-shell/process-manager.ts +6 -4
- package/src/resources/extensions/bg-shell/types.ts +33 -1
- package/src/resources/extensions/browser-tools/capture.ts +18 -16
- package/src/resources/extensions/browser-tools/index.ts +20 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
- package/src/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
- package/src/resources/extensions/browser-tools/tools/codegen.ts +274 -0
- package/src/resources/extensions/browser-tools/tools/device.ts +183 -0
- package/src/resources/extensions/browser-tools/tools/extract.ts +229 -0
- package/src/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
- package/src/resources/extensions/browser-tools/tools/pdf.ts +92 -0
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
- package/src/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +104 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +51 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/src/resources/extensions/gsd/auto.ts +560 -52
- package/src/resources/extensions/gsd/captures.ts +49 -0
- package/src/resources/extensions/gsd/commands.ts +194 -11
- package/src/resources/extensions/gsd/complexity.ts +1 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +54 -2
- package/src/resources/extensions/gsd/diff-context.ts +73 -80
- package/src/resources/extensions/gsd/doctor.ts +76 -12
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/forensics.ts +95 -52
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +85 -5
- package/src/resources/extensions/gsd/index.ts +34 -1
- package/src/resources/extensions/gsd/mcp-server.ts +33 -12
- package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +65 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
- package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/src/resources/extensions/gsd/roadmap-slices.ts +41 -1
- package/src/resources/extensions/gsd/session-forensics.ts +36 -2
- package/src/resources/extensions/gsd/session-status-io.ts +197 -0
- package/src/resources/extensions/gsd/state.ts +72 -30
- package/src/resources/extensions/gsd/templates/milestone-validation.md +62 -0
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/doctor.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
- package/src/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +83 -0
- package/src/resources/extensions/gsd/types.ts +15 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +8 -1
- package/src/resources/extensions/gsd/workspace-index.ts +34 -6
- package/src/resources/extensions/subagent/index.ts +5 -0
- package/src/resources/extensions/subagent/worker-registry.ts +99 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import type { ToolDeps } from "../state.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Prompt injection detection — scan page content for text attempting to hijack the agent.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Known injection patterns — regex patterns that match common prompt injection attempts
|
|
10
|
+
const INJECTION_PATTERNS: Array<{ pattern: RegExp; category: string; severity: "high" | "medium" | "low" }> = [
|
|
11
|
+
// Direct instruction override attempts
|
|
12
|
+
{ pattern: /ignore\s+(all\s+)?previous\s+(instructions?|prompts?)/i, category: "instruction_override", severity: "high" },
|
|
13
|
+
{ pattern: /disregard\s+(all\s+)?previous\s+(instructions?|prompts?)/i, category: "instruction_override", severity: "high" },
|
|
14
|
+
{ pattern: /forget\s+(all\s+)?previous\s+(instructions?|prompts?)/i, category: "instruction_override", severity: "high" },
|
|
15
|
+
{ pattern: /override\s+(all\s+)?previous\s+(instructions?|prompts?)/i, category: "instruction_override", severity: "high" },
|
|
16
|
+
|
|
17
|
+
// System prompt extraction
|
|
18
|
+
{ pattern: /(?:what|show|reveal|display|repeat|tell)\s+(?:me\s+)?(?:your|the)\s+system\s+prompt/i, category: "prompt_extraction", severity: "high" },
|
|
19
|
+
{ pattern: /print\s+(?:your|the)\s+(?:system\s+)?(?:prompt|instructions)/i, category: "prompt_extraction", severity: "high" },
|
|
20
|
+
|
|
21
|
+
// Role reassignment
|
|
22
|
+
{ pattern: /you\s+are\s+now\s+(?:a\s+)?(?:DAN|jailbreak|unrestricted|unfiltered)/i, category: "role_reassignment", severity: "high" },
|
|
23
|
+
{ pattern: /act\s+as\s+(?:a\s+)?(?:DAN|jailbreak|unrestricted|unfiltered)/i, category: "role_reassignment", severity: "high" },
|
|
24
|
+
{ pattern: /entering\s+(?:a\s+)?(?:developer|admin|root|sudo)\s+mode/i, category: "role_reassignment", severity: "high" },
|
|
25
|
+
|
|
26
|
+
// Hidden instruction injection
|
|
27
|
+
{ pattern: /\[SYSTEM\]\s*:/i, category: "fake_system_message", severity: "high" },
|
|
28
|
+
{ pattern: /\[INST\]\s*:/i, category: "fake_system_message", severity: "medium" },
|
|
29
|
+
{ pattern: /<\/?system>/i, category: "fake_system_message", severity: "high" },
|
|
30
|
+
|
|
31
|
+
// Tool/action manipulation
|
|
32
|
+
{ pattern: /execute\s+(?:the\s+following\s+)?(?:command|code|script)/i, category: "command_injection", severity: "medium" },
|
|
33
|
+
{ pattern: /run\s+(?:this|the\s+following)\s+(?:command|code|script)/i, category: "command_injection", severity: "medium" },
|
|
34
|
+
|
|
35
|
+
// Invisible text / social engineering
|
|
36
|
+
{ pattern: /do\s+not\s+(?:read|process|show)\s+(?:the\s+)?(?:following|rest)/i, category: "social_engineering", severity: "low" },
|
|
37
|
+
{ pattern: /(?:this|the\s+following)\s+(?:is|are)\s+(?:your\s+)?new\s+instructions/i, category: "instruction_override", severity: "high" },
|
|
38
|
+
|
|
39
|
+
// Base64/encoded content markers
|
|
40
|
+
{ pattern: /base64\s*:\s*[A-Za-z0-9+\/=]{50,}/i, category: "encoded_payload", severity: "medium" },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export function registerInjectionDetectionTools(pi: ExtensionAPI, deps: ToolDeps): void {
|
|
44
|
+
pi.registerTool({
|
|
45
|
+
name: "browser_check_injection",
|
|
46
|
+
label: "Browser Check Injection",
|
|
47
|
+
description:
|
|
48
|
+
"Scan current page content for potential prompt injection attempts. " +
|
|
49
|
+
"Checks visible text and hidden elements for patterns that might hijack the agent. " +
|
|
50
|
+
"Returns findings with severity levels. Use after navigating to untrusted pages.",
|
|
51
|
+
parameters: Type.Object({
|
|
52
|
+
includeHidden: Type.Optional(
|
|
53
|
+
Type.Boolean({
|
|
54
|
+
description:
|
|
55
|
+
"Also scan hidden/invisible text (default: true). " +
|
|
56
|
+
"Hidden text is a common vector for injection attacks.",
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
59
|
+
}),
|
|
60
|
+
|
|
61
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
62
|
+
try {
|
|
63
|
+
const { page: p } = await deps.ensureBrowser();
|
|
64
|
+
const includeHidden = params.includeHidden ?? true;
|
|
65
|
+
|
|
66
|
+
// Extract text content from the page
|
|
67
|
+
const pageContent = await p.evaluate((scanHidden: boolean) => {
|
|
68
|
+
const results: Array<{ text: string; source: string; visible: boolean }> = [];
|
|
69
|
+
|
|
70
|
+
// 1. Visible text content
|
|
71
|
+
const bodyText = document.body?.innerText ?? "";
|
|
72
|
+
results.push({ text: bodyText, source: "body_visible_text", visible: true });
|
|
73
|
+
|
|
74
|
+
// 2. Title and meta
|
|
75
|
+
results.push({ text: document.title, source: "page_title", visible: true });
|
|
76
|
+
|
|
77
|
+
// Meta descriptions and keywords
|
|
78
|
+
const metas = document.querySelectorAll("meta[name], meta[property]");
|
|
79
|
+
for (const meta of metas) {
|
|
80
|
+
const content = meta.getAttribute("content");
|
|
81
|
+
if (content) {
|
|
82
|
+
results.push({
|
|
83
|
+
text: content,
|
|
84
|
+
source: `meta:${meta.getAttribute("name") || meta.getAttribute("property")}`,
|
|
85
|
+
visible: false,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (scanHidden) {
|
|
91
|
+
// 3. Hidden elements (display:none, visibility:hidden, opacity:0, off-screen, aria-hidden)
|
|
92
|
+
const allElements = document.querySelectorAll("*");
|
|
93
|
+
for (const el of allElements) {
|
|
94
|
+
const htmlEl = el as HTMLElement;
|
|
95
|
+
const style = window.getComputedStyle(htmlEl);
|
|
96
|
+
const isHidden =
|
|
97
|
+
style.display === "none" ||
|
|
98
|
+
style.visibility === "hidden" ||
|
|
99
|
+
style.opacity === "0" ||
|
|
100
|
+
htmlEl.getAttribute("aria-hidden") === "true" ||
|
|
101
|
+
(htmlEl.offsetWidth === 0 && htmlEl.offsetHeight === 0);
|
|
102
|
+
|
|
103
|
+
if (isHidden && htmlEl.textContent?.trim()) {
|
|
104
|
+
const text = htmlEl.textContent.trim();
|
|
105
|
+
if (text.length > 5 && text.length < 5000) {
|
|
106
|
+
results.push({ text, source: "hidden_element", visible: false });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 4. HTML comments
|
|
112
|
+
const walker = document.createTreeWalker(
|
|
113
|
+
document.documentElement,
|
|
114
|
+
NodeFilter.SHOW_COMMENT,
|
|
115
|
+
);
|
|
116
|
+
let node;
|
|
117
|
+
while ((node = walker.nextNode())) {
|
|
118
|
+
const text = (node as Comment).textContent?.trim() ?? "";
|
|
119
|
+
if (text.length > 10) {
|
|
120
|
+
results.push({ text, source: "html_comment", visible: false });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 5. Data attributes with text content
|
|
125
|
+
const dataElements = document.querySelectorAll("[data-prompt], [data-instruction], [data-system]");
|
|
126
|
+
for (const el of dataElements) {
|
|
127
|
+
for (const attr of el.attributes) {
|
|
128
|
+
if (attr.name.startsWith("data-") && attr.value.length > 10) {
|
|
129
|
+
results.push({
|
|
130
|
+
text: attr.value,
|
|
131
|
+
source: `data_attribute:${attr.name}`,
|
|
132
|
+
visible: false,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return results;
|
|
140
|
+
}, includeHidden);
|
|
141
|
+
|
|
142
|
+
// Scan all extracted text against injection patterns
|
|
143
|
+
const findings: Array<{
|
|
144
|
+
pattern: string;
|
|
145
|
+
category: string;
|
|
146
|
+
severity: string;
|
|
147
|
+
source: string;
|
|
148
|
+
visible: boolean;
|
|
149
|
+
matchedText: string;
|
|
150
|
+
}> = [];
|
|
151
|
+
|
|
152
|
+
for (const { text, source, visible } of pageContent) {
|
|
153
|
+
for (const { pattern, category, severity } of INJECTION_PATTERNS) {
|
|
154
|
+
const match = text.match(pattern);
|
|
155
|
+
if (match) {
|
|
156
|
+
findings.push({
|
|
157
|
+
pattern: pattern.source.slice(0, 60),
|
|
158
|
+
category,
|
|
159
|
+
severity,
|
|
160
|
+
source,
|
|
161
|
+
visible,
|
|
162
|
+
matchedText: match[0].slice(0, 100),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Deduplicate findings by category + source
|
|
169
|
+
const seen = new Set<string>();
|
|
170
|
+
const uniqueFindings = findings.filter((f) => {
|
|
171
|
+
const key = `${f.category}|${f.source}|${f.matchedText}`;
|
|
172
|
+
if (seen.has(key)) return false;
|
|
173
|
+
seen.add(key);
|
|
174
|
+
return true;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const highCount = uniqueFindings.filter((f) => f.severity === "high").length;
|
|
178
|
+
const medCount = uniqueFindings.filter((f) => f.severity === "medium").length;
|
|
179
|
+
const lowCount = uniqueFindings.filter((f) => f.severity === "low").length;
|
|
180
|
+
|
|
181
|
+
if (uniqueFindings.length === 0) {
|
|
182
|
+
return {
|
|
183
|
+
content: [{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: `No prompt injection patterns detected.\nScanned: ${pageContent.length} text regions (hidden: ${includeHidden})`,
|
|
186
|
+
}],
|
|
187
|
+
details: {
|
|
188
|
+
clean: true,
|
|
189
|
+
scannedRegions: pageContent.length,
|
|
190
|
+
includeHidden,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const findingLines = uniqueFindings.map((f) =>
|
|
196
|
+
` [${f.severity.toUpperCase()}] ${f.category} in ${f.source}${!f.visible ? " (HIDDEN)" : ""}: "${f.matchedText}"`,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: [{
|
|
201
|
+
type: "text",
|
|
202
|
+
text: `⚠️ Prompt injection patterns detected: ${uniqueFindings.length} finding(s)\nHigh: ${highCount} | Medium: ${medCount} | Low: ${lowCount}\n\n${findingLines.join("\n")}\n\n⚠️ This page may be attempting to manipulate the agent. Proceed with caution.`,
|
|
203
|
+
}],
|
|
204
|
+
details: {
|
|
205
|
+
clean: false,
|
|
206
|
+
findings: uniqueFindings,
|
|
207
|
+
counts: { high: highCount, medium: medCount, low: lowCount, total: uniqueFindings.length },
|
|
208
|
+
scannedRegions: pageContent.length,
|
|
209
|
+
includeHidden,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
} catch (err: any) {
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: "text", text: `Injection check failed: ${err.message}` }],
|
|
215
|
+
details: { error: err.message },
|
|
216
|
+
isError: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import type { ToolDeps } from "../state.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Network interception & mocking tools — mock API responses, block URLs, simulate errors.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface ActiveRoute {
|
|
10
|
+
id: number;
|
|
11
|
+
pattern: string;
|
|
12
|
+
type: "mock" | "block";
|
|
13
|
+
status?: number;
|
|
14
|
+
delay?: number;
|
|
15
|
+
description: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let nextRouteId = 1;
|
|
19
|
+
const activeRoutes: ActiveRoute[] = [];
|
|
20
|
+
const routeCleanups: Map<number, () => Promise<void>> = new Map();
|
|
21
|
+
|
|
22
|
+
export function registerNetworkMockTools(pi: ExtensionAPI, deps: ToolDeps): void {
|
|
23
|
+
// -------------------------------------------------------------------------
|
|
24
|
+
// browser_mock_route
|
|
25
|
+
// -------------------------------------------------------------------------
|
|
26
|
+
pi.registerTool({
|
|
27
|
+
name: "browser_mock_route",
|
|
28
|
+
label: "Browser Mock Route",
|
|
29
|
+
description:
|
|
30
|
+
"Intercept network requests matching a URL pattern and respond with custom status, body, and headers. " +
|
|
31
|
+
"Supports simulating slow responses via delay parameter. " +
|
|
32
|
+
"Routes survive page navigation within the same context. Use browser_clear_routes to remove all mocks.",
|
|
33
|
+
parameters: Type.Object({
|
|
34
|
+
url: Type.String({
|
|
35
|
+
description: "URL pattern to intercept. Supports glob patterns (e.g., '**/api/users*') or exact URLs.",
|
|
36
|
+
}),
|
|
37
|
+
status: Type.Optional(
|
|
38
|
+
Type.Number({ description: "HTTP status code for the mock response (default: 200)." }),
|
|
39
|
+
),
|
|
40
|
+
body: Type.Optional(
|
|
41
|
+
Type.String({ description: "Response body string. For JSON responses, pass a JSON string." }),
|
|
42
|
+
),
|
|
43
|
+
contentType: Type.Optional(
|
|
44
|
+
Type.String({ description: "Content-Type header (default: 'application/json' if body looks like JSON, else 'text/plain')." }),
|
|
45
|
+
),
|
|
46
|
+
headers: Type.Optional(
|
|
47
|
+
Type.Record(Type.String(), Type.String(), {
|
|
48
|
+
description: "Additional response headers as key-value pairs.",
|
|
49
|
+
}),
|
|
50
|
+
),
|
|
51
|
+
delay: Type.Optional(
|
|
52
|
+
Type.Number({ description: "Delay in milliseconds before sending the response. Simulates slow responses." }),
|
|
53
|
+
),
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
57
|
+
try {
|
|
58
|
+
const { page: p } = await deps.ensureBrowser();
|
|
59
|
+
const routeId = nextRouteId++;
|
|
60
|
+
|
|
61
|
+
const status = params.status ?? 200;
|
|
62
|
+
const body = params.body ?? "";
|
|
63
|
+
const delay = params.delay ?? 0;
|
|
64
|
+
|
|
65
|
+
// Auto-detect content type
|
|
66
|
+
let contentType = params.contentType;
|
|
67
|
+
if (!contentType) {
|
|
68
|
+
try {
|
|
69
|
+
JSON.parse(body);
|
|
70
|
+
contentType = "application/json";
|
|
71
|
+
} catch {
|
|
72
|
+
contentType = "text/plain";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const headers: Record<string, string> = {
|
|
77
|
+
"content-type": contentType,
|
|
78
|
+
"access-control-allow-origin": "*",
|
|
79
|
+
...(params.headers ?? {}),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handler = async (route: any) => {
|
|
83
|
+
if (delay > 0) {
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
85
|
+
}
|
|
86
|
+
await route.fulfill({
|
|
87
|
+
status,
|
|
88
|
+
body,
|
|
89
|
+
headers,
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
await p.route(params.url, handler);
|
|
94
|
+
|
|
95
|
+
const cleanup = async () => {
|
|
96
|
+
try {
|
|
97
|
+
await p.unroute(params.url, handler);
|
|
98
|
+
} catch {
|
|
99
|
+
// Page may be closed
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const routeInfo: ActiveRoute = {
|
|
104
|
+
id: routeId,
|
|
105
|
+
pattern: params.url,
|
|
106
|
+
type: "mock",
|
|
107
|
+
status,
|
|
108
|
+
delay: delay > 0 ? delay : undefined,
|
|
109
|
+
description: `Mock ${params.url} → ${status}${delay > 0 ? ` (${delay}ms delay)` : ""}`,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
activeRoutes.push(routeInfo);
|
|
113
|
+
routeCleanups.set(routeId, cleanup);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
content: [{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: `Route mocked: ${routeInfo.description}\nRoute ID: ${routeId}\nActive routes: ${activeRoutes.length}`,
|
|
119
|
+
}],
|
|
120
|
+
details: { routeId, ...routeInfo, activeRouteCount: activeRoutes.length },
|
|
121
|
+
};
|
|
122
|
+
} catch (err: any) {
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: "text", text: `Mock route failed: ${err.message}` }],
|
|
125
|
+
details: { error: err.message },
|
|
126
|
+
isError: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// -------------------------------------------------------------------------
|
|
133
|
+
// browser_block_urls
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
pi.registerTool({
|
|
136
|
+
name: "browser_block_urls",
|
|
137
|
+
label: "Browser Block URLs",
|
|
138
|
+
description:
|
|
139
|
+
"Block network requests matching URL patterns. Useful for blocking analytics, ads, or third-party scripts. " +
|
|
140
|
+
"Accepts glob patterns. Routes survive page navigation.",
|
|
141
|
+
parameters: Type.Object({
|
|
142
|
+
patterns: Type.Array(Type.String(), {
|
|
143
|
+
description: "URL patterns to block (glob syntax, e.g., ['**/analytics*', '**/ads*']).",
|
|
144
|
+
}),
|
|
145
|
+
}),
|
|
146
|
+
|
|
147
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
148
|
+
try {
|
|
149
|
+
const { page: p } = await deps.ensureBrowser();
|
|
150
|
+
const results: ActiveRoute[] = [];
|
|
151
|
+
|
|
152
|
+
for (const pattern of params.patterns) {
|
|
153
|
+
const routeId = nextRouteId++;
|
|
154
|
+
|
|
155
|
+
const handler = async (route: any) => {
|
|
156
|
+
await route.abort("blockedbyclient");
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
await p.route(pattern, handler);
|
|
160
|
+
|
|
161
|
+
const cleanup = async () => {
|
|
162
|
+
try {
|
|
163
|
+
await p.unroute(pattern, handler);
|
|
164
|
+
} catch {}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const routeInfo: ActiveRoute = {
|
|
168
|
+
id: routeId,
|
|
169
|
+
pattern,
|
|
170
|
+
type: "block",
|
|
171
|
+
description: `Block ${pattern}`,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
activeRoutes.push(routeInfo);
|
|
175
|
+
routeCleanups.set(routeId, cleanup);
|
|
176
|
+
results.push(routeInfo);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
content: [{
|
|
181
|
+
type: "text",
|
|
182
|
+
text: `Blocked ${results.length} URL pattern(s):\n${results.map((r) => ` - ${r.description} (ID: ${r.id})`).join("\n")}\nActive routes: ${activeRoutes.length}`,
|
|
183
|
+
}],
|
|
184
|
+
details: { blocked: results, activeRouteCount: activeRoutes.length },
|
|
185
|
+
};
|
|
186
|
+
} catch (err: any) {
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: "text", text: `Block URLs failed: ${err.message}` }],
|
|
189
|
+
details: { error: err.message },
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// -------------------------------------------------------------------------
|
|
197
|
+
// browser_clear_routes
|
|
198
|
+
// -------------------------------------------------------------------------
|
|
199
|
+
pi.registerTool({
|
|
200
|
+
name: "browser_clear_routes",
|
|
201
|
+
label: "Browser Clear Routes",
|
|
202
|
+
description:
|
|
203
|
+
"Remove all active route mocks and URL blocks. Also lists currently active routes if called with no routes active.",
|
|
204
|
+
parameters: Type.Object({}),
|
|
205
|
+
|
|
206
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
|
207
|
+
try {
|
|
208
|
+
await deps.ensureBrowser();
|
|
209
|
+
const count = activeRoutes.length;
|
|
210
|
+
|
|
211
|
+
if (count === 0) {
|
|
212
|
+
return {
|
|
213
|
+
content: [{ type: "text", text: "No active routes to clear." }],
|
|
214
|
+
details: { cleared: 0 },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const routeDescriptions = activeRoutes.map((r) => r.description);
|
|
219
|
+
|
|
220
|
+
// Clean up all routes
|
|
221
|
+
for (const [id, cleanup] of routeCleanups) {
|
|
222
|
+
await cleanup();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
activeRoutes.length = 0;
|
|
226
|
+
routeCleanups.clear();
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
content: [{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: `Cleared ${count} route(s):\n${routeDescriptions.map((d) => ` - ${d}`).join("\n")}`,
|
|
232
|
+
}],
|
|
233
|
+
details: { cleared: count, routes: routeDescriptions },
|
|
234
|
+
};
|
|
235
|
+
} catch (err: any) {
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: `Clear routes failed: ${err.message}` }],
|
|
238
|
+
details: { error: err.message },
|
|
239
|
+
isError: true,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import type { ToolDeps } from "../state.js";
|
|
4
|
+
|
|
5
|
+
export function registerPdfTools(pi: ExtensionAPI, deps: ToolDeps): void {
|
|
6
|
+
pi.registerTool({
|
|
7
|
+
name: "browser_save_pdf",
|
|
8
|
+
label: "Browser Save PDF",
|
|
9
|
+
description:
|
|
10
|
+
"Render current page as PDF artifact via Playwright's page.pdf(). " +
|
|
11
|
+
"Supports A4/Letter/custom page formats and optional background graphics. " +
|
|
12
|
+
"Writes to session artifacts directory. Chromium only.",
|
|
13
|
+
parameters: Type.Object({
|
|
14
|
+
filename: Type.Optional(
|
|
15
|
+
Type.String({ description: "Output filename (default: auto-generated from page title + timestamp)." }),
|
|
16
|
+
),
|
|
17
|
+
format: Type.Optional(
|
|
18
|
+
Type.String({
|
|
19
|
+
description:
|
|
20
|
+
"Page format: 'A4' (default), 'Letter', 'Legal', 'Tabloid', or custom like '8.5in x 11in'. " +
|
|
21
|
+
"Custom format uses CSS dimension syntax for width x height.",
|
|
22
|
+
}),
|
|
23
|
+
),
|
|
24
|
+
printBackground: Type.Optional(
|
|
25
|
+
Type.Boolean({ description: "Include background graphics (default: true)." }),
|
|
26
|
+
),
|
|
27
|
+
}),
|
|
28
|
+
|
|
29
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
30
|
+
try {
|
|
31
|
+
const { page: p } = await deps.ensureBrowser();
|
|
32
|
+
|
|
33
|
+
const url = p.url();
|
|
34
|
+
const title = await p.title().catch(() => "untitled");
|
|
35
|
+
|
|
36
|
+
// Resolve filename
|
|
37
|
+
const timestamp = deps.formatArtifactTimestamp(Date.now());
|
|
38
|
+
const safeName = deps.sanitizeArtifactName(params.filename || `${title}-${timestamp}`, `pdf-${timestamp}`);
|
|
39
|
+
const filename = safeName.endsWith(".pdf") ? safeName : `${safeName}.pdf`;
|
|
40
|
+
|
|
41
|
+
// Resolve format
|
|
42
|
+
const knownFormats = new Set(["A4", "Letter", "Legal", "Tabloid", "Ledger", "A0", "A1", "A2", "A3", "A5", "A6"]);
|
|
43
|
+
const formatInput = params.format ?? "A4";
|
|
44
|
+
let pdfOptions: Record<string, unknown> = {};
|
|
45
|
+
|
|
46
|
+
if (knownFormats.has(formatInput)) {
|
|
47
|
+
pdfOptions.format = formatInput;
|
|
48
|
+
} else {
|
|
49
|
+
// Custom format: parse "WIDTHin x HEIGHTin" or "WIDTHcm x HEIGHTcm" etc.
|
|
50
|
+
const customMatch = formatInput.match(/^(.+?)\s*[xX×]\s*(.+)$/);
|
|
51
|
+
if (customMatch) {
|
|
52
|
+
pdfOptions.width = customMatch[1]!.trim();
|
|
53
|
+
pdfOptions.height = customMatch[2]!.trim();
|
|
54
|
+
} else {
|
|
55
|
+
pdfOptions.format = "A4"; // fallback
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pdfOptions.printBackground = params.printBackground ?? true;
|
|
60
|
+
|
|
61
|
+
// Generate PDF
|
|
62
|
+
await deps.ensureSessionArtifactDir();
|
|
63
|
+
const outputPath = deps.buildSessionArtifactPath(filename);
|
|
64
|
+
pdfOptions.path = outputPath;
|
|
65
|
+
|
|
66
|
+
await p.pdf(pdfOptions as any);
|
|
67
|
+
|
|
68
|
+
// Read file size
|
|
69
|
+
const { stat } = await import("node:fs/promises");
|
|
70
|
+
const fileStat = await stat(outputPath);
|
|
71
|
+
const sizeBytes = fileStat.size;
|
|
72
|
+
const sizeKB = (sizeBytes / 1024).toFixed(1);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `PDF saved: ${outputPath}\nSize: ${sizeKB} KB\nFormat: ${formatInput}\nPage: ${title}\nURL: ${url}`,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
details: { path: outputPath, sizeBytes, format: formatInput, pageUrl: url, pageTitle: title },
|
|
82
|
+
};
|
|
83
|
+
} catch (err: any) {
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: `PDF generation failed: ${err.message}` }],
|
|
86
|
+
details: { error: err.message },
|
|
87
|
+
isError: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|