@wrongstack/tools 0.286.0 → 0.287.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 (54) hide show
  1. package/README.md +2 -2
  2. package/dist/_edit-match.d.ts.map +1 -1
  3. package/dist/batch-tool-use.d.ts +1 -1
  4. package/dist/batch-tool-use.d.ts.map +1 -1
  5. package/dist/batch-tool-use.js +26 -8
  6. package/dist/batch-tool-use.js.map +2 -2
  7. package/dist/browser/index.js +1 -1
  8. package/dist/browser/index.js.map +1 -1
  9. package/dist/browser/types.d.ts +1 -1
  10. package/dist/browser/types.d.ts.map +1 -1
  11. package/dist/builtin.js +544 -118
  12. package/dist/builtin.js.map +4 -4
  13. package/dist/codebase-index/go-parser.d.ts.map +1 -1
  14. package/dist/codebase-index/index.js +190 -96
  15. package/dist/codebase-index/index.js.map +4 -4
  16. package/dist/codebase-index/py-parser.d.ts.map +1 -1
  17. package/dist/codebase-index/rs-parser.d.ts.map +1 -1
  18. package/dist/codebase-index/worker.js +188 -94
  19. package/dist/codebase-index/worker.js.map +4 -4
  20. package/dist/edit.d.ts.map +1 -1
  21. package/dist/edit.js +36 -12
  22. package/dist/edit.js.map +3 -3
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +818 -125
  26. package/dist/index.js.map +4 -4
  27. package/dist/kanban.d.ts +7 -2
  28. package/dist/kanban.d.ts.map +1 -1
  29. package/dist/kanban.js +158 -19
  30. package/dist/kanban.js.map +4 -4
  31. package/dist/next-steps.d.ts +6 -2
  32. package/dist/next-steps.d.ts.map +1 -1
  33. package/dist/next-steps.js +3 -3
  34. package/dist/next-steps.js.map +2 -2
  35. package/dist/pack.js +544 -118
  36. package/dist/pack.js.map +4 -4
  37. package/dist/plan.d.ts.map +1 -1
  38. package/dist/plan.js +154 -4
  39. package/dist/plan.js.map +4 -4
  40. package/dist/session-kanban.d.ts +38 -0
  41. package/dist/session-kanban.d.ts.map +1 -0
  42. package/dist/session-kanban.js +489 -0
  43. package/dist/session-kanban.js.map +7 -0
  44. package/dist/task.d.ts.map +1 -1
  45. package/dist/task.js +163 -4
  46. package/dist/task.js.map +4 -4
  47. package/dist/todo.d.ts.map +1 -1
  48. package/dist/todo.js +155 -7
  49. package/dist/todo.js.map +4 -4
  50. package/dist/tool-icon-map.d.ts.map +1 -1
  51. package/dist/tool-icons.d.ts.map +1 -1
  52. package/dist/tool-icons.js +7 -1
  53. package/dist/tool-icons.js.map +2 -2
  54. package/package.json +7 -3
package/README.md CHANGED
@@ -69,10 +69,10 @@ execution remain separate permission-gated steps.
69
69
  | `typecheck` | `auto` | Project-aware typechecker |
70
70
  | `test` | `confirm` | Project-aware test runner |
71
71
  | `install` | `confirm` | Package manager install |
72
- | `audit` | `auto` | Dependency vuln audit |
72
+ | `audit` | `confirm` | Dependency vulnerability audit |
73
73
  | `outdated` | `auto` | List outdated dependencies |
74
74
  | `scaffold` | `confirm` | Template-based scaffolding |
75
- | `document` | `confirm` | Inline JSDoc generation |
75
+ | `document` | `auto` | Deprecated read-only documentation-candidate preview; use plugin tool `auto_doc` for generation |
76
76
  | `logs` | `auto` | Tail logs with rolling 100k-line window |
77
77
 
78
78
  ### Agent control
