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.
Files changed (228) hide show
  1. package/README.md +25 -1
  2. package/dist/cli.js +74 -7
  3. package/dist/headless.d.ts +25 -0
  4. package/dist/headless.js +454 -0
  5. package/dist/help-text.js +47 -0
  6. package/dist/mcp-server.d.ts +20 -3
  7. package/dist/mcp-server.js +21 -1
  8. package/dist/models-resolver.d.ts +32 -0
  9. package/dist/models-resolver.js +50 -0
  10. package/dist/resource-loader.js +64 -9
  11. package/dist/resources/extensions/bg-shell/output-formatter.ts +36 -16
  12. package/dist/resources/extensions/bg-shell/process-manager.ts +6 -4
  13. package/dist/resources/extensions/bg-shell/types.ts +33 -1
  14. package/dist/resources/extensions/browser-tools/capture.ts +18 -16
  15. package/dist/resources/extensions/browser-tools/index.ts +20 -0
  16. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
  17. package/dist/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
  18. package/dist/resources/extensions/browser-tools/tools/codegen.ts +274 -0
  19. package/dist/resources/extensions/browser-tools/tools/device.ts +183 -0
  20. package/dist/resources/extensions/browser-tools/tools/extract.ts +229 -0
  21. package/dist/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
  22. package/dist/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
  23. package/dist/resources/extensions/browser-tools/tools/pdf.ts +92 -0
  24. package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
  25. package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
  26. package/dist/resources/extensions/browser-tools/tools/zoom.ts +104 -0
  27. package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -0
  28. package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
  29. package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
  30. package/dist/resources/extensions/gsd/auto-recovery.ts +51 -2
  31. package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
  32. package/dist/resources/extensions/gsd/auto.ts +560 -52
  33. package/dist/resources/extensions/gsd/captures.ts +49 -0
  34. package/dist/resources/extensions/gsd/commands.ts +194 -11
  35. package/dist/resources/extensions/gsd/complexity.ts +1 -0
  36. package/dist/resources/extensions/gsd/dashboard-overlay.ts +54 -2
  37. package/dist/resources/extensions/gsd/diff-context.ts +73 -80
  38. package/dist/resources/extensions/gsd/doctor.ts +76 -12
  39. package/dist/resources/extensions/gsd/exit-command.ts +2 -2
  40. package/dist/resources/extensions/gsd/forensics.ts +95 -52
  41. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  42. package/dist/resources/extensions/gsd/guided-flow.ts +85 -5
  43. package/dist/resources/extensions/gsd/index.ts +34 -1
  44. package/dist/resources/extensions/gsd/mcp-server.ts +33 -12
  45. package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  46. package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
  47. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  48. package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  49. package/dist/resources/extensions/gsd/preferences.ts +65 -1
  50. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  51. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -0
  52. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
  53. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
  54. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  55. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  56. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
  57. package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
  58. package/dist/resources/extensions/gsd/roadmap-slices.ts +41 -1
  59. package/dist/resources/extensions/gsd/session-forensics.ts +36 -2
  60. package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
  61. package/dist/resources/extensions/gsd/state.ts +72 -30
  62. package/dist/resources/extensions/gsd/templates/milestone-validation.md +62 -0
  63. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  64. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  65. package/dist/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
  66. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  67. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
  68. package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
  69. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  70. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  71. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  72. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  73. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  74. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  75. package/dist/resources/extensions/gsd/tests/doctor.test.ts +58 -0
  76. package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
  77. package/dist/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
  78. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  79. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  80. package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  81. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  82. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  83. package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
  84. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
  85. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
  86. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  87. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
  88. package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  89. package/dist/resources/extensions/gsd/triage-resolution.ts +83 -0
  90. package/dist/resources/extensions/gsd/types.ts +15 -1
  91. package/dist/resources/extensions/gsd/visualizer-overlay.ts +8 -1
  92. package/dist/resources/extensions/gsd/workspace-index.ts +34 -6
  93. package/dist/resources/extensions/subagent/index.ts +5 -0
  94. package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
  95. package/dist/update-check.d.ts +9 -0
  96. package/dist/update-check.js +97 -0
  97. package/package.json +6 -1
  98. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  99. package/packages/pi-ai/dist/providers/anthropic.js +16 -7
  100. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  101. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  102. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
  103. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
  106. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
  109. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  110. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  111. package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
  112. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic.ts +21 -8
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
  115. package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
  116. package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
  117. package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
  118. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  119. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  121. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts +10 -0
  123. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js +79 -0
  125. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +18 -0
  127. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/tools/bash.js +77 -1
  129. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
  131. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
  133. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  135. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/index.js +1 -1
  137. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  138. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  139. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  140. package/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +91 -0
  141. package/packages/pi-coding-agent/src/core/tools/bash.ts +83 -1
  142. package/packages/pi-coding-agent/src/core/tools/index.ts +1 -0
  143. package/packages/pi-coding-agent/src/index.ts +1 -0
  144. package/scripts/postinstall.js +7 -109
  145. package/src/resources/extensions/bg-shell/output-formatter.ts +36 -16
  146. package/src/resources/extensions/bg-shell/process-manager.ts +6 -4
  147. package/src/resources/extensions/bg-shell/types.ts +33 -1
  148. package/src/resources/extensions/browser-tools/capture.ts +18 -16
  149. package/src/resources/extensions/browser-tools/index.ts +20 -0
  150. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
  151. package/src/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
  152. package/src/resources/extensions/browser-tools/tools/codegen.ts +274 -0
  153. package/src/resources/extensions/browser-tools/tools/device.ts +183 -0
  154. package/src/resources/extensions/browser-tools/tools/extract.ts +229 -0
  155. package/src/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
  156. package/src/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
  157. package/src/resources/extensions/browser-tools/tools/pdf.ts +92 -0
  158. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
  159. package/src/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
  160. package/src/resources/extensions/browser-tools/tools/zoom.ts +104 -0
  161. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
  162. package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
  163. package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
  164. package/src/resources/extensions/gsd/auto-recovery.ts +51 -2
  165. package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
  166. package/src/resources/extensions/gsd/auto.ts +560 -52
  167. package/src/resources/extensions/gsd/captures.ts +49 -0
  168. package/src/resources/extensions/gsd/commands.ts +194 -11
  169. package/src/resources/extensions/gsd/complexity.ts +1 -0
  170. package/src/resources/extensions/gsd/dashboard-overlay.ts +54 -2
  171. package/src/resources/extensions/gsd/diff-context.ts +73 -80
  172. package/src/resources/extensions/gsd/doctor.ts +76 -12
  173. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  174. package/src/resources/extensions/gsd/forensics.ts +95 -52
  175. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  176. package/src/resources/extensions/gsd/guided-flow.ts +85 -5
  177. package/src/resources/extensions/gsd/index.ts +34 -1
  178. package/src/resources/extensions/gsd/mcp-server.ts +33 -12
  179. package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  180. package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
  181. package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  182. package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  183. package/src/resources/extensions/gsd/preferences.ts +65 -1
  184. package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  185. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -0
  186. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
  188. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  190. package/src/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
  191. package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
  192. package/src/resources/extensions/gsd/roadmap-slices.ts +41 -1
  193. package/src/resources/extensions/gsd/session-forensics.ts +36 -2
  194. package/src/resources/extensions/gsd/session-status-io.ts +197 -0
  195. package/src/resources/extensions/gsd/state.ts +72 -30
  196. package/src/resources/extensions/gsd/templates/milestone-validation.md +62 -0
  197. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  198. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  199. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
  200. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  201. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
  202. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
  203. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  204. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  205. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  206. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  207. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  208. package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  209. package/src/resources/extensions/gsd/tests/doctor.test.ts +58 -0
  210. package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
  211. package/src/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
  212. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  213. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  214. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  215. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  216. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  217. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
  218. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
  219. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
  220. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  221. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
  222. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  223. package/src/resources/extensions/gsd/triage-resolution.ts +83 -0
  224. package/src/resources/extensions/gsd/types.ts +15 -1
  225. package/src/resources/extensions/gsd/visualizer-overlay.ts +8 -1
  226. package/src/resources/extensions/gsd/workspace-index.ts +34 -6
  227. package/src/resources/extensions/subagent/index.ts +5 -0
  228. 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
+ }