@@ -1 +1 @@
1
- {"version":3,"file":"_edit-match.d.ts","sourceRoot":"","sources":["../src/_edit-match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,qBAAqB,GAAG,uBAAuB,GAAG,OAAO,CAAC;AAE5F,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjC;AAED,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMhD,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAKlF,CAAC;AAYF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CA+BzF;AA6GD,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAUvD;AAyBD,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAkBrC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CA0B/C"}
1
+ {"version":3,"file":"_edit-match.d.ts","sourceRoot":"","sources":["../src/_edit-match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,qBAAqB,GAAG,uBAAuB,GAAG,OAAO,CAAC;AAE5F,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjC;AAED,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMhD,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAKlF,CAAC;AAYF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CA+CzF;AA4HD,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAUvD;AAyBD,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMpD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAkBrC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CA0B/C"}
@@ -1,4 +1,4 @@
1
- import type { Tool } from '@wrongstack/core';
1
+ import { type Tool } from '@wrongstack/core';
2
2
  interface BatchToolUseInput {
3
3
  calls: {
4
4
  tool: string;
@@ -1 +1 @@
1
- {"version":3,"file":"batch-tool-use.d.ts","sourceRoot":"","sources":["../src/batch-tool-use.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,UAAU,iBAAiB;IACzB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAChC,EAAE,CAAC;IACJ,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED,UAAU,kBAAkB;IAC1B,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;KACrB,EAAE,CAAC;IACJ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,gBAAgB,EAAE,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,CAoFxE,CAAC"}
1
+ {"version":3,"file":"batch-tool-use.d.ts","sourceRoot":"","sources":["../src/batch-tool-use.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,IAAI,EACV,MAAM,kBAAkB,CAAC;AAE1B,UAAU,iBAAiB;IACzB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAChC,EAAE,CAAC;IACJ,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED,UAAU,kBAAkB;IAC1B,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;KACrB,EAAE,CAAC;IACJ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,gBAAgB,EAAE,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,CAsGxE,CAAC"}
@@ -1,4 +1,7 @@
1
1
  // src/batch-tool-use.ts
2
+ import {
3
+ GOVERNED_TOOL_EXECUTOR_META_KEY
4
+ } from "@wrongstack/core";
2
5
  var batchToolUseTool = {
3
6
  name: "batch_tool_use",
4
7
  category: "Meta",
@@ -35,7 +38,7 @@ var batchToolUseTool = {
35
38
  },
36
39
  required: ["calls"]
37
40
  },
38
- async execute(input, ctx, opts) {
41
+ async execute(input, ctx, _opts) {
39
42
  if (!input?.calls || input.calls.length === 0) {
40
43
  return {
41
44
  results: [],
@@ -45,18 +48,33 @@ var batchToolUseTool = {
45
48
  stop_on_error: false
46
49
  };
47
50
  }
51
+ const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
52
+ if (typeof governedExecute !== "function") {
53
+ return {
54
+ results: input.calls.map((call) => ({
55
+ tool: call.tool,
56
+ success: false,
57
+ error: "governed nested execution is unavailable; call the tool directly",
58
+ executionMs: 0
59
+ })),
60
+ total: input.calls.length,
61
+ succeeded: 0,
62
+ failed: input.calls.length,
63
+ stop_on_error: input.stop_on_error ?? false
64
+ };
65
+ }
48
66
  const results = [];
49
67
  let succeeded = 0;
50
68
  let failed = 0;
51
69
  if (input.parallel !== false) {
52
- const promises = input.calls.map(async (call) => executeSingle(call, ctx, opts));
70
+ const promises = input.calls.map(async (call) => executeSingle(call, ctx, governedExecute));
53
71
  const allResults = await Promise.all(promises);
54
72
  results.push(...allResults);
55
73
  succeeded = allResults.filter((r) => r.success).length;
56
74
  failed = allResults.filter((r) => !r.success).length;
57
75
  } else {
58
76
  for (const call of input.calls) {
59
- const result = await executeSingle(call, ctx, opts ?? { signal: void 0 });
77
+ const result = await executeSingle(call, ctx, governedExecute);
60
78
  results.push(result);
61
79
  if (result.success) {
62
80
  succeeded++;
@@ -75,9 +93,9 @@ var batchToolUseTool = {
75
93
  };
76
94
  }
77
95
  };
78
- async function executeSingle(call, ctx, opts) {
96
+ async function executeSingle(call, ctx, governedExecute) {
79
97
  const start = Date.now();
80
- const tool = ctx.tools.find((t) => t.name === call.tool);
98
+ const tool = ctx.tools.find((candidate) => candidate.name === call.tool);
81
99
  if (!tool) {
82
100
  return {
83
101
  tool: call.tool,
@@ -87,11 +105,11 @@ async function executeSingle(call, ctx, opts) {
87
105
  };
88
106
  }
89
107
  try {
90
- const result = await tool.execute(call.input, ctx, opts);
108
+ const result = await governedExecute(call.tool, call.input);
91
109
  return {
92
110
  tool: call.tool,
93
- success: true,
94
- result,
111
+ success: result.success,
112
+ ...result.success ? { result: result.result } : { error: result.error ?? "nested tool failed" },
95
113
  executionMs: Date.now() - start
96
114
  };
97
115
  } catch (e) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/batch-tool-use.ts"],
4
- "sourcesContent": ["import type { Tool } from '@wrongstack/core';\n\ninterface BatchToolUseInput {\n calls: {\n tool: string;\n input: Record<string, unknown>;\n }[];\n stop_on_error?: boolean | undefined;\n parallel?: boolean | undefined;\n}\n\ninterface BatchToolUseOutput {\n results: {\n tool: string;\n success: boolean;\n result?: unknown | undefined;\n error?: string | undefined;\n executionMs: number;\n }[];\n total: number;\n succeeded: number;\n failed: number;\n stop_on_error: boolean;\n}\n\nexport const batchToolUseTool: Tool<BatchToolUseInput, BatchToolUseOutput> = {\n name: 'batch_tool_use',\n category: 'Meta',\n description:\n 'Execute a batch of tool calls either sequentially or in parallel. Returns structured results for every call.',\n usageHint:\n 'ADVANCED / POWER USER TOOL:\\n\\n' +\n '- Useful when you have a clear list of independent operations to perform.\\n' +\n '- `parallel: true` (default) runs them concurrently for speed.\\n' +\n '- `stop_on_error: true` makes it fail fast on the first error.\\n' +\n 'Use with care \u2014 batching many mutating operations can be risky. Prefer explicit sequential steps for important work.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: 120_000,\n capabilities: ['tool.mutate.any'],\n icon: 'meta',\n inputSchema: {\n type: 'object',\n properties: {\n calls: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n tool: { type: 'string' },\n input: { type: 'object' },\n },\n required: ['tool'],\n },\n description: 'Array of tool calls to execute',\n },\n stop_on_error: {\n type: 'boolean',\n description: 'Stop execution on first error (default: false)',\n },\n parallel: {\n type: 'boolean',\n description: 'Execute calls in parallel (default: true)',\n },\n },\n required: ['calls'],\n },\n async execute(input, ctx, opts) {\n if (!input?.calls || input.calls.length === 0) {\n return {\n results: [],\n total: 0,\n succeeded: 0,\n failed: 0,\n stop_on_error: false,\n };\n }\n\n const results: BatchToolUseOutput['results'] = [];\n let succeeded = 0;\n let failed = 0;\n\n if (input.parallel !== false) {\n const promises = input.calls.map(async (call) => executeSingle(call, ctx, opts));\n const allResults = await Promise.all(promises);\n results.push(...allResults);\n succeeded = allResults.filter((r) => r.success).length;\n failed = allResults.filter((r) => !r.success).length;\n } else {\n for (const call of input.calls) {\n const result = await executeSingle(call, ctx, opts ?? { signal: undefined as never as AbortSignal });\n results.push(result);\n if (result.success) {\n succeeded++;\n } else {\n failed++;\n if (input.stop_on_error) break;\n }\n }\n }\n\n return {\n results,\n total: input.calls.length,\n succeeded,\n failed,\n stop_on_error: input.stop_on_error ?? false,\n };\n },\n};\n\nasync function executeSingle(\n call: { tool: string; input: Record<string, unknown> },\n ctx: import('@wrongstack/core').Context,\n opts: { signal: AbortSignal },\n): Promise<BatchToolUseOutput['results'][0]> {\n const start = Date.now();\n const tool = ctx.tools.find((t: Tool) => t.name === call.tool);\n\n if (!tool) {\n return {\n tool: call.tool,\n success: false,\n error: `tool \"${call.tool}\" not found`,\n executionMs: Date.now() - start,\n };\n }\n\n try {\n const result = await tool.execute(call.input, ctx, opts);\n return {\n tool: call.tool,\n success: true,\n result,\n executionMs: Date.now() - start,\n };\n } catch (e) {\n return {\n tool: call.tool,\n success: false,\n error: e instanceof Error ? e.message : String(e),\n executionMs: Date.now() - start,\n };\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n GOVERNED_TOOL_EXECUTOR_META_KEY,\n type GovernedToolExecutor,\n type Tool,\n} from '@wrongstack/core';\n\ninterface BatchToolUseInput {\n calls: {\n tool: string;\n input: Record<string, unknown>;\n }[];\n stop_on_error?: boolean | undefined;\n parallel?: boolean | undefined;\n}\n\ninterface BatchToolUseOutput {\n results: {\n tool: string;\n success: boolean;\n result?: unknown | undefined;\n error?: string | undefined;\n executionMs: number;\n }[];\n total: number;\n succeeded: number;\n failed: number;\n stop_on_error: boolean;\n}\n\nexport const batchToolUseTool: Tool<BatchToolUseInput, BatchToolUseOutput> = {\n name: 'batch_tool_use',\n category: 'Meta',\n description:\n 'Execute a batch of tool calls either sequentially or in parallel. Returns structured results for every call.',\n usageHint:\n 'ADVANCED / POWER USER TOOL:\\n\\n' +\n '- Useful when you have a clear list of independent operations to perform.\\n' +\n '- `parallel: true` (default) runs them concurrently for speed.\\n' +\n '- `stop_on_error: true` makes it fail fast on the first error.\\n' +\n 'Use with care \u2014 batching many mutating operations can be risky. Prefer explicit sequential steps for important work.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: 120_000,\n capabilities: ['tool.mutate.any'],\n icon: 'meta',\n inputSchema: {\n type: 'object',\n properties: {\n calls: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n tool: { type: 'string' },\n input: { type: 'object' },\n },\n required: ['tool'],\n },\n description: 'Array of tool calls to execute',\n },\n stop_on_error: {\n type: 'boolean',\n description: 'Stop execution on first error (default: false)',\n },\n parallel: {\n type: 'boolean',\n description: 'Execute calls in parallel (default: true)',\n },\n },\n required: ['calls'],\n },\n async execute(input, ctx, _opts) {\n if (!input?.calls || input.calls.length === 0) {\n return {\n results: [],\n total: 0,\n succeeded: 0,\n failed: 0,\n stop_on_error: false,\n };\n }\n\n const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY] as\n | GovernedToolExecutor\n | undefined;\n if (typeof governedExecute !== 'function') {\n return {\n results: input.calls.map((call) => ({\n tool: call.tool,\n success: false,\n error: 'governed nested execution is unavailable; call the tool directly',\n executionMs: 0,\n })),\n total: input.calls.length,\n succeeded: 0,\n failed: input.calls.length,\n stop_on_error: input.stop_on_error ?? false,\n };\n }\n\n const results: BatchToolUseOutput['results'] = [];\n let succeeded = 0;\n let failed = 0;\n\n if (input.parallel !== false) {\n const promises = input.calls.map(async (call) => executeSingle(call, ctx, governedExecute));\n const allResults = await Promise.all(promises);\n results.push(...allResults);\n succeeded = allResults.filter((r) => r.success).length;\n failed = allResults.filter((r) => !r.success).length;\n } else {\n for (const call of input.calls) {\n const result = await executeSingle(call, ctx, governedExecute);\n results.push(result);\n if (result.success) {\n succeeded++;\n } else {\n failed++;\n if (input.stop_on_error) break;\n }\n }\n }\n\n return {\n results,\n total: input.calls.length,\n succeeded,\n failed,\n stop_on_error: input.stop_on_error ?? false,\n };\n },\n};\n\nasync function executeSingle(\n call: { tool: string; input: Record<string, unknown> },\n ctx: import('@wrongstack/core').Context,\n governedExecute: GovernedToolExecutor,\n): Promise<BatchToolUseOutput['results'][0]> {\n const start = Date.now();\n const tool = ctx.tools.find((candidate: Tool) => candidate.name === call.tool);\n\n if (!tool) {\n return {\n tool: call.tool,\n success: false,\n error: `tool \"${call.tool}\" not found`,\n executionMs: Date.now() - start,\n };\n }\n\n try {\n const result = await governedExecute(call.tool, call.input);\n return {\n tool: call.tool,\n success: result.success,\n ...(result.success ? { result: result.result } : { error: result.error ?? 'nested tool failed' }),\n executionMs: Date.now() - start,\n };\n } catch (e) {\n return {\n tool: call.tool,\n success: false,\n error: e instanceof Error ? e.message : String(e),\n executionMs: Date.now() - start,\n };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -390,7 +390,7 @@ var BrowserSessionManager = class {
390
390
  constructor(options, launcher = defaultLauncher) {
391
391
  this.launcher = launcher;
392
392
  this.artifacts = new BrowserArtifactStore(options.artifactRoot);
393
- this.allowPrivateHosts = options.allowPrivateHosts ?? false;
393
+ this.allowPrivateHosts = options.allowPrivateHosts ?? true;
394
394
  this.allowedPrivateOrigins = options.allowedPrivateOrigins ?? [];
395
395
  this.networkProxy = new BrowserNetworkGuardProxy({
396
396
  allowPrivateHosts: this.allowPrivateHosts,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/browser/artifacts.ts", "../../src/browser/manager.ts", "../../src/browser/network-guard-proxy.ts", "../../src/browser/security.ts", "../../src/browser/tools.ts"],
4
- "sourcesContent": ["import { createHash } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, ulid } from '@wrongstack/core/utils';\nimport type { BrowserArtifact, BrowserArtifactKind } from './types.js';\n\nexport class BrowserArtifactStore {\n constructor(private readonly root: string) {}\n\n async write(\n sessionId: string,\n kind: BrowserArtifactKind,\n extension: string,\n mimeType: string,\n content: Uint8Array,\n ): Promise<BrowserArtifact> {\n const id = ulid();\n const dir = path.join(this.root, safeSegment(sessionId));\n const target = path.join(dir, `${id}.${extension.replace(/^\\./, '')}`);\n await atomicWrite(target, content, { mode: 0o600 });\n return this.record(id, sessionId, kind, target, mimeType);\n }\n\n private async record(\n id: string,\n sessionId: string,\n kind: BrowserArtifactKind,\n target: string,\n mimeType: string,\n ): Promise<BrowserArtifact> {\n await fs.chmod(target, 0o600).catch(() => undefined);\n const [stat, sha256] = await Promise.all([fs.stat(target), hashFile(target)]);\n const artifact: BrowserArtifact = {\n id,\n kind,\n sensitivity: 'sensitive',\n path: target,\n mimeType,\n sizeBytes: stat.size,\n sha256,\n createdAt: new Date().toISOString(),\n };\n const metadataPath = path.join(path.dirname(target), `${id}.metadata.json`);\n try {\n await atomicWrite(\n metadataPath,\n `${JSON.stringify({\n id: artifact.id,\n sessionId: safeSegment(sessionId),\n kind: artifact.kind,\n sensitivity: artifact.sensitivity,\n mimeType: artifact.mimeType,\n sizeBytes: artifact.sizeBytes,\n sha256: artifact.sha256,\n createdAt: artifact.createdAt,\n })}\\n`,\n { mode: 0o600 },\n );\n } catch (error) {\n await fs.rm(target, { force: true }).catch(() => undefined);\n throw error;\n }\n return artifact;\n }\n\n pathFor(sessionId: string, extension: string): { id: string; path: string } {\n const id = ulid();\n return {\n id,\n path: path.join(this.root, safeSegment(sessionId), `${id}.${extension.replace(/^\\./, '')}`),\n };\n }\n\n async describe(\n id: string,\n kind: BrowserArtifactKind,\n target: string,\n mimeType: string,\n ): Promise<BrowserArtifact> {\n return this.record(id, path.basename(path.dirname(target)), kind, target, mimeType);\n }\n}\n\nasync function hashFile(target: string): Promise<string> {\n const hash = createHash('sha256');\n await new Promise<void>((resolve, reject) => {\n const stream = createReadStream(target);\n stream.on('data', (chunk) => hash.update(chunk));\n stream.once('error', reject);\n stream.once('end', resolve);\n });\n return hash.digest('hex');\n}\n\nfunction safeSegment(value: string): string {\n return value.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 80);\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { Browser, BrowserContext, Page } from '@playwright/test';\nimport { ulid } from '@wrongstack/core/utils';\nimport { BrowserArtifactStore } from './artifacts.js';\nimport { BrowserNetworkGuardProxy } from './network-guard-proxy.js';\nimport { assertBrowserUrlAllowed, redactBrowserText, safeBrowserUrl } from './security.js';\nimport type {\n BrowserArtifact,\n BrowserConsoleEntry,\n BrowserManagerOptions,\n BrowserNetworkEntry,\n BrowserOpenOptions,\n BrowserSessionSummary,\n BrowserSnapshot,\n} from './types.js';\n\nconst DEFAULT_OPERATION_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_SNAPSHOT_CHARS = 64 * 1024;\nconst DEFAULT_MAX_ENTRIES = 100;\n\ninterface LiveSession {\n id: string;\n ownerId: string;\n context: BrowserContext;\n page: Page;\n createdAt: string;\n lastUsedAt: string;\n tracing: boolean;\n console: BrowserConsoleEntry[];\n network: BrowserNetworkEntry[];\n}\n\nexport type BrowserLauncher = (headless: boolean) => Promise<Browser>;\n\nexport class BrowserSessionManager {\n private readonly sessions = new Map<string, LiveSession>();\n private readonly artifacts: BrowserArtifactStore;\n private readonly allowPrivateHosts: boolean;\n private readonly allowedPrivateOrigins: readonly string[];\n private readonly networkProxy: BrowserNetworkGuardProxy;\n private readonly headless: boolean;\n private readonly operationTimeoutMs: number;\n private readonly maxSnapshotChars: number;\n private readonly maxConsoleEntries: number;\n private readonly maxNetworkEntries: number;\n private browser?: Browser | undefined;\n private launching?: Promise<Browser> | undefined;\n\n constructor(\n options: BrowserManagerOptions,\n private readonly launcher: BrowserLauncher = defaultLauncher,\n ) {\n this.artifacts = new BrowserArtifactStore(options.artifactRoot);\n this.allowPrivateHosts = options.allowPrivateHosts ?? false;\n this.allowedPrivateOrigins = options.allowedPrivateOrigins ?? [];\n this.networkProxy = new BrowserNetworkGuardProxy({\n allowPrivateHosts: this.allowPrivateHosts,\n allowedPrivateOrigins: this.allowedPrivateOrigins,\n });\n this.headless = options.headless ?? true;\n this.operationTimeoutMs = options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;\n this.maxSnapshotChars = options.maxSnapshotChars ?? DEFAULT_MAX_SNAPSHOT_CHARS;\n this.maxConsoleEntries = options.maxConsoleEntries ?? DEFAULT_MAX_ENTRIES;\n this.maxNetworkEntries = options.maxNetworkEntries ?? DEFAULT_MAX_ENTRIES;\n }\n\n async open(\n ownerId: string,\n input: BrowserOpenOptions,\n signal: AbortSignal,\n ): Promise<BrowserSessionSummary> {\n signal.throwIfAborted();\n if (input.url) {\n await assertBrowserUrlAllowed(input.url, {\n allowPrivateHosts: this.allowPrivateHosts,\n allowedPrivateOrigins: this.allowedPrivateOrigins,\n navigation: true,\n });\n }\n const browser = await this.ensureBrowser(signal);\n const proxyServer = await this.networkProxy.start();\n const context = await abortable(\n signal,\n async () => {\n const created = await browser.newContext({\n acceptDownloads: false,\n ignoreHTTPSErrors: false,\n serviceWorkers: 'block',\n proxy: { server: proxyServer },\n viewport: input.viewport ?? { width: 1440, height: 900 },\n });\n if (signal.aborted) {\n await created.close().catch(() => undefined);\n signal.throwIfAborted();\n }\n return created;\n },\n () => undefined,\n );\n const session: LiveSession = {\n id: ulid(),\n ownerId,\n context,\n page: await context.newPage(),\n createdAt: new Date().toISOString(),\n lastUsedAt: new Date().toISOString(),\n tracing: input.trace ?? true,\n console: [],\n network: [],\n };\n this.sessions.set(session.id, session);\n this.attachEvidenceCollectors(session);\n await context.route('**/*', async (route) => {\n try {\n await assertBrowserUrlAllowed(route.request().url(), {\n allowPrivateHosts: this.allowPrivateHosts,\n allowedPrivateOrigins: this.allowedPrivateOrigins,\n navigation: false,\n });\n await route.continue();\n } catch {\n this.pushNetwork(session, {\n method: route.request().method(),\n url: safeBrowserUrl(route.request().url()),\n failed: true,\n at: new Date().toISOString(),\n });\n await route.abort('blockedbyclient');\n }\n });\n try {\n if (session.tracing) {\n await context.tracing.start({ screenshots: true, snapshots: true, sources: false });\n }\n if (input.url) await this.navigate(session.id, ownerId, input.url, signal);\n return await this.summary(session);\n } catch (err) {\n await context.close().catch(() => undefined);\n this.sessions.delete(session.id);\n await this.closeBrowserIfIdle();\n throw err;\n }\n }\n\n async list(ownerId: string): Promise<BrowserSessionSummary[]> {\n const owned = [...this.sessions.values()].filter((session) => session.ownerId === ownerId);\n return Promise.all(owned.map((session) => this.summary(session)));\n }\n\n async navigate(\n id: string,\n ownerId: string,\n rawUrl: string,\n signal: AbortSignal,\n ): Promise<BrowserSessionSummary> {\n await assertBrowserUrlAllowed(rawUrl, {\n allowPrivateHosts: this.allowPrivateHosts,\n allowedPrivateOrigins: this.allowedPrivateOrigins,\n navigation: true,\n });\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.goto(rawUrl, {\n waitUntil: 'domcontentloaded',\n timeout: this.operationTimeoutMs,\n }),\n );\n return this.summary(session);\n }\n\n async snapshot(id: string, ownerId: string, signal: AbortSignal): Promise<BrowserSnapshot> {\n const session = this.requireOwned(id, ownerId);\n const raw = await this.runPageOperation(session, signal, async () => {\n try {\n return await session.page\n .locator('body')\n .ariaSnapshot({ timeout: this.operationTimeoutMs });\n } catch {\n return await session.page.locator('body').innerText({ timeout: this.operationTimeoutMs });\n }\n });\n const aria = redactBrowserText(raw);\n const truncated = aria.length > this.maxSnapshotChars;\n return {\n session: await this.summary(session),\n aria: truncated ? `${aria.slice(0, this.maxSnapshotChars)}\\n\u2026[snapshot truncated]` : aria,\n console: session.console.map((entry) => ({ ...entry })),\n network: session.network.map((entry) => ({ ...entry })),\n truncated,\n };\n }\n\n async screenshot(\n id: string,\n ownerId: string,\n input: { fullPage?: boolean | undefined; selector?: string | undefined },\n signal: AbortSignal,\n ): Promise<BrowserArtifact> {\n const session = this.requireOwned(id, ownerId);\n const bytes = await this.runPageOperation(session, signal, () =>\n input.selector\n ? session.page\n .locator(input.selector)\n .screenshot({ type: 'png', timeout: this.operationTimeoutMs })\n : session.page.screenshot({ type: 'png', fullPage: input.fullPage ?? false }),\n );\n signal.throwIfAborted();\n return this.artifacts.write(session.id, 'screenshot', 'png', 'image/png', bytes);\n }\n\n async click(id: string, ownerId: string, selector: string, signal: AbortSignal): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).click({ timeout: this.operationTimeoutMs }),\n );\n }\n\n async type(\n id: string,\n ownerId: string,\n selector: string,\n text: string,\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).fill(text, { timeout: this.operationTimeoutMs }),\n );\n }\n\n async select(\n id: string,\n ownerId: string,\n selector: string,\n value: string,\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).selectOption(value, { timeout: this.operationTimeoutMs }),\n );\n }\n\n async press(id: string, ownerId: string, key: string, signal: AbortSignal): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () => session.page.keyboard.press(key));\n }\n\n async hover(id: string, ownerId: string, selector: string, signal: AbortSignal): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).hover({ timeout: this.operationTimeoutMs }),\n );\n }\n\n async wait(\n id: string,\n ownerId: string,\n input: { selector?: string | undefined; timeoutMs?: number | undefined },\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n const timeout = Math.min(\n Math.max(input.timeoutMs ?? this.operationTimeoutMs, 0),\n this.operationTimeoutMs,\n );\n await this.runPageOperation(session, signal, () =>\n input.selector\n ? session.page.locator(input.selector).waitFor({ state: 'visible', timeout })\n : session.page.waitForTimeout(timeout),\n );\n }\n\n async drag(\n id: string,\n ownerId: string,\n from: string,\n to: string,\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(from).dragTo(session.page.locator(to), {\n timeout: this.operationTimeoutMs,\n }),\n );\n }\n\n async upload(\n id: string,\n ownerId: string,\n selector: string,\n files: string[],\n projectRoot: string,\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n const root = path.resolve(projectRoot);\n const realRoot = await fs.realpath(root);\n const resolved: string[] = [];\n for (const file of files) {\n const absolute = path.resolve(root, file);\n const relative = path.relative(root, absolute);\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('browser: upload files must stay inside the project root');\n }\n const realFile = await fs.realpath(absolute);\n const realRelative = path.relative(realRoot, realFile);\n if (realRelative.startsWith('..') || path.isAbsolute(realRelative)) {\n throw new Error('browser: upload files must not escape the project root through a symlink');\n }\n const stat = await fs.stat(realFile);\n if (!stat.isFile()) throw new Error(`browser: upload target is not a file: ${file}`);\n resolved.push(realFile);\n }\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).setInputFiles(resolved, { timeout: this.operationTimeoutMs }),\n );\n }\n\n async evaluate(\n id: string,\n ownerId: string,\n expression: string,\n signal: AbortSignal,\n ): Promise<unknown> {\n const session = this.requireOwned(id, ownerId);\n const result = await this.runPageOperation(session, signal, () =>\n session.page.evaluate(expression),\n );\n const serialized = JSON.stringify(result);\n if (serialized && serialized.length > this.maxSnapshotChars) {\n throw new Error(`browser: evaluation result exceeds ${this.maxSnapshotChars} characters`);\n }\n return result;\n }\n\n async close(id: string, ownerId: string): Promise<{ trace?: BrowserArtifact | undefined }> {\n const session = this.requireOwned(id, ownerId);\n let trace: BrowserArtifact | undefined;\n try {\n if (session.tracing) {\n const target = this.artifacts.pathFor(session.id, 'zip');\n await fs.mkdir(path.dirname(target.path), { recursive: true });\n await session.context.tracing.stop({ path: target.path });\n trace = await this.artifacts.describe(target.id, 'trace', target.path, 'application/zip');\n }\n } finally {\n await session.context.close().catch(() => undefined);\n this.sessions.delete(id);\n await this.closeBrowserIfIdle();\n }\n return trace ? { trace } : {};\n }\n\n async closeOwner(ownerId: string): Promise<void> {\n const ids = [...this.sessions.values()]\n .filter((session) => session.ownerId === ownerId)\n .map((session) => session.id);\n for (const id of ids) await this.close(id, ownerId).catch(() => undefined);\n }\n\n async dispose(): Promise<void> {\n for (const session of [...this.sessions.values()]) {\n await session.context.close().catch(() => undefined);\n }\n this.sessions.clear();\n const browser = this.browser;\n this.browser = undefined;\n this.launching = undefined;\n await browser?.close().catch(() => undefined);\n await this.networkProxy.close();\n }\n\n private async ensureBrowser(signal: AbortSignal): Promise<Browser> {\n if (this.browser?.isConnected()) return this.browser;\n if (!this.launching) {\n this.launching = this.launcher(this.headless).then((browser) => {\n this.browser = browser;\n browser.on('disconnected', () => {\n if (this.browser === browser) this.browser = undefined;\n for (const [id, session] of this.sessions) {\n if (session.context.browser() === browser) this.sessions.delete(id);\n }\n });\n return browser;\n });\n }\n try {\n return await abortable(\n signal,\n () => this.launching!,\n async () => {\n const browser = await this.launching?.catch(() => undefined);\n await browser?.close().catch(() => undefined);\n },\n );\n } finally {\n this.launching = undefined;\n }\n }\n\n private requireOwned(id: string, ownerId: string): LiveSession {\n const session = this.sessions.get(id);\n if (!session) throw new Error(`browser: unknown session \"${id}\"`);\n if (session.ownerId !== ownerId) throw new Error('browser: session belongs to another agent');\n session.lastUsedAt = new Date().toISOString();\n return session;\n }\n\n private async summary(session: LiveSession): Promise<BrowserSessionSummary> {\n return {\n id: session.id,\n ownerId: session.ownerId,\n url: safeBrowserUrl(session.page.url()),\n title: redactBrowserText(await session.page.title().catch(() => '')),\n createdAt: session.createdAt,\n lastUsedAt: session.lastUsedAt,\n tracing: session.tracing,\n };\n }\n\n private attachEvidenceCollectors(session: LiveSession): void {\n session.page.on('console', (message) => {\n pushBounded(\n session.console,\n {\n level: message.type(),\n text: redactBrowserText(message.text()).slice(0, 4_096),\n at: new Date().toISOString(),\n },\n this.maxConsoleEntries,\n );\n });\n session.page.on('response', (response) => {\n this.pushNetwork(session, {\n method: response.request().method(),\n url: safeBrowserUrl(response.url()),\n status: response.status(),\n at: new Date().toISOString(),\n });\n });\n session.page.on('requestfailed', (request) => {\n this.pushNetwork(session, {\n method: request.method(),\n url: safeBrowserUrl(request.url()),\n failed: true,\n at: new Date().toISOString(),\n });\n });\n }\n\n private pushNetwork(session: LiveSession, entry: BrowserNetworkEntry): void {\n pushBounded(session.network, entry, this.maxNetworkEntries);\n }\n\n private async runPageOperation<T>(\n session: LiveSession,\n signal: AbortSignal,\n operation: () => Promise<T>,\n ): Promise<T> {\n signal.throwIfAborted();\n session.lastUsedAt = new Date().toISOString();\n return abortable(signal, operation, async () => {\n await session.context.close().catch(() => undefined);\n this.sessions.delete(session.id);\n await this.closeBrowserIfIdle();\n });\n }\n\n private async closeBrowserIfIdle(): Promise<void> {\n if (this.sessions.size > 0 || !this.browser) return;\n const browser = this.browser;\n this.browser = undefined;\n await browser.close().catch(() => undefined);\n }\n}\n\nasync function defaultLauncher(headless: boolean): Promise<Browser> {\n try {\n const { chromium } = await import('@playwright/test');\n return await chromium.launch({ headless });\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(\n `browser: Playwright Chromium is unavailable (${detail}). Run \"pnpm exec playwright install chromium\".`,\n );\n }\n}\n\nexport async function browserInstallationDiagnostics(): Promise<{\n available: boolean;\n executablePath?: string | undefined;\n message: string;\n}> {\n try {\n const { chromium } = await import('@playwright/test');\n const executablePath = chromium.executablePath();\n await fs.access(executablePath);\n return { available: true, executablePath, message: 'Playwright Chromium is available.' };\n } catch {\n return {\n available: false,\n message: 'Playwright Chromium is unavailable. Run \"pnpm exec playwright install chromium\".',\n };\n }\n}\n\nfunction pushBounded<T>(target: T[], value: T, limit: number): void {\n target.push(value);\n if (target.length > limit) target.splice(0, target.length - limit);\n}\n\nasync function abortable<T>(\n signal: AbortSignal,\n operation: () => Promise<T>,\n onAbort: () => void | Promise<void>,\n): Promise<T> {\n signal.throwIfAborted();\n return new Promise<T>((resolve, reject) => {\n let settled = false;\n let aborting = false;\n const finish = (fn: () => void) => {\n if (settled) return;\n settled = true;\n signal.removeEventListener('abort', abort);\n fn();\n };\n const abort = () => {\n aborting = true;\n void Promise.resolve(onAbort()).finally(() => {\n finish(() => reject(signal.reason instanceof Error ? signal.reason : new Error('Aborted')));\n });\n };\n signal.addEventListener('abort', abort, { once: true });\n operation().then(\n (value) => {\n if (!aborting) finish(() => resolve(value));\n },\n (err) => {\n if (!aborting) finish(() => reject(err));\n },\n );\n });\n}\n", "import * as http from 'node:http';\nimport * as net from 'node:net';\nimport type { Duplex } from 'node:stream';\nimport type { BrowserDnsLookup } from './security.js';\nimport { resolvePinnedBrowserTarget } from './security.js';\n\nexport interface BrowserNetworkGuardProxyOptions {\n allowPrivateHosts?: boolean | undefined;\n allowedPrivateOrigins?: readonly string[] | undefined;\n lookup?: BrowserDnsLookup | undefined;\n connectTimeoutMs?: number | undefined;\n}\n\n/** Local HTTP proxy that pins every browser socket to its validated DNS answer. */\nexport class BrowserNetworkGuardProxy {\n private readonly server: http.Server;\n private readonly sockets = new Set<net.Socket>();\n private startPromise?: Promise<string> | undefined;\n private url?: string | undefined;\n\n constructor(private readonly options: BrowserNetworkGuardProxyOptions = {}) {\n this.server = http.createServer((request, response) => {\n void this.forwardHttp(request, response);\n });\n this.server.on('connect', (request, socket, head) => {\n void this.forwardConnect(request, socket, head);\n });\n this.server.on('upgrade', (request, socket, head) => {\n void this.forwardUpgrade(request, socket, head);\n });\n this.server.on('connection', (socket) => {\n this.sockets.add(socket);\n socket.once('close', () => this.sockets.delete(socket));\n });\n this.server.on('clientError', (_error, socket) => {\n socket.end('HTTP/1.1 400 Bad Request\\r\\nConnection: close\\r\\n\\r\\n');\n });\n }\n\n async start(): Promise<string> {\n if (this.url) return this.url;\n if (this.startPromise) return this.startPromise;\n this.startPromise = new Promise<string>((resolve, reject) => {\n const onError = (error: Error) => {\n this.server.off('listening', onListening);\n reject(error);\n };\n const onListening = () => {\n this.server.off('error', onError);\n const address = this.server.address();\n if (!address || typeof address === 'string') {\n reject(new Error('browser: network guard proxy failed to bind'));\n return;\n }\n this.url = `http://127.0.0.1:${address.port}`;\n resolve(this.url);\n };\n this.server.once('error', onError);\n this.server.once('listening', onListening);\n this.server.listen(0, '127.0.0.1');\n }).finally(() => {\n this.startPromise = undefined;\n });\n return this.startPromise;\n }\n\n async close(): Promise<void> {\n this.url = undefined;\n for (const socket of this.sockets) socket.destroy();\n this.sockets.clear();\n if (!this.server.listening) return;\n await new Promise<void>((resolve) => this.server.close(() => resolve()));\n }\n\n private async forwardHttp(\n request: http.IncomingMessage,\n response: http.ServerResponse,\n ): Promise<void> {\n try {\n const target = await this.resolve(request.url ?? '');\n if (target.url.protocol !== 'http:') throw new Error('browser: HTTPS must use CONNECT');\n const headers = sanitizeHeaders(request.headers);\n headers.host = target.url.host;\n const upstream = http.request({\n host: target.address,\n family: target.family,\n port: Number(target.url.port || 80),\n method: request.method,\n path: `${target.url.pathname}${target.url.search}`,\n headers,\n agent: false,\n });\n upstream.setTimeout(this.options.connectTimeoutMs ?? 30_000, () => {\n upstream.destroy(new Error('browser: upstream connection timed out'));\n });\n upstream.on('response', (upstreamResponse) => {\n response.writeHead(\n upstreamResponse.statusCode ?? 502,\n sanitizeHeaders(upstreamResponse.headers),\n );\n upstreamResponse.pipe(response);\n });\n upstream.on('error', () => writeProxyError(response, 502, 'Bad Gateway'));\n request.on('aborted', () => upstream.destroy());\n request.pipe(upstream);\n } catch {\n writeProxyError(response, 403, 'Blocked by browser network policy');\n }\n }\n\n private async forwardConnect(\n request: http.IncomingMessage,\n client: Duplex,\n head: Buffer,\n ): Promise<void> {\n try {\n const authority = request.url ?? '';\n const target = await this.resolve(`https://${authority}`);\n const upstream = net.connect({\n host: target.address,\n family: target.family,\n port: Number(target.url.port || 443),\n });\n upstream.setTimeout(this.options.connectTimeoutMs ?? 30_000, () => upstream.destroy());\n upstream.once('connect', () => {\n client.write('HTTP/1.1 200 Connection Established\\r\\n\\r\\n');\n if (head.length > 0) upstream.write(head);\n upstream.pipe(client);\n client.pipe(upstream);\n });\n upstream.once('error', () => {\n client.end('HTTP/1.1 502 Bad Gateway\\r\\nConnection: close\\r\\n\\r\\n');\n });\n client.once('close', () => upstream.destroy());\n } catch {\n client.end('HTTP/1.1 403 Forbidden\\r\\nConnection: close\\r\\n\\r\\n');\n }\n }\n\n private async forwardUpgrade(\n request: http.IncomingMessage,\n client: Duplex,\n head: Buffer,\n ): Promise<void> {\n let upstream: http.ClientRequest | undefined;\n try {\n const rawUrl = request.url ?? '';\n const websocketUrl = new URL(rawUrl);\n if (websocketUrl.protocol !== 'ws:') {\n throw new Error('browser: only plain ws upgrades are handled by the HTTP proxy');\n }\n websocketUrl.protocol = 'http:';\n const target = await this.resolve(websocketUrl.toString());\n const headers = sanitizeHeaders(request.headers, true);\n headers.host = target.url.host;\n upstream = http.request({\n host: target.address,\n family: target.family,\n port: Number(target.url.port || 80),\n method: request.method,\n path: `${target.url.pathname}${target.url.search}`,\n headers,\n agent: false,\n });\n upstream.once('upgrade', (response, upstreamSocket, upstreamHead) => {\n writeRawResponseHead(client, response);\n if (head.length > 0) upstreamSocket.write(head);\n if (upstreamHead.length > 0) client.write(upstreamHead);\n upstreamSocket.pipe(client);\n client.pipe(upstreamSocket);\n client.once('close', () => upstreamSocket.destroy());\n });\n upstream.once('response', (response) => {\n writeRawResponseHead(client, response);\n response.pipe(client);\n });\n upstream.once('error', () => {\n client.end('HTTP/1.1 502 Bad Gateway\\r\\nConnection: close\\r\\n\\r\\n');\n });\n client.once('close', () => upstream?.destroy());\n upstream.end();\n } catch {\n upstream?.destroy();\n client.end('HTTP/1.1 403 Forbidden\\r\\nConnection: close\\r\\n\\r\\n');\n }\n }\n\n private resolve(rawUrl: string) {\n return resolvePinnedBrowserTarget(rawUrl, {\n allowPrivateHosts: this.options.allowPrivateHosts,\n allowedPrivateOrigins: this.options.allowedPrivateOrigins,\n lookup: this.options.lookup,\n });\n }\n}\n\nfunction sanitizeHeaders(\n headers: http.IncomingHttpHeaders,\n preserveUpgrade = false,\n): http.OutgoingHttpHeaders {\n const sanitized: http.OutgoingHttpHeaders = { ...headers };\n delete sanitized['proxy-authorization'];\n delete sanitized['proxy-connection'];\n if (!preserveUpgrade) delete sanitized.connection;\n return sanitized;\n}\n\nfunction writeRawResponseHead(client: Duplex, response: http.IncomingMessage): void {\n const status = response.statusCode ?? 502;\n const statusText = response.statusMessage ?? 'Bad Gateway';\n const lines = [`HTTP/1.1 ${status} ${statusText}`];\n for (let index = 0; index < response.rawHeaders.length; index += 2) {\n lines.push(`${response.rawHeaders[index]}: ${response.rawHeaders[index + 1]}`);\n }\n client.write(`${lines.join('\\r\\n')}\\r\\n\\r\\n`);\n}\n\nfunction writeProxyError(response: http.ServerResponse, status: number, message: string): void {\n if (response.headersSent || response.destroyed) return;\n response.writeHead(status, { 'content-type': 'text/plain', connection: 'close' });\n response.end(message);\n}\n", "import * as dns from 'node:dns/promises';\nimport * as net from 'node:net';\nimport { assertNotPrivateHost, isPrivateIPv4, isPrivateIPv6 } from '@wrongstack/core/utils';\n\nconst SAFE_SUBRESOURCE_PROTOCOLS = new Set(['about:', 'blob:', 'data:']);\n\nexport async function assertBrowserUrlAllowed(\n rawUrl: string,\n opts: {\n allowPrivateHosts?: boolean | undefined;\n allowedPrivateOrigins?: readonly string[] | undefined;\n navigation?: boolean | undefined;\n },\n): Promise<URL> {\n const url = parseBrowserUrl(rawUrl, opts.navigation ?? true);\n if (!opts.navigation && SAFE_SUBRESOURCE_PROTOCOLS.has(url.protocol)) return url;\n if (!opts.allowPrivateHosts && !isAllowedPrivateOrigin(url, opts.allowedPrivateOrigins)) {\n await assertNotPrivateHost(url.hostname);\n }\n return url;\n}\n\nexport interface BrowserResolvedTarget {\n url: URL;\n address: string;\n family: 4 | 6;\n}\n\nexport type BrowserDnsLookup = (\n hostname: string,\n) => Promise<readonly { address: string; family: number }[]>;\n\n/**\n * Resolve once, validate every answer, and return the exact address a socket\n * must use. Callers must connect to `address`, never resolve `url.hostname`\n * again, otherwise DNS rebinding remains possible.\n */\nexport async function resolvePinnedBrowserTarget(\n rawUrl: string,\n opts: {\n allowPrivateHosts?: boolean | undefined;\n allowedPrivateOrigins?: readonly string[] | undefined;\n lookup?: BrowserDnsLookup | undefined;\n } = {},\n): Promise<BrowserResolvedTarget> {\n const url = parseBrowserUrl(rawUrl, true);\n const allowPrivate =\n opts.allowPrivateHosts === true || isAllowedPrivateOrigin(url, opts.allowedPrivateOrigins);\n const hostname = unbracket(url.hostname);\n const literalFamily = net.isIP(hostname);\n if (literalFamily === 4 || literalFamily === 6) {\n if (!allowPrivate && isPrivateAddress(hostname, literalFamily)) {\n throw new Error(`browser: blocked private/loopback address \"${hostname}\"`);\n }\n return { url, address: hostname, family: literalFamily };\n }\n if ((hostname === 'localhost' || hostname.endsWith('.localhost')) && !allowPrivate) {\n throw new Error('browser: blocked localhost target');\n }\n\n const lookup = opts.lookup ?? ((host) => dns.lookup(host, { all: true }));\n let records: readonly { address: string; family: number }[];\n try {\n records = await lookup(hostname);\n } catch (error) {\n throw new Error(\n `browser: DNS resolution failed for \"${hostname}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n if (records.length === 0) throw new Error(`browser: DNS returned no addresses for \"${hostname}\"`);\n\n for (const record of records) {\n if (record.family !== 4 && record.family !== 6) {\n throw new Error(`browser: DNS returned an unsupported address family for \"${hostname}\"`);\n }\n if (!allowPrivate && isPrivateAddress(record.address, record.family)) {\n throw new Error(`browser: resolved to private address ${record.address}`);\n }\n }\n const selected = records[0]!;\n return { url, address: selected.address, family: selected.family as 4 | 6 };\n}\n\nexport function parsePrivateOriginAllowlist(raw: string | undefined): string[] {\n if (!raw?.trim()) return [];\n const origins = new Set<string>();\n for (const entry of raw.split(',')) {\n const candidate = entry.trim();\n if (!candidate) continue;\n const url = parseBrowserUrl(candidate, true);\n if (url.pathname !== '/' || url.search || url.hash) {\n throw new Error(`browser: private origin allowlist entry must be an origin: \"${candidate}\"`);\n }\n origins.add(url.origin);\n }\n return [...origins];\n}\n\nfunction parseBrowserUrl(rawUrl: string, navigation: boolean): URL {\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n throw new Error('browser: url must be an absolute URL');\n }\n if (!navigation && SAFE_SUBRESOURCE_PROTOCOLS.has(url.protocol)) return url;\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error(`browser: unsupported protocol \"${url.protocol}\"`);\n }\n if (url.username || url.password) {\n throw new Error('browser: credentials in URLs are not allowed');\n }\n return url;\n}\n\nfunction isAllowedPrivateOrigin(url: URL, origins: readonly string[] | undefined): boolean {\n return origins?.includes(url.origin) ?? false;\n}\n\nfunction isPrivateAddress(address: string, family: 4 | 6): boolean {\n return family === 4 ? isPrivateIPv4(address) : isPrivateIPv6(address);\n}\n\nfunction unbracket(hostname: string): string {\n return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n}\n\n/** Remove credentials, query strings, and fragments before persistence/output. */\nexport function safeBrowserUrl(rawUrl: string): string {\n try {\n const url = new URL(rawUrl);\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.toString();\n } catch {\n return 'about:blank';\n }\n}\n\nexport function redactBrowserText(text: string): string {\n return text\n .replace(/\\bBearer\\s+[A-Za-z0-9._~+/-]+=*/gi, 'Bearer [REDACTED]')\n .replace(\n /\\b(api[-_ ]?key|access[-_ ]?token|auth[-_ ]?token|token|password|secret)\\b\\s*[:=]\\s*[^\\s,;]+/gi,\n '$1=[REDACTED]',\n );\n}\n", "import * as path from 'node:path';\nimport { type Context, type Tool, ToolCapabilities } from '@wrongstack/core';\nimport { resolveWstackPaths } from '@wrongstack/core/utils';\nimport { BrowserSessionManager, browserInstallationDiagnostics } from './manager.js';\nimport { parsePrivateOriginAllowlist } from './security.js';\n\nconst managers = new Map<string, BrowserSessionManager>();\nconst cleanupSignalByContext = new WeakMap<object, AbortSignal>();\n\nfunction managerFor(ctx: Context): BrowserSessionManager {\n const root = path.resolve(ctx.projectRoot);\n let manager = managers.get(root);\n if (!manager) {\n const wpaths = resolveWstackPaths({ projectRoot: root });\n manager = new BrowserSessionManager({\n artifactRoot: path.join(wpaths.projectDir, 'browser-artifacts'),\n allowedPrivateOrigins: parsePrivateOriginAllowlist(\n process.env['WRONGSTACK_BROWSER_PRIVATE_ORIGINS'],\n ),\n });\n managers.set(root, manager);\n }\n const runSignal = ctx.signal;\n if (cleanupSignalByContext.get(ctx) !== runSignal) {\n cleanupSignalByContext.set(ctx, runSignal);\n const managerForRun = manager;\n const ownerForRun = owner(ctx);\n ctx.registerAbortHook?.(async () => {\n if (cleanupSignalByContext.get(ctx) === runSignal) {\n cleanupSignalByContext.delete(ctx);\n }\n await managerForRun.closeOwner(ownerForRun);\n });\n }\n return manager;\n}\n\nfunction owner(ctx: Context): string {\n return ctx.agentId || 'leader';\n}\n\nfunction json(value: unknown): string {\n return JSON.stringify(value, null, 2);\n}\n\nconst sessionIdSchema = {\n type: 'string',\n minLength: 1,\n description: 'Browser session id returned by browser_open.',\n} as const;\n\nexport const browserOpenTool: Tool<{\n url?: string | undefined;\n width?: number | undefined;\n height?: number | undefined;\n trace?: boolean | undefined;\n}> = {\n name: 'browser_open',\n description:\n 'Open an isolated first-party Playwright browser session, optionally navigating to a URL.',\n usageHint: 'browser_open({ url?, width?, height?, trace? })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n subjectKey: 'url',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'Optional absolute http(s) URL.' },\n width: { type: 'integer', minimum: 320, maximum: 3840 },\n height: { type: 'integer', minimum: 240, maximum: 2160 },\n trace: { type: 'boolean', description: 'Capture a Playwright trace; defaults to true.' },\n },\n },\n async execute(input, ctx, opts) {\n const viewport =\n input.width || input.height\n ? { width: input.width ?? 1440, height: input.height ?? 900 }\n : undefined;\n return json(\n await managerFor(ctx).open(\n owner(ctx),\n { url: input.url, viewport, trace: input.trace },\n opts.signal,\n ),\n );\n },\n};\n\nexport const browserListTool: Tool<Record<string, never>> = {\n name: 'browser_list',\n description:\n 'List browser sessions owned by the current agent without exposing other agents sessions.',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: { type: 'object', properties: {} },\n async execute(_input, ctx) {\n return json(await managerFor(ctx).list(owner(ctx)));\n },\n};\n\nexport const browserStatusTool: Tool<Record<string, never>> = {\n name: 'browser_status',\n description: 'Check whether first-party Playwright Chromium is installed and ready to launch.',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.TOOL_META],\n inputSchema: { type: 'object', properties: {} },\n async execute() {\n return json(await browserInstallationDiagnostics());\n },\n};\n\nexport const browserNavigateTool: Tool<{ sessionId: string; url: string }> = {\n name: 'browser_navigate',\n description: 'Navigate an owned browser session to an approved http(s) URL.',\n usageHint: 'browser_navigate({ sessionId, url })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n subjectKey: 'url',\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema, url: { type: 'string', minLength: 1 } },\n required: ['sessionId', 'url'],\n },\n async execute(input, ctx, opts) {\n return json(\n await managerFor(ctx).navigate(input.sessionId, owner(ctx), input.url, opts.signal),\n );\n },\n};\n\nexport const browserSnapshotTool: Tool<{ sessionId: string }> = {\n name: 'browser_snapshot',\n description: 'Return bounded accessibility state plus redacted console and network summaries.',\n usageHint: 'browser_snapshot({ sessionId })',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema },\n required: ['sessionId'],\n },\n async execute(input, ctx, opts) {\n return json(await managerFor(ctx).snapshot(input.sessionId, owner(ctx), opts.signal));\n },\n};\n\nexport const browserScreenshotTool: Tool<{\n sessionId: string;\n fullPage?: boolean | undefined;\n selector?: string | undefined;\n}> = {\n name: 'browser_screenshot',\n description:\n 'Capture a page or element PNG and return sensitive artifact metadata with integrity hash.',\n usageHint: 'browser_screenshot({ sessionId, fullPage?, selector? })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n fullPage: { type: 'boolean' },\n selector: { type: 'string', minLength: 1 },\n },\n required: ['sessionId'],\n },\n async execute(input, ctx, opts) {\n return json(\n await managerFor(ctx).screenshot(\n input.sessionId,\n owner(ctx),\n { fullPage: input.fullPage, selector: input.selector },\n opts.signal,\n ),\n );\n },\n};\n\ntype SelectorInput = { sessionId: string; selector: string };\n\nfunction selectorTool(\n name: string,\n description: string,\n run: (\n manager: BrowserSessionManager,\n input: SelectorInput,\n ownerId: string,\n signal: AbortSignal,\n ) => Promise<void>,\n): Tool<SelectorInput> {\n return {\n name,\n description,\n usageHint: `${name}({ sessionId, selector })`,\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema, selector: { type: 'string', minLength: 1 } },\n required: ['sessionId', 'selector'],\n },\n async execute(input, ctx, opts) {\n await run(managerFor(ctx), input, owner(ctx), opts.signal);\n return 'ok';\n },\n };\n}\n\nexport const browserClickTool = selectorTool(\n 'browser_click',\n 'Click an element in an owned browser session.',\n (manager, input, ownerId, signal) =>\n manager.click(input.sessionId, ownerId, input.selector, signal),\n);\n\nexport const browserHoverTool = selectorTool(\n 'browser_hover',\n 'Hover an element in an owned browser session.',\n (manager, input, ownerId, signal) =>\n manager.hover(input.sessionId, ownerId, input.selector, signal),\n);\n\nexport const browserTypeTool: Tool<\n SelectorInput & { text?: string | undefined; secretEnv?: string | undefined }\n> = {\n name: 'browser_type',\n description:\n 'Fill an input in an owned browser session. Use secretEnv instead of text for credentials so values stay out of tool arguments and session audit.',\n usageHint: 'browser_type({ sessionId, selector, text? | secretEnv? })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n selector: { type: 'string', minLength: 1 },\n text: { type: 'string' },\n secretEnv: {\n type: 'string',\n pattern: '^[A-Z_][A-Z0-9_]*$',\n description: 'Host environment variable resolved only at execution time.',\n },\n },\n required: ['sessionId', 'selector'],\n },\n validate(input) {\n return (input.text === undefined) === (input.secretEnv === undefined)\n ? ['exactly one of text or secretEnv is required']\n : [];\n },\n async execute(input, ctx, opts) {\n const value = input.text ?? (input.secretEnv ? process.env[input.secretEnv] : undefined);\n if (value === undefined) {\n throw new Error(`browser: secret environment variable \"${input.secretEnv ?? ''}\" is not set`);\n }\n await managerFor(ctx).type(input.sessionId, owner(ctx), input.selector, value, opts.signal);\n return 'ok';\n },\n};\n\nexport const browserSelectTool: Tool<SelectorInput & { value: string }> = {\n name: 'browser_select',\n description: 'Select an option in an owned browser session.',\n usageHint: 'browser_select({ sessionId, selector, value })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n selector: { type: 'string', minLength: 1 },\n value: { type: 'string' },\n },\n required: ['sessionId', 'selector', 'value'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).select(\n input.sessionId,\n owner(ctx),\n input.selector,\n input.value,\n opts.signal,\n );\n return 'ok';\n },\n};\n\nexport const browserPressTool: Tool<{ sessionId: string; key: string }> = {\n name: 'browser_press',\n description: 'Press a keyboard key in an owned browser session.',\n usageHint: 'browser_press({ sessionId, key })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema, key: { type: 'string', minLength: 1 } },\n required: ['sessionId', 'key'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).press(input.sessionId, owner(ctx), input.key, opts.signal);\n return 'ok';\n },\n};\n\nexport const browserDragTool: Tool<{ sessionId: string; from: string; to: string }> = {\n name: 'browser_drag',\n description: 'Drag one element to another in an owned browser session.',\n usageHint: 'browser_drag({ sessionId, from, to })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n from: { type: 'string', minLength: 1 },\n to: { type: 'string', minLength: 1 },\n },\n required: ['sessionId', 'from', 'to'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).drag(input.sessionId, owner(ctx), input.from, input.to, opts.signal);\n return 'ok';\n },\n};\n\nexport const browserWaitTool: Tool<{\n sessionId: string;\n selector?: string | undefined;\n timeoutMs?: number | undefined;\n}> = {\n name: 'browser_wait',\n description: 'Wait for an element or a bounded duration in an owned browser session.',\n usageHint: 'browser_wait({ sessionId, selector?, timeoutMs? })',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n selector: { type: 'string', minLength: 1 },\n timeoutMs: { type: 'integer', minimum: 0, maximum: 30000 },\n },\n required: ['sessionId'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).wait(\n input.sessionId,\n owner(ctx),\n { selector: input.selector, timeoutMs: input.timeoutMs },\n opts.signal,\n );\n return 'ok';\n },\n};\n\nexport const browserEvaluateTool: Tool<{ sessionId: string; expression: string }> = {\n name: 'browser_evaluate',\n description:\n 'Evaluate bounded JavaScript in the page. Requires confirmation because page code is arbitrary.',\n usageHint: 'browser_evaluate({ sessionId, expression })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'destructive',\n capabilities: [ToolCapabilities.NET_OUTBOUND, ToolCapabilities.TOOL_MUTATE_ANY],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n expression: { type: 'string', minLength: 1, maxLength: 16384 },\n },\n required: ['sessionId', 'expression'],\n },\n async execute(input, ctx, opts) {\n return json(\n await managerFor(ctx).evaluate(input.sessionId, owner(ctx), input.expression, opts.signal),\n );\n },\n};\n\nexport const browserUploadTool: Tool<SelectorInput & { files: string[] }> = {\n name: 'browser_upload',\n description: 'Upload project-local files through a file input in an owned browser session.',\n usageHint: 'browser_upload({ sessionId, selector, files })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'destructive',\n capabilities: [ToolCapabilities.NET_OUTBOUND, ToolCapabilities.FS_READ],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n selector: { type: 'string', minLength: 1 },\n files: { type: 'array', minItems: 1, maxItems: 20, items: { type: 'string', minLength: 1 } },\n },\n required: ['sessionId', 'selector', 'files'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).upload(\n input.sessionId,\n owner(ctx),\n input.selector,\n input.files,\n ctx.projectRoot,\n opts.signal,\n );\n return 'ok';\n },\n};\n\nexport const browserCloseTool: Tool<{ sessionId: string }> = {\n name: 'browser_close',\n description:\n 'Close an owned browser session, reclaim its context, and return sensitive trace metadata.',\n usageHint: 'browser_close({ sessionId })',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema },\n required: ['sessionId'],\n },\n async execute(input, ctx) {\n return json(await managerFor(ctx).close(input.sessionId, owner(ctx)));\n },\n};\n\nexport const browserTools: Tool[] = [\n browserOpenTool,\n browserStatusTool,\n browserListTool,\n browserNavigateTool,\n browserSnapshotTool,\n browserScreenshotTool,\n browserClickTool,\n browserTypeTool,\n browserSelectTool,\n browserPressTool,\n browserHoverTool,\n browserDragTool,\n browserWaitTool,\n browserEvaluateTool,\n browserUploadTool,\n browserCloseTool,\n];\n\nfor (const tool of browserTools) tool.icon = 'web';\nfor (const tool of browserTools) tool.timeoutMs ??= 60_000;\n\nexport async function shutdownBrowserTools(): Promise<void> {\n const active = [...managers.values()];\n managers.clear();\n await Promise.all(active.map((manager) => manager.dispose()));\n}\n"],
4
+ "sourcesContent": ["import { createHash } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, ulid } from '@wrongstack/core/utils';\nimport type { BrowserArtifact, BrowserArtifactKind } from './types.js';\n\nexport class BrowserArtifactStore {\n constructor(private readonly root: string) {}\n\n async write(\n sessionId: string,\n kind: BrowserArtifactKind,\n extension: string,\n mimeType: string,\n content: Uint8Array,\n ): Promise<BrowserArtifact> {\n const id = ulid();\n const dir = path.join(this.root, safeSegment(sessionId));\n const target = path.join(dir, `${id}.${extension.replace(/^\\./, '')}`);\n await atomicWrite(target, content, { mode: 0o600 });\n return this.record(id, sessionId, kind, target, mimeType);\n }\n\n private async record(\n id: string,\n sessionId: string,\n kind: BrowserArtifactKind,\n target: string,\n mimeType: string,\n ): Promise<BrowserArtifact> {\n await fs.chmod(target, 0o600).catch(() => undefined);\n const [stat, sha256] = await Promise.all([fs.stat(target), hashFile(target)]);\n const artifact: BrowserArtifact = {\n id,\n kind,\n sensitivity: 'sensitive',\n path: target,\n mimeType,\n sizeBytes: stat.size,\n sha256,\n createdAt: new Date().toISOString(),\n };\n const metadataPath = path.join(path.dirname(target), `${id}.metadata.json`);\n try {\n await atomicWrite(\n metadataPath,\n `${JSON.stringify({\n id: artifact.id,\n sessionId: safeSegment(sessionId),\n kind: artifact.kind,\n sensitivity: artifact.sensitivity,\n mimeType: artifact.mimeType,\n sizeBytes: artifact.sizeBytes,\n sha256: artifact.sha256,\n createdAt: artifact.createdAt,\n })}\\n`,\n { mode: 0o600 },\n );\n } catch (error) {\n await fs.rm(target, { force: true }).catch(() => undefined);\n throw error;\n }\n return artifact;\n }\n\n pathFor(sessionId: string, extension: string): { id: string; path: string } {\n const id = ulid();\n return {\n id,\n path: path.join(this.root, safeSegment(sessionId), `${id}.${extension.replace(/^\\./, '')}`),\n };\n }\n\n async describe(\n id: string,\n kind: BrowserArtifactKind,\n target: string,\n mimeType: string,\n ): Promise<BrowserArtifact> {\n return this.record(id, path.basename(path.dirname(target)), kind, target, mimeType);\n }\n}\n\nasync function hashFile(target: string): Promise<string> {\n const hash = createHash('sha256');\n await new Promise<void>((resolve, reject) => {\n const stream = createReadStream(target);\n stream.on('data', (chunk) => hash.update(chunk));\n stream.once('error', reject);\n stream.once('end', resolve);\n });\n return hash.digest('hex');\n}\n\nfunction safeSegment(value: string): string {\n return value.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 80);\n}\n", "import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { Browser, BrowserContext, Page } from '@playwright/test';\nimport { ulid } from '@wrongstack/core/utils';\nimport { BrowserArtifactStore } from './artifacts.js';\nimport { BrowserNetworkGuardProxy } from './network-guard-proxy.js';\nimport { assertBrowserUrlAllowed, redactBrowserText, safeBrowserUrl } from './security.js';\nimport type {\n BrowserArtifact,\n BrowserConsoleEntry,\n BrowserManagerOptions,\n BrowserNetworkEntry,\n BrowserOpenOptions,\n BrowserSessionSummary,\n BrowserSnapshot,\n} from './types.js';\n\nconst DEFAULT_OPERATION_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_SNAPSHOT_CHARS = 64 * 1024;\nconst DEFAULT_MAX_ENTRIES = 100;\n\ninterface LiveSession {\n id: string;\n ownerId: string;\n context: BrowserContext;\n page: Page;\n createdAt: string;\n lastUsedAt: string;\n tracing: boolean;\n console: BrowserConsoleEntry[];\n network: BrowserNetworkEntry[];\n}\n\nexport type BrowserLauncher = (headless: boolean) => Promise<Browser>;\n\nexport class BrowserSessionManager {\n private readonly sessions = new Map<string, LiveSession>();\n private readonly artifacts: BrowserArtifactStore;\n private readonly allowPrivateHosts: boolean;\n private readonly allowedPrivateOrigins: readonly string[];\n private readonly networkProxy: BrowserNetworkGuardProxy;\n private readonly headless: boolean;\n private readonly operationTimeoutMs: number;\n private readonly maxSnapshotChars: number;\n private readonly maxConsoleEntries: number;\n private readonly maxNetworkEntries: number;\n private browser?: Browser | undefined;\n private launching?: Promise<Browser> | undefined;\n\n constructor(\n options: BrowserManagerOptions,\n private readonly launcher: BrowserLauncher = defaultLauncher,\n ) {\n this.artifacts = new BrowserArtifactStore(options.artifactRoot);\n this.allowPrivateHosts = options.allowPrivateHosts ?? true;\n this.allowedPrivateOrigins = options.allowedPrivateOrigins ?? [];\n this.networkProxy = new BrowserNetworkGuardProxy({\n allowPrivateHosts: this.allowPrivateHosts,\n allowedPrivateOrigins: this.allowedPrivateOrigins,\n });\n this.headless = options.headless ?? true;\n this.operationTimeoutMs = options.operationTimeoutMs ?? DEFAULT_OPERATION_TIMEOUT_MS;\n this.maxSnapshotChars = options.maxSnapshotChars ?? DEFAULT_MAX_SNAPSHOT_CHARS;\n this.maxConsoleEntries = options.maxConsoleEntries ?? DEFAULT_MAX_ENTRIES;\n this.maxNetworkEntries = options.maxNetworkEntries ?? DEFAULT_MAX_ENTRIES;\n }\n\n async open(\n ownerId: string,\n input: BrowserOpenOptions,\n signal: AbortSignal,\n ): Promise<BrowserSessionSummary> {\n signal.throwIfAborted();\n if (input.url) {\n await assertBrowserUrlAllowed(input.url, {\n allowPrivateHosts: this.allowPrivateHosts,\n allowedPrivateOrigins: this.allowedPrivateOrigins,\n navigation: true,\n });\n }\n const browser = await this.ensureBrowser(signal);\n const proxyServer = await this.networkProxy.start();\n const context = await abortable(\n signal,\n async () => {\n const created = await browser.newContext({\n acceptDownloads: false,\n ignoreHTTPSErrors: false,\n serviceWorkers: 'block',\n proxy: { server: proxyServer },\n viewport: input.viewport ?? { width: 1440, height: 900 },\n });\n if (signal.aborted) {\n await created.close().catch(() => undefined);\n signal.throwIfAborted();\n }\n return created;\n },\n () => undefined,\n );\n const session: LiveSession = {\n id: ulid(),\n ownerId,\n context,\n page: await context.newPage(),\n createdAt: new Date().toISOString(),\n lastUsedAt: new Date().toISOString(),\n tracing: input.trace ?? true,\n console: [],\n network: [],\n };\n this.sessions.set(session.id, session);\n this.attachEvidenceCollectors(session);\n await context.route('**/*', async (route) => {\n try {\n await assertBrowserUrlAllowed(route.request().url(), {\n allowPrivateHosts: this.allowPrivateHosts,\n allowedPrivateOrigins: this.allowedPrivateOrigins,\n navigation: false,\n });\n await route.continue();\n } catch {\n this.pushNetwork(session, {\n method: route.request().method(),\n url: safeBrowserUrl(route.request().url()),\n failed: true,\n at: new Date().toISOString(),\n });\n await route.abort('blockedbyclient');\n }\n });\n try {\n if (session.tracing) {\n await context.tracing.start({ screenshots: true, snapshots: true, sources: false });\n }\n if (input.url) await this.navigate(session.id, ownerId, input.url, signal);\n return await this.summary(session);\n } catch (err) {\n await context.close().catch(() => undefined);\n this.sessions.delete(session.id);\n await this.closeBrowserIfIdle();\n throw err;\n }\n }\n\n async list(ownerId: string): Promise<BrowserSessionSummary[]> {\n const owned = [...this.sessions.values()].filter((session) => session.ownerId === ownerId);\n return Promise.all(owned.map((session) => this.summary(session)));\n }\n\n async navigate(\n id: string,\n ownerId: string,\n rawUrl: string,\n signal: AbortSignal,\n ): Promise<BrowserSessionSummary> {\n await assertBrowserUrlAllowed(rawUrl, {\n allowPrivateHosts: this.allowPrivateHosts,\n allowedPrivateOrigins: this.allowedPrivateOrigins,\n navigation: true,\n });\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.goto(rawUrl, {\n waitUntil: 'domcontentloaded',\n timeout: this.operationTimeoutMs,\n }),\n );\n return this.summary(session);\n }\n\n async snapshot(id: string, ownerId: string, signal: AbortSignal): Promise<BrowserSnapshot> {\n const session = this.requireOwned(id, ownerId);\n const raw = await this.runPageOperation(session, signal, async () => {\n try {\n return await session.page\n .locator('body')\n .ariaSnapshot({ timeout: this.operationTimeoutMs });\n } catch {\n return await session.page.locator('body').innerText({ timeout: this.operationTimeoutMs });\n }\n });\n const aria = redactBrowserText(raw);\n const truncated = aria.length > this.maxSnapshotChars;\n return {\n session: await this.summary(session),\n aria: truncated ? `${aria.slice(0, this.maxSnapshotChars)}\\n\u2026[snapshot truncated]` : aria,\n console: session.console.map((entry) => ({ ...entry })),\n network: session.network.map((entry) => ({ ...entry })),\n truncated,\n };\n }\n\n async screenshot(\n id: string,\n ownerId: string,\n input: { fullPage?: boolean | undefined; selector?: string | undefined },\n signal: AbortSignal,\n ): Promise<BrowserArtifact> {\n const session = this.requireOwned(id, ownerId);\n const bytes = await this.runPageOperation(session, signal, () =>\n input.selector\n ? session.page\n .locator(input.selector)\n .screenshot({ type: 'png', timeout: this.operationTimeoutMs })\n : session.page.screenshot({ type: 'png', fullPage: input.fullPage ?? false }),\n );\n signal.throwIfAborted();\n return this.artifacts.write(session.id, 'screenshot', 'png', 'image/png', bytes);\n }\n\n async click(id: string, ownerId: string, selector: string, signal: AbortSignal): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).click({ timeout: this.operationTimeoutMs }),\n );\n }\n\n async type(\n id: string,\n ownerId: string,\n selector: string,\n text: string,\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).fill(text, { timeout: this.operationTimeoutMs }),\n );\n }\n\n async select(\n id: string,\n ownerId: string,\n selector: string,\n value: string,\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).selectOption(value, { timeout: this.operationTimeoutMs }),\n );\n }\n\n async press(id: string, ownerId: string, key: string, signal: AbortSignal): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () => session.page.keyboard.press(key));\n }\n\n async hover(id: string, ownerId: string, selector: string, signal: AbortSignal): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).hover({ timeout: this.operationTimeoutMs }),\n );\n }\n\n async wait(\n id: string,\n ownerId: string,\n input: { selector?: string | undefined; timeoutMs?: number | undefined },\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n const timeout = Math.min(\n Math.max(input.timeoutMs ?? this.operationTimeoutMs, 0),\n this.operationTimeoutMs,\n );\n await this.runPageOperation(session, signal, () =>\n input.selector\n ? session.page.locator(input.selector).waitFor({ state: 'visible', timeout })\n : session.page.waitForTimeout(timeout),\n );\n }\n\n async drag(\n id: string,\n ownerId: string,\n from: string,\n to: string,\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n await this.runPageOperation(session, signal, () =>\n session.page.locator(from).dragTo(session.page.locator(to), {\n timeout: this.operationTimeoutMs,\n }),\n );\n }\n\n async upload(\n id: string,\n ownerId: string,\n selector: string,\n files: string[],\n projectRoot: string,\n signal: AbortSignal,\n ): Promise<void> {\n const session = this.requireOwned(id, ownerId);\n const root = path.resolve(projectRoot);\n const realRoot = await fs.realpath(root);\n const resolved: string[] = [];\n for (const file of files) {\n const absolute = path.resolve(root, file);\n const relative = path.relative(root, absolute);\n if (relative.startsWith('..') || path.isAbsolute(relative)) {\n throw new Error('browser: upload files must stay inside the project root');\n }\n const realFile = await fs.realpath(absolute);\n const realRelative = path.relative(realRoot, realFile);\n if (realRelative.startsWith('..') || path.isAbsolute(realRelative)) {\n throw new Error('browser: upload files must not escape the project root through a symlink');\n }\n const stat = await fs.stat(realFile);\n if (!stat.isFile()) throw new Error(`browser: upload target is not a file: ${file}`);\n resolved.push(realFile);\n }\n await this.runPageOperation(session, signal, () =>\n session.page.locator(selector).setInputFiles(resolved, { timeout: this.operationTimeoutMs }),\n );\n }\n\n async evaluate(\n id: string,\n ownerId: string,\n expression: string,\n signal: AbortSignal,\n ): Promise<unknown> {\n const session = this.requireOwned(id, ownerId);\n const result = await this.runPageOperation(session, signal, () =>\n session.page.evaluate(expression),\n );\n const serialized = JSON.stringify(result);\n if (serialized && serialized.length > this.maxSnapshotChars) {\n throw new Error(`browser: evaluation result exceeds ${this.maxSnapshotChars} characters`);\n }\n return result;\n }\n\n async close(id: string, ownerId: string): Promise<{ trace?: BrowserArtifact | undefined }> {\n const session = this.requireOwned(id, ownerId);\n let trace: BrowserArtifact | undefined;\n try {\n if (session.tracing) {\n const target = this.artifacts.pathFor(session.id, 'zip');\n await fs.mkdir(path.dirname(target.path), { recursive: true });\n await session.context.tracing.stop({ path: target.path });\n trace = await this.artifacts.describe(target.id, 'trace', target.path, 'application/zip');\n }\n } finally {\n await session.context.close().catch(() => undefined);\n this.sessions.delete(id);\n await this.closeBrowserIfIdle();\n }\n return trace ? { trace } : {};\n }\n\n async closeOwner(ownerId: string): Promise<void> {\n const ids = [...this.sessions.values()]\n .filter((session) => session.ownerId === ownerId)\n .map((session) => session.id);\n for (const id of ids) await this.close(id, ownerId).catch(() => undefined);\n }\n\n async dispose(): Promise<void> {\n for (const session of [...this.sessions.values()]) {\n await session.context.close().catch(() => undefined);\n }\n this.sessions.clear();\n const browser = this.browser;\n this.browser = undefined;\n this.launching = undefined;\n await browser?.close().catch(() => undefined);\n await this.networkProxy.close();\n }\n\n private async ensureBrowser(signal: AbortSignal): Promise<Browser> {\n if (this.browser?.isConnected()) return this.browser;\n if (!this.launching) {\n this.launching = this.launcher(this.headless).then((browser) => {\n this.browser = browser;\n browser.on('disconnected', () => {\n if (this.browser === browser) this.browser = undefined;\n for (const [id, session] of this.sessions) {\n if (session.context.browser() === browser) this.sessions.delete(id);\n }\n });\n return browser;\n });\n }\n try {\n return await abortable(\n signal,\n () => this.launching!,\n async () => {\n const browser = await this.launching?.catch(() => undefined);\n await browser?.close().catch(() => undefined);\n },\n );\n } finally {\n this.launching = undefined;\n }\n }\n\n private requireOwned(id: string, ownerId: string): LiveSession {\n const session = this.sessions.get(id);\n if (!session) throw new Error(`browser: unknown session \"${id}\"`);\n if (session.ownerId !== ownerId) throw new Error('browser: session belongs to another agent');\n session.lastUsedAt = new Date().toISOString();\n return session;\n }\n\n private async summary(session: LiveSession): Promise<BrowserSessionSummary> {\n return {\n id: session.id,\n ownerId: session.ownerId,\n url: safeBrowserUrl(session.page.url()),\n title: redactBrowserText(await session.page.title().catch(() => '')),\n createdAt: session.createdAt,\n lastUsedAt: session.lastUsedAt,\n tracing: session.tracing,\n };\n }\n\n private attachEvidenceCollectors(session: LiveSession): void {\n session.page.on('console', (message) => {\n pushBounded(\n session.console,\n {\n level: message.type(),\n text: redactBrowserText(message.text()).slice(0, 4_096),\n at: new Date().toISOString(),\n },\n this.maxConsoleEntries,\n );\n });\n session.page.on('response', (response) => {\n this.pushNetwork(session, {\n method: response.request().method(),\n url: safeBrowserUrl(response.url()),\n status: response.status(),\n at: new Date().toISOString(),\n });\n });\n session.page.on('requestfailed', (request) => {\n this.pushNetwork(session, {\n method: request.method(),\n url: safeBrowserUrl(request.url()),\n failed: true,\n at: new Date().toISOString(),\n });\n });\n }\n\n private pushNetwork(session: LiveSession, entry: BrowserNetworkEntry): void {\n pushBounded(session.network, entry, this.maxNetworkEntries);\n }\n\n private async runPageOperation<T>(\n session: LiveSession,\n signal: AbortSignal,\n operation: () => Promise<T>,\n ): Promise<T> {\n signal.throwIfAborted();\n session.lastUsedAt = new Date().toISOString();\n return abortable(signal, operation, async () => {\n await session.context.close().catch(() => undefined);\n this.sessions.delete(session.id);\n await this.closeBrowserIfIdle();\n });\n }\n\n private async closeBrowserIfIdle(): Promise<void> {\n if (this.sessions.size > 0 || !this.browser) return;\n const browser = this.browser;\n this.browser = undefined;\n await browser.close().catch(() => undefined);\n }\n}\n\nasync function defaultLauncher(headless: boolean): Promise<Browser> {\n try {\n const { chromium } = await import('@playwright/test');\n return await chromium.launch({ headless });\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(\n `browser: Playwright Chromium is unavailable (${detail}). Run \"pnpm exec playwright install chromium\".`,\n );\n }\n}\n\nexport async function browserInstallationDiagnostics(): Promise<{\n available: boolean;\n executablePath?: string | undefined;\n message: string;\n}> {\n try {\n const { chromium } = await import('@playwright/test');\n const executablePath = chromium.executablePath();\n await fs.access(executablePath);\n return { available: true, executablePath, message: 'Playwright Chromium is available.' };\n } catch {\n return {\n available: false,\n message: 'Playwright Chromium is unavailable. Run \"pnpm exec playwright install chromium\".',\n };\n }\n}\n\nfunction pushBounded<T>(target: T[], value: T, limit: number): void {\n target.push(value);\n if (target.length > limit) target.splice(0, target.length - limit);\n}\n\nasync function abortable<T>(\n signal: AbortSignal,\n operation: () => Promise<T>,\n onAbort: () => void | Promise<void>,\n): Promise<T> {\n signal.throwIfAborted();\n return new Promise<T>((resolve, reject) => {\n let settled = false;\n let aborting = false;\n const finish = (fn: () => void) => {\n if (settled) return;\n settled = true;\n signal.removeEventListener('abort', abort);\n fn();\n };\n const abort = () => {\n aborting = true;\n void Promise.resolve(onAbort()).finally(() => {\n finish(() => reject(signal.reason instanceof Error ? signal.reason : new Error('Aborted')));\n });\n };\n signal.addEventListener('abort', abort, { once: true });\n operation().then(\n (value) => {\n if (!aborting) finish(() => resolve(value));\n },\n (err) => {\n if (!aborting) finish(() => reject(err));\n },\n );\n });\n}\n", "import * as http from 'node:http';\nimport * as net from 'node:net';\nimport type { Duplex } from 'node:stream';\nimport type { BrowserDnsLookup } from './security.js';\nimport { resolvePinnedBrowserTarget } from './security.js';\n\nexport interface BrowserNetworkGuardProxyOptions {\n allowPrivateHosts?: boolean | undefined;\n allowedPrivateOrigins?: readonly string[] | undefined;\n lookup?: BrowserDnsLookup | undefined;\n connectTimeoutMs?: number | undefined;\n}\n\n/** Local HTTP proxy that pins every browser socket to its validated DNS answer. */\nexport class BrowserNetworkGuardProxy {\n private readonly server: http.Server;\n private readonly sockets = new Set<net.Socket>();\n private startPromise?: Promise<string> | undefined;\n private url?: string | undefined;\n\n constructor(private readonly options: BrowserNetworkGuardProxyOptions = {}) {\n this.server = http.createServer((request, response) => {\n void this.forwardHttp(request, response);\n });\n this.server.on('connect', (request, socket, head) => {\n void this.forwardConnect(request, socket, head);\n });\n this.server.on('upgrade', (request, socket, head) => {\n void this.forwardUpgrade(request, socket, head);\n });\n this.server.on('connection', (socket) => {\n this.sockets.add(socket);\n socket.once('close', () => this.sockets.delete(socket));\n });\n this.server.on('clientError', (_error, socket) => {\n socket.end('HTTP/1.1 400 Bad Request\\r\\nConnection: close\\r\\n\\r\\n');\n });\n }\n\n async start(): Promise<string> {\n if (this.url) return this.url;\n if (this.startPromise) return this.startPromise;\n this.startPromise = new Promise<string>((resolve, reject) => {\n const onError = (error: Error) => {\n this.server.off('listening', onListening);\n reject(error);\n };\n const onListening = () => {\n this.server.off('error', onError);\n const address = this.server.address();\n if (!address || typeof address === 'string') {\n reject(new Error('browser: network guard proxy failed to bind'));\n return;\n }\n this.url = `http://127.0.0.1:${address.port}`;\n resolve(this.url);\n };\n this.server.once('error', onError);\n this.server.once('listening', onListening);\n this.server.listen(0, '127.0.0.1');\n }).finally(() => {\n this.startPromise = undefined;\n });\n return this.startPromise;\n }\n\n async close(): Promise<void> {\n this.url = undefined;\n for (const socket of this.sockets) socket.destroy();\n this.sockets.clear();\n if (!this.server.listening) return;\n await new Promise<void>((resolve) => this.server.close(() => resolve()));\n }\n\n private async forwardHttp(\n request: http.IncomingMessage,\n response: http.ServerResponse,\n ): Promise<void> {\n try {\n const target = await this.resolve(request.url ?? '');\n if (target.url.protocol !== 'http:') throw new Error('browser: HTTPS must use CONNECT');\n const headers = sanitizeHeaders(request.headers);\n headers.host = target.url.host;\n const upstream = http.request({\n host: target.address,\n family: target.family,\n port: Number(target.url.port || 80),\n method: request.method,\n path: `${target.url.pathname}${target.url.search}`,\n headers,\n agent: false,\n });\n upstream.setTimeout(this.options.connectTimeoutMs ?? 30_000, () => {\n upstream.destroy(new Error('browser: upstream connection timed out'));\n });\n upstream.on('response', (upstreamResponse) => {\n response.writeHead(\n upstreamResponse.statusCode ?? 502,\n sanitizeHeaders(upstreamResponse.headers),\n );\n upstreamResponse.pipe(response);\n });\n upstream.on('error', () => writeProxyError(response, 502, 'Bad Gateway'));\n request.on('aborted', () => upstream.destroy());\n request.pipe(upstream);\n } catch {\n writeProxyError(response, 403, 'Blocked by browser network policy');\n }\n }\n\n private async forwardConnect(\n request: http.IncomingMessage,\n client: Duplex,\n head: Buffer,\n ): Promise<void> {\n try {\n const authority = request.url ?? '';\n const target = await this.resolve(`https://${authority}`);\n const upstream = net.connect({\n host: target.address,\n family: target.family,\n port: Number(target.url.port || 443),\n });\n upstream.setTimeout(this.options.connectTimeoutMs ?? 30_000, () => upstream.destroy());\n upstream.once('connect', () => {\n client.write('HTTP/1.1 200 Connection Established\\r\\n\\r\\n');\n if (head.length > 0) upstream.write(head);\n upstream.pipe(client);\n client.pipe(upstream);\n });\n upstream.once('error', () => {\n client.end('HTTP/1.1 502 Bad Gateway\\r\\nConnection: close\\r\\n\\r\\n');\n });\n client.once('close', () => upstream.destroy());\n } catch {\n client.end('HTTP/1.1 403 Forbidden\\r\\nConnection: close\\r\\n\\r\\n');\n }\n }\n\n private async forwardUpgrade(\n request: http.IncomingMessage,\n client: Duplex,\n head: Buffer,\n ): Promise<void> {\n let upstream: http.ClientRequest | undefined;\n try {\n const rawUrl = request.url ?? '';\n const websocketUrl = new URL(rawUrl);\n if (websocketUrl.protocol !== 'ws:') {\n throw new Error('browser: only plain ws upgrades are handled by the HTTP proxy');\n }\n websocketUrl.protocol = 'http:';\n const target = await this.resolve(websocketUrl.toString());\n const headers = sanitizeHeaders(request.headers, true);\n headers.host = target.url.host;\n upstream = http.request({\n host: target.address,\n family: target.family,\n port: Number(target.url.port || 80),\n method: request.method,\n path: `${target.url.pathname}${target.url.search}`,\n headers,\n agent: false,\n });\n upstream.once('upgrade', (response, upstreamSocket, upstreamHead) => {\n writeRawResponseHead(client, response);\n if (head.length > 0) upstreamSocket.write(head);\n if (upstreamHead.length > 0) client.write(upstreamHead);\n upstreamSocket.pipe(client);\n client.pipe(upstreamSocket);\n client.once('close', () => upstreamSocket.destroy());\n });\n upstream.once('response', (response) => {\n writeRawResponseHead(client, response);\n response.pipe(client);\n });\n upstream.once('error', () => {\n client.end('HTTP/1.1 502 Bad Gateway\\r\\nConnection: close\\r\\n\\r\\n');\n });\n client.once('close', () => upstream?.destroy());\n upstream.end();\n } catch {\n upstream?.destroy();\n client.end('HTTP/1.1 403 Forbidden\\r\\nConnection: close\\r\\n\\r\\n');\n }\n }\n\n private resolve(rawUrl: string) {\n return resolvePinnedBrowserTarget(rawUrl, {\n allowPrivateHosts: this.options.allowPrivateHosts,\n allowedPrivateOrigins: this.options.allowedPrivateOrigins,\n lookup: this.options.lookup,\n });\n }\n}\n\nfunction sanitizeHeaders(\n headers: http.IncomingHttpHeaders,\n preserveUpgrade = false,\n): http.OutgoingHttpHeaders {\n const sanitized: http.OutgoingHttpHeaders = { ...headers };\n delete sanitized['proxy-authorization'];\n delete sanitized['proxy-connection'];\n if (!preserveUpgrade) delete sanitized.connection;\n return sanitized;\n}\n\nfunction writeRawResponseHead(client: Duplex, response: http.IncomingMessage): void {\n const status = response.statusCode ?? 502;\n const statusText = response.statusMessage ?? 'Bad Gateway';\n const lines = [`HTTP/1.1 ${status} ${statusText}`];\n for (let index = 0; index < response.rawHeaders.length; index += 2) {\n lines.push(`${response.rawHeaders[index]}: ${response.rawHeaders[index + 1]}`);\n }\n client.write(`${lines.join('\\r\\n')}\\r\\n\\r\\n`);\n}\n\nfunction writeProxyError(response: http.ServerResponse, status: number, message: string): void {\n if (response.headersSent || response.destroyed) return;\n response.writeHead(status, { 'content-type': 'text/plain', connection: 'close' });\n response.end(message);\n}\n", "import * as dns from 'node:dns/promises';\nimport * as net from 'node:net';\nimport { assertNotPrivateHost, isPrivateIPv4, isPrivateIPv6 } from '@wrongstack/core/utils';\n\nconst SAFE_SUBRESOURCE_PROTOCOLS = new Set(['about:', 'blob:', 'data:']);\n\nexport async function assertBrowserUrlAllowed(\n rawUrl: string,\n opts: {\n allowPrivateHosts?: boolean | undefined;\n allowedPrivateOrigins?: readonly string[] | undefined;\n navigation?: boolean | undefined;\n },\n): Promise<URL> {\n const url = parseBrowserUrl(rawUrl, opts.navigation ?? true);\n if (!opts.navigation && SAFE_SUBRESOURCE_PROTOCOLS.has(url.protocol)) return url;\n if (!opts.allowPrivateHosts && !isAllowedPrivateOrigin(url, opts.allowedPrivateOrigins)) {\n await assertNotPrivateHost(url.hostname);\n }\n return url;\n}\n\nexport interface BrowserResolvedTarget {\n url: URL;\n address: string;\n family: 4 | 6;\n}\n\nexport type BrowserDnsLookup = (\n hostname: string,\n) => Promise<readonly { address: string; family: number }[]>;\n\n/**\n * Resolve once, validate every answer, and return the exact address a socket\n * must use. Callers must connect to `address`, never resolve `url.hostname`\n * again, otherwise DNS rebinding remains possible.\n */\nexport async function resolvePinnedBrowserTarget(\n rawUrl: string,\n opts: {\n allowPrivateHosts?: boolean | undefined;\n allowedPrivateOrigins?: readonly string[] | undefined;\n lookup?: BrowserDnsLookup | undefined;\n } = {},\n): Promise<BrowserResolvedTarget> {\n const url = parseBrowserUrl(rawUrl, true);\n const allowPrivate =\n opts.allowPrivateHosts === true || isAllowedPrivateOrigin(url, opts.allowedPrivateOrigins);\n const hostname = unbracket(url.hostname);\n const literalFamily = net.isIP(hostname);\n if (literalFamily === 4 || literalFamily === 6) {\n if (!allowPrivate && isPrivateAddress(hostname, literalFamily)) {\n throw new Error(`browser: blocked private/loopback address \"${hostname}\"`);\n }\n return { url, address: hostname, family: literalFamily };\n }\n if ((hostname === 'localhost' || hostname.endsWith('.localhost')) && !allowPrivate) {\n throw new Error('browser: blocked localhost target');\n }\n\n const lookup = opts.lookup ?? ((host) => dns.lookup(host, { all: true }));\n let records: readonly { address: string; family: number }[];\n try {\n records = await lookup(hostname);\n } catch (error) {\n throw new Error(\n `browser: DNS resolution failed for \"${hostname}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n if (records.length === 0) throw new Error(`browser: DNS returned no addresses for \"${hostname}\"`);\n\n for (const record of records) {\n if (record.family !== 4 && record.family !== 6) {\n throw new Error(`browser: DNS returned an unsupported address family for \"${hostname}\"`);\n }\n if (!allowPrivate && isPrivateAddress(record.address, record.family)) {\n throw new Error(`browser: resolved to private address ${record.address}`);\n }\n }\n const selected = records[0]!;\n return { url, address: selected.address, family: selected.family as 4 | 6 };\n}\n\nexport function parsePrivateOriginAllowlist(raw: string | undefined): string[] {\n if (!raw?.trim()) return [];\n const origins = new Set<string>();\n for (const entry of raw.split(',')) {\n const candidate = entry.trim();\n if (!candidate) continue;\n const url = parseBrowserUrl(candidate, true);\n if (url.pathname !== '/' || url.search || url.hash) {\n throw new Error(`browser: private origin allowlist entry must be an origin: \"${candidate}\"`);\n }\n origins.add(url.origin);\n }\n return [...origins];\n}\n\nfunction parseBrowserUrl(rawUrl: string, navigation: boolean): URL {\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n throw new Error('browser: url must be an absolute URL');\n }\n if (!navigation && SAFE_SUBRESOURCE_PROTOCOLS.has(url.protocol)) return url;\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error(`browser: unsupported protocol \"${url.protocol}\"`);\n }\n if (url.username || url.password) {\n throw new Error('browser: credentials in URLs are not allowed');\n }\n return url;\n}\n\nfunction isAllowedPrivateOrigin(url: URL, origins: readonly string[] | undefined): boolean {\n return origins?.includes(url.origin) ?? false;\n}\n\nfunction isPrivateAddress(address: string, family: 4 | 6): boolean {\n return family === 4 ? isPrivateIPv4(address) : isPrivateIPv6(address);\n}\n\nfunction unbracket(hostname: string): string {\n return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n}\n\n/** Remove credentials, query strings, and fragments before persistence/output. */\nexport function safeBrowserUrl(rawUrl: string): string {\n try {\n const url = new URL(rawUrl);\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.toString();\n } catch {\n return 'about:blank';\n }\n}\n\nexport function redactBrowserText(text: string): string {\n return text\n .replace(/\\bBearer\\s+[A-Za-z0-9._~+/-]+=*/gi, 'Bearer [REDACTED]')\n .replace(\n /\\b(api[-_ ]?key|access[-_ ]?token|auth[-_ ]?token|token|password|secret)\\b\\s*[:=]\\s*[^\\s,;]+/gi,\n '$1=[REDACTED]',\n );\n}\n", "import * as path from 'node:path';\nimport { type Context, type Tool, ToolCapabilities } from '@wrongstack/core';\nimport { resolveWstackPaths } from '@wrongstack/core/utils';\nimport { BrowserSessionManager, browserInstallationDiagnostics } from './manager.js';\nimport { parsePrivateOriginAllowlist } from './security.js';\n\nconst managers = new Map<string, BrowserSessionManager>();\nconst cleanupSignalByContext = new WeakMap<object, AbortSignal>();\n\nfunction managerFor(ctx: Context): BrowserSessionManager {\n const root = path.resolve(ctx.projectRoot);\n let manager = managers.get(root);\n if (!manager) {\n const wpaths = resolveWstackPaths({ projectRoot: root });\n manager = new BrowserSessionManager({\n artifactRoot: path.join(wpaths.projectDir, 'browser-artifacts'),\n allowedPrivateOrigins: parsePrivateOriginAllowlist(\n process.env['WRONGSTACK_BROWSER_PRIVATE_ORIGINS'],\n ),\n });\n managers.set(root, manager);\n }\n const runSignal = ctx.signal;\n if (cleanupSignalByContext.get(ctx) !== runSignal) {\n cleanupSignalByContext.set(ctx, runSignal);\n const managerForRun = manager;\n const ownerForRun = owner(ctx);\n ctx.registerAbortHook?.(async () => {\n if (cleanupSignalByContext.get(ctx) === runSignal) {\n cleanupSignalByContext.delete(ctx);\n }\n await managerForRun.closeOwner(ownerForRun);\n });\n }\n return manager;\n}\n\nfunction owner(ctx: Context): string {\n return ctx.agentId || 'leader';\n}\n\nfunction json(value: unknown): string {\n return JSON.stringify(value, null, 2);\n}\n\nconst sessionIdSchema = {\n type: 'string',\n minLength: 1,\n description: 'Browser session id returned by browser_open.',\n} as const;\n\nexport const browserOpenTool: Tool<{\n url?: string | undefined;\n width?: number | undefined;\n height?: number | undefined;\n trace?: boolean | undefined;\n}> = {\n name: 'browser_open',\n description:\n 'Open an isolated first-party Playwright browser session, optionally navigating to a URL.',\n usageHint: 'browser_open({ url?, width?, height?, trace? })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n subjectKey: 'url',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'Optional absolute http(s) URL.' },\n width: { type: 'integer', minimum: 320, maximum: 3840 },\n height: { type: 'integer', minimum: 240, maximum: 2160 },\n trace: { type: 'boolean', description: 'Capture a Playwright trace; defaults to true.' },\n },\n },\n async execute(input, ctx, opts) {\n const viewport =\n input.width || input.height\n ? { width: input.width ?? 1440, height: input.height ?? 900 }\n : undefined;\n return json(\n await managerFor(ctx).open(\n owner(ctx),\n { url: input.url, viewport, trace: input.trace },\n opts.signal,\n ),\n );\n },\n};\n\nexport const browserListTool: Tool<Record<string, never>> = {\n name: 'browser_list',\n description:\n 'List browser sessions owned by the current agent without exposing other agents sessions.',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: { type: 'object', properties: {} },\n async execute(_input, ctx) {\n return json(await managerFor(ctx).list(owner(ctx)));\n },\n};\n\nexport const browserStatusTool: Tool<Record<string, never>> = {\n name: 'browser_status',\n description: 'Check whether first-party Playwright Chromium is installed and ready to launch.',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.TOOL_META],\n inputSchema: { type: 'object', properties: {} },\n async execute() {\n return json(await browserInstallationDiagnostics());\n },\n};\n\nexport const browserNavigateTool: Tool<{ sessionId: string; url: string }> = {\n name: 'browser_navigate',\n description: 'Navigate an owned browser session to an approved http(s) URL.',\n usageHint: 'browser_navigate({ sessionId, url })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n subjectKey: 'url',\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema, url: { type: 'string', minLength: 1 } },\n required: ['sessionId', 'url'],\n },\n async execute(input, ctx, opts) {\n return json(\n await managerFor(ctx).navigate(input.sessionId, owner(ctx), input.url, opts.signal),\n );\n },\n};\n\nexport const browserSnapshotTool: Tool<{ sessionId: string }> = {\n name: 'browser_snapshot',\n description: 'Return bounded accessibility state plus redacted console and network summaries.',\n usageHint: 'browser_snapshot({ sessionId })',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema },\n required: ['sessionId'],\n },\n async execute(input, ctx, opts) {\n return json(await managerFor(ctx).snapshot(input.sessionId, owner(ctx), opts.signal));\n },\n};\n\nexport const browserScreenshotTool: Tool<{\n sessionId: string;\n fullPage?: boolean | undefined;\n selector?: string | undefined;\n}> = {\n name: 'browser_screenshot',\n description:\n 'Capture a page or element PNG and return sensitive artifact metadata with integrity hash.',\n usageHint: 'browser_screenshot({ sessionId, fullPage?, selector? })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n fullPage: { type: 'boolean' },\n selector: { type: 'string', minLength: 1 },\n },\n required: ['sessionId'],\n },\n async execute(input, ctx, opts) {\n return json(\n await managerFor(ctx).screenshot(\n input.sessionId,\n owner(ctx),\n { fullPage: input.fullPage, selector: input.selector },\n opts.signal,\n ),\n );\n },\n};\n\ntype SelectorInput = { sessionId: string; selector: string };\n\nfunction selectorTool(\n name: string,\n description: string,\n run: (\n manager: BrowserSessionManager,\n input: SelectorInput,\n ownerId: string,\n signal: AbortSignal,\n ) => Promise<void>,\n): Tool<SelectorInput> {\n return {\n name,\n description,\n usageHint: `${name}({ sessionId, selector })`,\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema, selector: { type: 'string', minLength: 1 } },\n required: ['sessionId', 'selector'],\n },\n async execute(input, ctx, opts) {\n await run(managerFor(ctx), input, owner(ctx), opts.signal);\n return 'ok';\n },\n };\n}\n\nexport const browserClickTool = selectorTool(\n 'browser_click',\n 'Click an element in an owned browser session.',\n (manager, input, ownerId, signal) =>\n manager.click(input.sessionId, ownerId, input.selector, signal),\n);\n\nexport const browserHoverTool = selectorTool(\n 'browser_hover',\n 'Hover an element in an owned browser session.',\n (manager, input, ownerId, signal) =>\n manager.hover(input.sessionId, ownerId, input.selector, signal),\n);\n\nexport const browserTypeTool: Tool<\n SelectorInput & { text?: string | undefined; secretEnv?: string | undefined }\n> = {\n name: 'browser_type',\n description:\n 'Fill an input in an owned browser session. Use secretEnv instead of text for credentials so values stay out of tool arguments and session audit.',\n usageHint: 'browser_type({ sessionId, selector, text? | secretEnv? })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n selector: { type: 'string', minLength: 1 },\n text: { type: 'string' },\n secretEnv: {\n type: 'string',\n pattern: '^[A-Z_][A-Z0-9_]*$',\n description: 'Host environment variable resolved only at execution time.',\n },\n },\n required: ['sessionId', 'selector'],\n },\n validate(input) {\n return (input.text === undefined) === (input.secretEnv === undefined)\n ? ['exactly one of text or secretEnv is required']\n : [];\n },\n async execute(input, ctx, opts) {\n const value = input.text ?? (input.secretEnv ? process.env[input.secretEnv] : undefined);\n if (value === undefined) {\n throw new Error(`browser: secret environment variable \"${input.secretEnv ?? ''}\" is not set`);\n }\n await managerFor(ctx).type(input.sessionId, owner(ctx), input.selector, value, opts.signal);\n return 'ok';\n },\n};\n\nexport const browserSelectTool: Tool<SelectorInput & { value: string }> = {\n name: 'browser_select',\n description: 'Select an option in an owned browser session.',\n usageHint: 'browser_select({ sessionId, selector, value })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n selector: { type: 'string', minLength: 1 },\n value: { type: 'string' },\n },\n required: ['sessionId', 'selector', 'value'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).select(\n input.sessionId,\n owner(ctx),\n input.selector,\n input.value,\n opts.signal,\n );\n return 'ok';\n },\n};\n\nexport const browserPressTool: Tool<{ sessionId: string; key: string }> = {\n name: 'browser_press',\n description: 'Press a keyboard key in an owned browser session.',\n usageHint: 'browser_press({ sessionId, key })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema, key: { type: 'string', minLength: 1 } },\n required: ['sessionId', 'key'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).press(input.sessionId, owner(ctx), input.key, opts.signal);\n return 'ok';\n },\n};\n\nexport const browserDragTool: Tool<{ sessionId: string; from: string; to: string }> = {\n name: 'browser_drag',\n description: 'Drag one element to another in an owned browser session.',\n usageHint: 'browser_drag({ sessionId, from, to })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'standard',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n from: { type: 'string', minLength: 1 },\n to: { type: 'string', minLength: 1 },\n },\n required: ['sessionId', 'from', 'to'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).drag(input.sessionId, owner(ctx), input.from, input.to, opts.signal);\n return 'ok';\n },\n};\n\nexport const browserWaitTool: Tool<{\n sessionId: string;\n selector?: string | undefined;\n timeoutMs?: number | undefined;\n}> = {\n name: 'browser_wait',\n description: 'Wait for an element or a bounded duration in an owned browser session.',\n usageHint: 'browser_wait({ sessionId, selector?, timeoutMs? })',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n selector: { type: 'string', minLength: 1 },\n timeoutMs: { type: 'integer', minimum: 0, maximum: 30000 },\n },\n required: ['sessionId'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).wait(\n input.sessionId,\n owner(ctx),\n { selector: input.selector, timeoutMs: input.timeoutMs },\n opts.signal,\n );\n return 'ok';\n },\n};\n\nexport const browserEvaluateTool: Tool<{ sessionId: string; expression: string }> = {\n name: 'browser_evaluate',\n description:\n 'Evaluate bounded JavaScript in the page. Requires confirmation because page code is arbitrary.',\n usageHint: 'browser_evaluate({ sessionId, expression })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'destructive',\n capabilities: [ToolCapabilities.NET_OUTBOUND, ToolCapabilities.TOOL_MUTATE_ANY],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n expression: { type: 'string', minLength: 1, maxLength: 16384 },\n },\n required: ['sessionId', 'expression'],\n },\n async execute(input, ctx, opts) {\n return json(\n await managerFor(ctx).evaluate(input.sessionId, owner(ctx), input.expression, opts.signal),\n );\n },\n};\n\nexport const browserUploadTool: Tool<SelectorInput & { files: string[] }> = {\n name: 'browser_upload',\n description: 'Upload project-local files through a file input in an owned browser session.',\n usageHint: 'browser_upload({ sessionId, selector, files })',\n permission: 'confirm',\n mutating: true,\n riskTier: 'destructive',\n capabilities: [ToolCapabilities.NET_OUTBOUND, ToolCapabilities.FS_READ],\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: sessionIdSchema,\n selector: { type: 'string', minLength: 1 },\n files: { type: 'array', minItems: 1, maxItems: 20, items: { type: 'string', minLength: 1 } },\n },\n required: ['sessionId', 'selector', 'files'],\n },\n async execute(input, ctx, opts) {\n await managerFor(ctx).upload(\n input.sessionId,\n owner(ctx),\n input.selector,\n input.files,\n ctx.projectRoot,\n opts.signal,\n );\n return 'ok';\n },\n};\n\nexport const browserCloseTool: Tool<{ sessionId: string }> = {\n name: 'browser_close',\n description:\n 'Close an owned browser session, reclaim its context, and return sensitive trace metadata.',\n usageHint: 'browser_close({ sessionId })',\n permission: 'auto',\n mutating: false,\n riskTier: 'safe',\n capabilities: [ToolCapabilities.NET_OUTBOUND],\n inputSchema: {\n type: 'object',\n properties: { sessionId: sessionIdSchema },\n required: ['sessionId'],\n },\n async execute(input, ctx) {\n return json(await managerFor(ctx).close(input.sessionId, owner(ctx)));\n },\n};\n\nexport const browserTools: Tool[] = [\n browserOpenTool,\n browserStatusTool,\n browserListTool,\n browserNavigateTool,\n browserSnapshotTool,\n browserScreenshotTool,\n browserClickTool,\n browserTypeTool,\n browserSelectTool,\n browserPressTool,\n browserHoverTool,\n browserDragTool,\n browserWaitTool,\n browserEvaluateTool,\n browserUploadTool,\n browserCloseTool,\n];\n\nfor (const tool of browserTools) tool.icon = 'web';\nfor (const tool of browserTools) tool.timeoutMs ??= 60_000;\n\nexport async function shutdownBrowserTools(): Promise<void> {\n const active = [...managers.values()];\n managers.clear();\n await Promise.all(active.map((manager) => manager.dispose()));\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["stat", "resolve", "fs", "path", "ulid", "net", "lookup", "request", "resolve", "ulid", "relative", "stat", "request", "resolve", "path"]
7
7
  }
@@ -48,7 +48,7 @@ export interface BrowserOpenOptions {
48
48
  }
49
49
  export interface BrowserManagerOptions {
50
50
  artifactRoot: string;
51
- /** @deprecated Test-only broad bypass. Production hosts use exact origins. */
51
+ /** Allow navigation to private/loopback addresses (localhost, 127.0.0.1, etc.). True by default. */
52
52
  allowPrivateHosts?: boolean | undefined;
53
53
  allowedPrivateOrigins?: string[] | undefined;
54
54
  headless?: boolean | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,mBAAmB,CAAC;IAC1B,uEAAuE;IACvE,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACzD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,mBAAmB,CAAC;IAC1B,uEAAuE;IACvE,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACzD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC"}