cbrowser 18.73.2 → 18.74.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 (137) hide show
  1. package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
  2. package/dist/analysis/accessibility-empathy.js +113 -8
  3. package/dist/analysis/accessibility-empathy.js.map +1 -1
  4. package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
  5. package/dist/analysis/agent-ready-audit.js +66 -5
  6. package/dist/analysis/agent-ready-audit.js.map +1 -1
  7. package/dist/analysis/natural-language.d.ts.map +1 -1
  8. package/dist/analysis/natural-language.js +9 -8
  9. package/dist/analysis/natural-language.js.map +1 -1
  10. package/dist/analysis/page-understanding.d.ts.map +1 -1
  11. package/dist/analysis/page-understanding.js +115 -14
  12. package/dist/analysis/page-understanding.js.map +1 -1
  13. package/dist/analysis/webmcp-readiness.d.ts.map +1 -1
  14. package/dist/analysis/webmcp-readiness.js +18 -7
  15. package/dist/analysis/webmcp-readiness.js.map +1 -1
  16. package/dist/artifact-store.d.ts +55 -0
  17. package/dist/artifact-store.d.ts.map +1 -0
  18. package/dist/artifact-store.js +63 -0
  19. package/dist/artifact-store.js.map +1 -0
  20. package/dist/browser.d.ts +8 -0
  21. package/dist/browser.d.ts.map +1 -1
  22. package/dist/browser.js +157 -41
  23. package/dist/browser.js.map +1 -1
  24. package/dist/cli.js +12 -0
  25. package/dist/cli.js.map +1 -1
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +28 -1
  28. package/dist/config.js.map +1 -1
  29. package/dist/mcp-server-remote.d.ts +17 -1
  30. package/dist/mcp-server-remote.d.ts.map +1 -1
  31. package/dist/mcp-server-remote.js +22 -3
  32. package/dist/mcp-server-remote.js.map +1 -1
  33. package/dist/mcp-server.d.ts.map +1 -1
  34. package/dist/mcp-server.js +20 -0
  35. package/dist/mcp-server.js.map +1 -1
  36. package/dist/mcp-tools/base/analysis-tools.d.ts +1 -1
  37. package/dist/mcp-tools/base/analysis-tools.d.ts.map +1 -1
  38. package/dist/mcp-tools/base/analysis-tools.js +83 -10
  39. package/dist/mcp-tools/base/analysis-tools.js.map +1 -1
  40. package/dist/mcp-tools/base/assertion-tools.d.ts +1 -1
  41. package/dist/mcp-tools/base/assertion-tools.d.ts.map +1 -1
  42. package/dist/mcp-tools/base/assertion-tools.js +26 -3
  43. package/dist/mcp-tools/base/assertion-tools.js.map +1 -1
  44. package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
  45. package/dist/mcp-tools/base/audit-tools.js +189 -28
  46. package/dist/mcp-tools/base/audit-tools.js.map +1 -1
  47. package/dist/mcp-tools/base/browser-management-tools.d.ts +1 -1
  48. package/dist/mcp-tools/base/browser-management-tools.d.ts.map +1 -1
  49. package/dist/mcp-tools/base/browser-management-tools.js +66 -15
  50. package/dist/mcp-tools/base/browser-management-tools.js.map +1 -1
  51. package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -1
  52. package/dist/mcp-tools/base/browser-state-tools.js +6 -1
  53. package/dist/mcp-tools/base/browser-state-tools.js.map +1 -1
  54. package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
  55. package/dist/mcp-tools/base/cognitive-tools.js +103 -30
  56. package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
  57. package/dist/mcp-tools/base/extraction-tools.d.ts.map +1 -1
  58. package/dist/mcp-tools/base/extraction-tools.js +6 -0
  59. package/dist/mcp-tools/base/extraction-tools.js.map +1 -1
  60. package/dist/mcp-tools/base/gif-tools.d.ts.map +1 -1
  61. package/dist/mcp-tools/base/gif-tools.js +7 -7
  62. package/dist/mcp-tools/base/gif-tools.js.map +1 -1
  63. package/dist/mcp-tools/base/interaction-tools.d.ts.map +1 -1
  64. package/dist/mcp-tools/base/interaction-tools.js +76 -11
  65. package/dist/mcp-tools/base/interaction-tools.js.map +1 -1
  66. package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
  67. package/dist/mcp-tools/base/persona-comparison-tools.js +78 -10
  68. package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
  69. package/dist/mcp-tools/base/session-tools.d.ts +1 -1
  70. package/dist/mcp-tools/base/session-tools.d.ts.map +1 -1
  71. package/dist/mcp-tools/base/session-tools.js +64 -7
  72. package/dist/mcp-tools/base/session-tools.js.map +1 -1
  73. package/dist/mcp-tools/base/site-knowledge-tools.d.ts.map +1 -1
  74. package/dist/mcp-tools/base/site-knowledge-tools.js +47 -1
  75. package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
  76. package/dist/mcp-tools/base/testing-tools.d.ts.map +1 -1
  77. package/dist/mcp-tools/base/testing-tools.js +20 -0
  78. package/dist/mcp-tools/base/testing-tools.js.map +1 -1
  79. package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
  80. package/dist/mcp-tools/base/visual-testing-tools.js +71 -46
  81. package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
  82. package/dist/mcp-tools/persona-creation-tools.d.ts.map +1 -1
  83. package/dist/mcp-tools/persona-creation-tools.js +40 -10
  84. package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
  85. package/dist/mcp-tools/session-policy.d.ts +43 -0
  86. package/dist/mcp-tools/session-policy.d.ts.map +1 -0
  87. package/dist/mcp-tools/session-policy.js +54 -0
  88. package/dist/mcp-tools/session-policy.js.map +1 -0
  89. package/dist/mcp-ui-resources.d.ts +83 -0
  90. package/dist/mcp-ui-resources.d.ts.map +1 -0
  91. package/dist/mcp-ui-resources.js +128 -0
  92. package/dist/mcp-ui-resources.js.map +1 -0
  93. package/dist/performance/metrics.d.ts +0 -3
  94. package/dist/performance/metrics.d.ts.map +1 -1
  95. package/dist/performance/metrics.js +45 -1
  96. package/dist/performance/metrics.js.map +1 -1
  97. package/dist/personas.d.ts.map +1 -1
  98. package/dist/personas.js +38 -4
  99. package/dist/personas.js.map +1 -1
  100. package/dist/remediation/patch-generator.d.ts.map +1 -1
  101. package/dist/remediation/patch-generator.js +36 -2
  102. package/dist/remediation/patch-generator.js.map +1 -1
  103. package/dist/selector-escape.d.ts +37 -0
  104. package/dist/selector-escape.d.ts.map +1 -0
  105. package/dist/selector-escape.js +83 -0
  106. package/dist/selector-escape.js.map +1 -0
  107. package/dist/testing/coverage.d.ts +8 -0
  108. package/dist/testing/coverage.d.ts.map +1 -1
  109. package/dist/testing/coverage.js +12 -0
  110. package/dist/testing/coverage.js.map +1 -1
  111. package/dist/types.d.ts +42 -0
  112. package/dist/types.d.ts.map +1 -1
  113. package/dist/types.js.map +1 -1
  114. package/dist/visual/attention-quality.d.ts +0 -9
  115. package/dist/visual/attention-quality.d.ts.map +1 -1
  116. package/dist/visual/attention-quality.js +28 -1
  117. package/dist/visual/attention-quality.js.map +1 -1
  118. package/dist/visual/barrier-overlay-html.d.ts +88 -0
  119. package/dist/visual/barrier-overlay-html.d.ts.map +1 -0
  120. package/dist/visual/barrier-overlay-html.js +142 -0
  121. package/dist/visual/barrier-overlay-html.js.map +1 -0
  122. package/dist/visual/cognitive-transport.d.ts +15 -8
  123. package/dist/visual/cognitive-transport.d.ts.map +1 -1
  124. package/dist/visual/cognitive-transport.js +113 -34
  125. package/dist/visual/cognitive-transport.js.map +1 -1
  126. package/dist/visual/cross-browser.d.ts +13 -0
  127. package/dist/visual/cross-browser.d.ts.map +1 -1
  128. package/dist/visual/cross-browser.js +29 -3
  129. package/dist/visual/cross-browser.js.map +1 -1
  130. package/dist/visual/perceptual-transport.d.ts +8 -0
  131. package/dist/visual/perceptual-transport.d.ts.map +1 -1
  132. package/dist/visual/perceptual-transport.js +19 -0
  133. package/dist/visual/perceptual-transport.js.map +1 -1
  134. package/dist/visual/regression.d.ts.map +1 -1
  135. package/dist/visual/regression.js +76 -3
  136. package/dist/visual/regression.js.map +1 -1
  137. package/package.json +2 -1
@@ -5,15 +5,21 @@
5
5
  * @license MIT
6
6
  */
7
7
  import { z } from "zod";
8
+ import { refuseUnboundSession } from "../session-policy.js";
8
9
  import { findElementByIntent, runAIReadinessBenchmark } from "../../analysis/index.js";
9
10
  /**
10
11
  * Register analysis tools (4 tools: analyze_page, generate_tests, find_element_by_intent, ai_benchmark)
11
12
  */
12
- export function registerAnalysisTools(server, { getBrowser }) {
13
+ export function registerAnalysisTools(server, { getBrowser, getBrowserByToken }) {
13
14
  server.registerTool("analyze_page", {
14
15
  title: "Analyze Page Structure",
15
- description: "Analyze page structure for forms, buttons, links",
16
- inputSchema: {},
16
+ description: "Analyze page structure for forms, buttons, links. MUST pass _browserToken from a previous tool call to analyze the same page — without it a blank page is analyzed and every count comes back zero.",
17
+ inputSchema: {
18
+ // Took no arguments at all, so on the HTTP transport it always analysed a
19
+ // fresh blank page: 0 forms, 0 buttons, 0 links, hasLogin false. Reads as a
20
+ // real analysis of a sparse page rather than as an error. (2026-07-28)
21
+ _browserToken: z.string().optional().describe("Browser session token from a previous tool call"),
22
+ },
17
23
  annotations: {
18
24
  title: "Analyze Page Structure",
19
25
  readOnlyHint: true,
@@ -21,14 +27,29 @@ export function registerAnalysisTools(server, { getBrowser }) {
21
27
  idempotentHint: true,
22
28
  openWorldHint: false,
23
29
  },
24
- }, async () => {
25
- const b = await getBrowser();
30
+ }, async ({ _browserToken }) => {
31
+ // Reports on page structure, so a blank-page answer (0 forms, 0 buttons,
32
+ // 0 links) reads as a real finding rather than an error.
33
+ const unbound = refuseUnboundSession("analyze_page", { getBrowserByToken }, _browserToken);
34
+ if (unbound)
35
+ return unbound;
36
+ let b;
37
+ let token;
38
+ if (getBrowserByToken) {
39
+ const resolved = await getBrowserByToken(_browserToken);
40
+ b = resolved.browser;
41
+ token = resolved.token;
42
+ }
43
+ else {
44
+ b = await getBrowser();
45
+ }
26
46
  const analysis = await b.analyzePage();
27
47
  return {
28
48
  content: [
29
49
  {
30
50
  type: "text",
31
51
  text: JSON.stringify({
52
+ ...(token ? { _browserToken: token } : {}),
32
53
  title: analysis.title,
33
54
  forms: analysis.forms.length,
34
55
  buttons: analysis.buttons.length,
@@ -46,6 +67,10 @@ export function registerAnalysisTools(server, { getBrowser }) {
46
67
  description: "Generate test scenarios for a page",
47
68
  inputSchema: {
48
69
  url: z.string().url().optional().describe("URL to analyze (uses current page if not provided)"),
70
+ // "uses current page if not provided" was unreachable on the HTTP transport:
71
+ // omitting url fell back to a blank page, so the no-url branch generated
72
+ // tests for about:blank. (2026-07-28)
73
+ _browserToken: z.string().optional().describe("Browser session token from a previous tool call — required when url is omitted"),
49
74
  },
50
75
  annotations: {
51
76
  title: "Generate Test Cases",
@@ -54,20 +79,52 @@ export function registerAnalysisTools(server, { getBrowser }) {
54
79
  idempotentHint: true,
55
80
  openWorldHint: false,
56
81
  },
57
- }, async ({ url }) => {
58
- const b = await getBrowser();
82
+ }, async ({ url, _browserToken }) => {
83
+ // Only when it is being asked about "the current page". With an explicit
84
+ // url it navigates itself and needs no session.
85
+ if (!url) {
86
+ const unbound = refuseUnboundSession("generate_tests", { getBrowserByToken }, _browserToken);
87
+ if (unbound)
88
+ return unbound;
89
+ }
90
+ let b;
91
+ let token;
92
+ if (getBrowserByToken) {
93
+ const resolved = await getBrowserByToken(_browserToken);
94
+ b = resolved.browser;
95
+ token = resolved.token;
96
+ }
97
+ else {
98
+ b = await getBrowser();
99
+ }
59
100
  const result = await b.generateTests(url);
60
101
  return {
61
102
  content: [
62
103
  {
63
104
  type: "text",
64
105
  text: JSON.stringify({
106
+ ...(token ? { _browserToken: token } : {}),
65
107
  testsGenerated: result.tests.length,
108
+ // `steps` used to be t.steps.length — a bare count. A caller got
109
+ // "3 steps" with no way to see or run them, so there was no handoff
110
+ // into nl_test_inline / nl_test_file and the tool could only be
111
+ // read, not used. The step text and a runnable script were both
112
+ // already computed and then discarded. (2026-07-29)
66
113
  tests: result.tests.map(t => ({
67
114
  name: t.name,
68
115
  description: t.description,
69
- steps: t.steps.length,
116
+ stepCount: t.steps.length,
117
+ steps: t.steps.map(st => ({
118
+ action: st.action,
119
+ ...(st.target ? { target: st.target } : {}),
120
+ ...(st.value !== undefined ? { value: st.value } : {}),
121
+ description: st.description,
122
+ })),
123
+ assertions: t.assertions,
70
124
  })),
125
+ // Ready to paste straight into nl_test_inline.
126
+ cbrowserScript: result.cbrowserScript,
127
+ usage: "Pass cbrowserScript to nl_test_inline as `content`, or write it to a file and use nl_test_file.",
71
128
  }, null, 2),
72
129
  },
73
130
  ],
@@ -79,6 +136,11 @@ export function registerAnalysisTools(server, { getBrowser }) {
79
136
  inputSchema: {
80
137
  intent: z.string().describe("Natural language description like 'the cheapest product' or 'login form'"),
81
138
  verbose: z.boolean().optional().describe("Include alternative matches with confidence scores and AI suggestions"),
139
+ // Without this, the tool searched a blank browser on the HTTP transport and
140
+ // returned `confidence: 0, "No match found"` with empty alternatives — a
141
+ // plausible "nothing here" that is indistinguishable from a real miss.
142
+ // (2026-07-28)
143
+ _browserToken: z.string().optional().describe("Browser session token from a previous tool call"),
82
144
  },
83
145
  annotations: {
84
146
  title: "Find Element by Intent",
@@ -87,8 +149,19 @@ export function registerAnalysisTools(server, { getBrowser }) {
87
149
  idempotentHint: true,
88
150
  openWorldHint: false,
89
151
  },
90
- }, async ({ intent, verbose }) => {
91
- const b = await getBrowser();
152
+ }, async ({ intent, verbose, _browserToken }) => {
153
+ // Searches the page, so a blank-browser search returns "no match found"
154
+ // with confidence 0 — indistinguishable from a genuine miss.
155
+ const unbound = refuseUnboundSession("find_element_by_intent", { getBrowserByToken }, _browserToken);
156
+ if (unbound)
157
+ return unbound;
158
+ let b;
159
+ if (getBrowserByToken) {
160
+ b = (await getBrowserByToken(_browserToken)).browser;
161
+ }
162
+ else {
163
+ b = await getBrowser();
164
+ }
92
165
  const result = await findElementByIntent(b, intent, { verbose });
93
166
  if (result && result.confidence > 0) {
94
167
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"analysis-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/analysis-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEvF;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAiB,EACjB,EAAE,UAAU,EAA2B;IAEvC,MAAM,CAAC,YAAY,CAAC,cAAc,EAAE;QAClC,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EAAE,kDAAkD;QAC/D,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,KAAK,EAAE,wBAAwB;YAC/B,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EAAE,KAAK,IAAI,EAAE;QACV,MAAM,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QACvC,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;wBACrB,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;wBAC5B,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM;wBAChC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;wBAC5B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;wBAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;wBAC7B,aAAa,EAAE,QAAQ,CAAC,aAAa;qBACtC,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CAAC,gBAAgB,EAAE;QACpC,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EAAE,oCAAoC;QACjD,WAAW,EAAE;YACX,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;SAChG;QACD,WAAW,EAAE;YACX,KAAK,EAAE,qBAAqB;YAC5B,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;QACjB,MAAM,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QAC1C,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;wBACnC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;4BAC5B,IAAI,EAAE,CAAC,CAAC,IAAI;4BACZ,WAAW,EAAE,CAAC,CAAC,WAAW;4BAC1B,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM;yBACtB,CAAC,CAAC;qBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CAAC,wBAAwB,EAAE;QAC5C,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EAAE,8PAA8P;QAC3Q,WAAW,EAAE;YACX,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0EAA0E,CAAC;YACvG,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uEAAuE,CAAC;SAClH;QACD,WAAW,EAAE;YACX,KAAK,EAAE,wBAAwB;YAC/B,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC7B,MAAM,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,IAAI,MAAM,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YACpC,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACnE,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,2BAA2B,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SAC7H,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CAAC,cAAc,EAAE;QAClC,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,qOAAqO;QAClP,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,uCAAuC,CAAC;YACjF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;SAC9F;QACD,WAAW,EAAE;YACX,KAAK,EAAE,2BAA2B;YAClC,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,IAAI;SACpB;KACF,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;QACxB,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC;YAC3C,IAAI;YACJ,IAAI;YACJ,QAAQ,EAAE,IAAI;YACd,cAAc,EAAE,CAAC;SAClB,CAAC,CAAC;QAEH,qDAAqD;QACrD,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;QACrF,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;QAElF,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB;wBACE,SAAS,EAAE,MAAM,CAAC,SAAS;wBAC3B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;wBACnD,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;wBAClC,cAAc,EAAE,YAAY;wBAC5B,WAAW,EAAE,WAAW;wBACxB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;4BAClC,IAAI,EAAE,CAAC,CAAC,IAAI;4BACZ,IAAI,EAAE,CAAC,CAAC,IAAI;4BACZ,KAAK,EAAE,CAAC,CAAC,KAAK;4BACd,KAAK,EAAE,CAAC,CAAC,KAAK;4BACd,WAAW,EAAE,CAAC,CAAC,WAAW;yBAC3B,CAAC,CAAC;wBACH,UAAU,EAAE;4BACV,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,WAAW;4BAC1C,eAAe,EAAE,MAAM,CAAC,UAAU,CAAC,eAAe;4BAClD,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,aAAa;4BAC9C,iBAAiB,EAAE,MAAM,CAAC,UAAU,CAAC,iBAAiB;4BACtD,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,aAAa;4BAC9C,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;yBACzD;wBACD,cAAc,EAAE,MAAM,CAAC,UAAU,CAAC,cAAc;wBAChD,kBAAkB,EAAE,MAAM,CAAC,eAAe;6BACvC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;6BACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;4BACX,IAAI,EAAE,CAAC,CAAC,IAAI;4BACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;4BACpB,WAAW,EAAE,CAAC,CAAC,WAAW;4BAC1B,mBAAmB,EAAE,CAAC,CAAC,mBAAmB;yBAC3C,CAAC,CAAC;wBACL,eAAe,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;4BACxC,IAAI,EAAE,CAAC,CAAC,QAAQ;4BAChB,KAAK,EAAE,CAAC,CAAC,KAAK;4BACd,KAAK,EAAE,CAAC,CAAC,KAAK;4BACd,SAAS,EAAE,CAAC,CAAC,SAAS;4BACtB,UAAU,EAAE,CAAC,CAAC,UAAU;4BACxB,SAAS,EAAE,CAAC,CAAC,SAAS;4BACtB,sDAAsD;4BACtD,WAAW,EAAE,CAAC,CAAC,WAAW;4BAC1B,GAAG,CAAC,CAAC,CAAC,WAAW,KAAK,QAAQ,IAAI;gCAChC,eAAe,EAAE,CAAC,CAAC,eAAe;gCAClC,cAAc,EAAE,CAAC,CAAC,cAAc;gCAChC,UAAU,EAAE,CAAC,CAAC,UAAU;gCACxB,aAAa,EAAE,CAAC,CAAC,aAAa;6BAC/B,CAAC;yBACH,CAAC,CAAC;qBACJ,EACD,IAAI,EACJ,CAAC,CACF;iBACF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"analysis-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/analysis-tools.ts"],"names":[],"mappings":"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"}
@@ -8,5 +8,5 @@ import type { McpServer, ToolRegistrationContext } from "../types.js";
8
8
  /**
9
9
  * Register assertion tools (1 tool: assert)
10
10
  */
11
- export declare function registerAssertionTools(server: McpServer, { getBrowser }: ToolRegistrationContext): void;
11
+ export declare function registerAssertionTools(server: McpServer, { getBrowser, getBrowserByToken }: ToolRegistrationContext): void;
12
12
  //# sourceMappingURL=assertion-tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assertion-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/assertion-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtE;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,EAAE,UAAU,EAAE,EAAE,uBAAuB,GACtC,IAAI,CAgCN"}
1
+ {"version":3,"file":"assertion-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/assertion-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGtE;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,uBAAuB,GACzD,IAAI,CAqDN"}
@@ -5,15 +5,22 @@
5
5
  * @license MIT
6
6
  */
7
7
  import { z } from "zod";
8
+ import { refuseUnboundSession } from "../session-policy.js";
8
9
  /**
9
10
  * Register assertion tools (1 tool: assert)
10
11
  */
11
- export function registerAssertionTools(server, { getBrowser }) {
12
+ export function registerAssertionTools(server, { getBrowser, getBrowserByToken }) {
12
13
  server.registerTool("assert", {
13
14
  title: "Assert Page State",
14
15
  description: "Assert a condition using natural language",
15
16
  inputSchema: {
16
17
  assertion: z.string().describe("Natural language assertion like \"page contains 'Welcome'\" or \"title is 'Home'\""),
18
+ // Without this, assert could not bind to the session navigate created on the
19
+ // HTTP transport: it asserted against a blank page and returned
20
+ // `passed:false, actual:""`. Worse than a crash — a NEGATIVE assertion
21
+ // ("page does not contain 'error'") then PASSED against nothing, so a whole
22
+ // suite could report green while testing an empty browser. (2026-07-28)
23
+ _browserToken: z.string().optional().describe("Browser session token from a previous tool call"),
17
24
  },
18
25
  annotations: {
19
26
  title: "Assert Page State",
@@ -22,8 +29,23 @@ export function registerAssertionTools(server, { getBrowser }) {
22
29
  idempotentHint: true,
23
30
  openWorldHint: false,
24
31
  },
25
- }, async ({ assertion }) => {
26
- const b = await getBrowser();
32
+ }, async ({ assertion, _browserToken }) => {
33
+ // Adding the token was only half the fix: omitting it entirely still
34
+ // asserted against a blank page and returned a confident verdict, with a
35
+ // fresh _browserToken attached that made it look session-backed.
36
+ const unbound = refuseUnboundSession("assert", { getBrowserByToken }, _browserToken);
37
+ if (unbound)
38
+ return unbound;
39
+ let b;
40
+ let token;
41
+ if (getBrowserByToken) {
42
+ const resolved = await getBrowserByToken(_browserToken);
43
+ b = resolved.browser;
44
+ token = resolved.token;
45
+ }
46
+ else {
47
+ b = await getBrowser();
48
+ }
27
49
  const result = await b.assert(assertion);
28
50
  return {
29
51
  content: [
@@ -34,6 +56,7 @@ export function registerAssertionTools(server, { getBrowser }) {
34
56
  message: result.message,
35
57
  actual: result.actual,
36
58
  expected: result.expected,
59
+ ...(token ? { _browserToken: token } : {}),
37
60
  }, null, 2),
38
61
  },
39
62
  ],
@@ -1 +1 @@
1
- {"version":3,"file":"assertion-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/assertion-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,EAAE,UAAU,EAA2B;IAEvC,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE;QAC5B,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,2CAA2C;QACxD,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oFAAoF,CAAC;SACrH;QACD,WAAW,EAAE;YACX,KAAK,EAAE,mBAAmB;YAC1B,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;QACvB,MAAM,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACzC,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,OAAO,EAAE,MAAM,CAAC,OAAO;wBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;qBAC1B,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"assertion-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/base/assertion-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D;;GAEG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,EAAE,UAAU,EAAE,iBAAiB,EAA2B;IAE1D,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE;QAC5B,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,2CAA2C;QACxD,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oFAAoF,CAAC;YACpH,6EAA6E;YAC7E,gEAAgE;YAChE,uEAAuE;YACvE,4EAA4E;YAC5E,wEAAwE;YACxE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iDAAiD,CAAC;SACjG;QACD,WAAW,EAAE;YACX,KAAK,EAAE,mBAAmB;YAC1B,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,EAAE;QACtC,qEAAqE;QACrE,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,OAAO,GAAG,oBAAoB,CAAC,QAAQ,EAAE,EAAE,iBAAiB,EAAE,EAAE,aAAa,CAAC,CAAC;QACrF,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;QAE5B,IAAI,CAAC,CAAC;QACN,IAAI,KAAyB,CAAC;QAC9B,IAAI,iBAAiB,EAAE,CAAC;YACtB,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;YACxD,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;YACrB,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,CAAC,GAAG,MAAM,UAAU,EAAE,CAAC;QACzB,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACzC,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,OAAO,EAAE,MAAM,CAAC,OAAO;wBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;wBACzB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC3C,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"audit-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/audit-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAStE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CA2jC7F"}
1
+ {"version":3,"file":"audit-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/base/audit-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAoDtE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CA4rC7F"}
@@ -5,8 +5,52 @@
5
5
  * @license MIT
6
6
  */
7
7
  import { z } from "zod";
8
+ import { randomBytes } from "crypto";
9
+ import { htmlUiResource, attachUiResource, uiResourcesEnabled } from "../../mcp-ui-resources.js";
10
+ import { writeArtifact } from "../../artifact-store.js";
8
11
  import { runAgentReadyAudit, runCompetitiveBenchmark, runEmpathyAudit, runWebMCPReadyAudit, } from "../../analysis/index.js";
9
12
  import { listAccessibilityPersonas } from "../../personas.js";
13
+ import { writeFileSync, mkdirSync, existsSync } from "fs";
14
+ import { join } from "path";
15
+ import { homedir } from "os";
16
+ /**
17
+ * Persist an audit screenshot and return its path.
18
+ *
19
+ * empathy_audit inlined base64 page screenshots, which routinely overflowed an
20
+ * MCP client's context and made the tool untestable through a normal client.
21
+ * Writing them out keeps the overlay data usable without carrying megabytes
22
+ * through the transport. Returns undefined rather than throwing if the write
23
+ * fails — a screenshot is not worth failing an audit over. (2026-07-29)
24
+ */
25
+ function writeAuditScreenshot(base64, persona) {
26
+ if (!base64)
27
+ return {};
28
+ const safe = String(persona || "persona").toLowerCase().replace(/[^a-z0-9-]+/g, "-");
29
+ const out = {};
30
+ // Local copy, as before — the CLI and anything on this box reads this.
31
+ try {
32
+ const dir = join(process.env.CBROWSER_DATA_DIR || join(homedir(), ".cbrowser"), "audit-screenshots");
33
+ if (!existsSync(dir))
34
+ mkdirSync(dir, { recursive: true });
35
+ const path = join(dir, `empathy-${safe}-${Date.now()}.png`);
36
+ writeFileSync(path, Buffer.from(base64, "base64"));
37
+ out.path = path;
38
+ }
39
+ catch { /* a screenshot is not worth failing an audit over */ }
40
+ // Served copy, so the barrier overlay has an image an MCP host can fetch.
41
+ // The name carries 12 random hex chars rather than only a timestamp: these
42
+ // are captures of a CUSTOMER'S page served from a public path, and a
43
+ // timestamped name is guessable by anyone who knows roughly when an audit
44
+ // ran. Unguessability is the only access control this path has. (2026-07-29)
45
+ try {
46
+ const nonce = randomBytes(6).toString("hex");
47
+ const written = writeArtifact(Buffer.from(base64, "base64"), `empathy-${safe}-${nonce}.png`);
48
+ if (written)
49
+ out.url = written.url;
50
+ }
51
+ catch { /* overlay is optional; the audit is not */ }
52
+ return out;
53
+ }
10
54
  /**
11
55
  * Register audit tools (4 tools + site_cognitive_assessment + visual_cognitive_story)
12
56
  */
@@ -92,8 +136,14 @@ export function registerAuditTools(server, context) {
92
136
  wcagLevel: z.enum(["A", "AA", "AAA"]).optional().default("AA").describe("WCAG conformance level"),
93
137
  maxSteps: z.number().optional().default(5).describe("Max cognitive journey steps (keep low for MCP)"),
94
138
  maxTime: z.number().optional().default(20).describe("Max time per persona in seconds"),
139
+ // Base64 page screenshots are large enough to blow an MCP client's context
140
+ // on a single call — every empathy_audit in the 18.73.3 test pass had to be
141
+ // read off disk instead. Off by default; images go to disk and their paths
142
+ // are returned. (2026-07-29)
143
+ includeScreenshots: z.boolean().optional().default(false).describe("Inline base64 page screenshots in the response. Off by default because they are large enough to overflow an MCP client's context; when off, screenshots are written to disk and screenshotPath is returned instead."),
95
144
  scope: z.enum(["viewport", "full_page"]).optional().default("viewport").describe("What to score: 'viewport' (first impression, above-the-fold only — default) or 'full_page' (scroll through entire page, all barriers). Use 'viewport' for landing page optimization; 'full_page' for WCAG compliance audits."),
96
145
  device: z.string().optional().describe("Device emulation: 'mobile', 'tablet', 'desktop', or specific device like 'iPhone 15', 'Pixel 7'. Essential for mobile WCAG audits — touch targets, viewport sizing, and responsive barriers differ significantly on mobile."),
146
+ uiResource: z.boolean().optional().default(true).describe("Return an interactive HTML report as an MCP UI resource alongside the JSON. Hosts that support MCP Apps render it inline; others ignore it. Set false for scripted callers that diff whole responses."),
97
147
  },
98
148
  annotations: {
99
149
  title: "Empathy Accessibility Audit",
@@ -102,7 +152,7 @@ export function registerAuditTools(server, context) {
102
152
  idempotentHint: false,
103
153
  openWorldHint: true,
104
154
  },
105
- }, async ({ url, goal, disabilities, wcagLevel, maxSteps, maxTime, scope, device }) => {
155
+ }, async ({ url, goal, disabilities, wcagLevel, maxSteps, maxTime, scope, device, includeScreenshots, uiResource }) => {
106
156
  try {
107
157
  // Auto-limit to 1 persona to avoid MCP client timeout on Claude.ai (~60s limit)
108
158
  const allPersonas = listAccessibilityPersonas();
@@ -140,11 +190,23 @@ export function registerAuditTools(server, context) {
140
190
  goalAchieved: r.goalAchieved,
141
191
  empathyScore: r.empathyScore,
142
192
  // v18.22.0: Added score context for transparency
193
+ // Whitelist projection: anything added to scoreContext upstream is
194
+ // invisible to the caller unless named here. The blend breakdown
195
+ // below is what lets the deductions reconcile to a score the
196
+ // caller can actually see. (2026-07-28)
143
197
  scoreContext: r.scoreContext ? {
144
198
  explanation: r.scoreContext.explanation,
145
199
  deductionsByType: r.scoreContext.deductionsByType,
200
+ totalBarrierDeduction: r.scoreContext.totalBarrierDeduction,
146
201
  frictionDeduction: r.scoreContext.frictionDeduction,
147
202
  goalDeduction: r.scoreContext.goalDeduction,
203
+ cognitiveOverloadPenalty: r.scoreContext.cognitiveOverloadPenalty,
204
+ appliedBarrierWeights: r.scoreContext.appliedBarrierWeights,
205
+ explainsScore: r.scoreContext.explainsScore,
206
+ barrierOnlyScore: r.scoreContext.barrierOnlyScore,
207
+ perceptualScore: r.scoreContext.perceptualScore,
208
+ blendWeights: r.scoreContext.blendWeights,
209
+ finalScore: r.scoreContext.finalScore,
148
210
  } : undefined,
149
211
  barrierTypeCount: uniqueTypes.size,
150
212
  barrierTypes: Array.from(uniqueTypes),
@@ -155,6 +217,10 @@ export function registerAuditTools(server, context) {
155
217
  empathyScoreBarrierOnly: r.empathyScoreBarrierOnly || undefined,
156
218
  // v18.27.0: Cognitive load estimation (optimal transport)
157
219
  cognitiveLoad: r.cognitiveLoad || undefined,
220
+ // Names the three different "cognitive load" numbers in this
221
+ // payload and what each measures. Same whitelist caveat as
222
+ // scoreContext above: set upstream, invisible unless named here.
223
+ cognitiveLoadReadings: r.cognitiveLoadReadings || undefined,
158
224
  // v18.28.0: Attention transport analysis (W₂ saliency)
159
225
  attentionAnalysis: r.attentionAnalysis || undefined,
160
226
  // v18.29.0: Journey validation — evidence, path, forensics
@@ -166,34 +232,123 @@ export function registerAuditTools(server, context) {
166
232
  }),
167
233
  allWcagViolations: result.allWcagViolations,
168
234
  topBarriers: result.topBarriers.slice(0, 5),
235
+ // A barrier's affectedPersonas names the personas it hits HARDEST. It
236
+ // is not an exclusion list, which is why a persona absent from it can
237
+ // still take a deduction — susceptibility is applied as a weight, not
238
+ // a gate (see scoreContext.appliedBarrierWeights). Stating it here
239
+ // because the pairing otherwise reads as a contradiction: a
240
+ // touch_target barrier listing motor and elderly personas, scored
241
+ // against cognitive-adhd. (2026-07-29)
242
+ barrierFieldNotes: {
243
+ affectedPersonas: "Exemplars — the personas this barrier affects most. NOT an exclusion list; every tested persona is scored against every barrier, weighted by susceptibility.",
244
+ severity: "On topBarriers entries this is the group maximum across all elements of that type (severityIsGroupMax), not one element's severity.",
245
+ },
169
246
  topRemediation: result.combinedRemediation.slice(0, 5),
170
247
  duration: result.duration,
171
248
  // v18.60.0: Include per-result screenshots and element rects for WCAG overlay
172
249
  pageScreenshots: result.results?.map((r) => ({
173
250
  persona: r.persona,
174
- screenshot: r.pageScreenshot,
251
+ ...(includeScreenshots
252
+ ? { screenshot: r.pageScreenshot }
253
+ : (() => {
254
+ const shot = writeAuditScreenshot(r.pageScreenshot, r.persona);
255
+ return {
256
+ screenshotPath: shot.path,
257
+ screenshotUrl: shot.url,
258
+ screenshotNote: "Pass includeScreenshots:true to inline the base64 instead.",
259
+ };
260
+ })()),
175
261
  viewportSize: r.viewportSize,
176
- barrierRects: r.barriers?.filter((b) => b.rect).map((b) => ({
177
- type: b.type, severity: b.severity, element: b.element,
178
- description: b.description, rect: b.rect,
179
- wcag: b.wcagCriteria,
180
- })),
181
- })).filter((s) => s.screenshot),
262
+ // Rects come from getBoundingClientRect with no scroll compensation,
263
+ // so they are VIEWPORT coordinates — which is correct here, because
264
+ // the screenshot above is captured with fullPage: false and these are
265
+ // meant to overlay it. Converting them to document coordinates would
266
+ // break that alignment.
267
+ //
268
+ // The real hazard is scope: "full_page", where the audit scrolls and
269
+ // rects captured at different offsets land in one list against a
270
+ // single viewport image; anything above the captured viewport then
271
+ // carries a negative y and would render off-canvas. Say which space
272
+ // these are in and flag the ones that fall outside the image rather
273
+ // than emitting silent negatives. (2026-07-28)
274
+ // Rects are now DOCUMENT coordinates (scroll offset added at capture),
275
+ // so they are stable across the scrolling that scope:"full_page" does.
276
+ // They were previously labelled "viewport" while holding values from
277
+ // several scroll positions at once — y:-274 and y:3951 in one list.
278
+ coordinateSpace: "document",
279
+ // Height of the captured image in document space. A viewport capture
280
+ // covers only the scroll position it was taken at; a full_page capture
281
+ // covers the document. Without this the bounds test compared document
282
+ // coordinates against a viewport height and flagged EVERY rect,
283
+ // including ones plainly inside the image. (2026-07-29)
284
+ captureHeight: (scope === "full_page" ? (r.documentHeight ?? undefined) : r.viewportSize?.height),
285
+ barrierRects: r.barriers?.filter((b) => b.rect).map((b) => {
286
+ const vp = r.viewportSize;
287
+ const bound = scope === "full_page" ? (r.documentHeight ?? Infinity) : (vp?.height ?? Infinity);
288
+ const outside = !!b.rect && (b.rect.y + b.rect.height < 0 || b.rect.x + b.rect.width < 0 ||
289
+ b.rect.y > bound || (!!vp && b.rect.x > vp.width));
290
+ return {
291
+ type: b.type, severity: b.severity, element: b.element,
292
+ description: b.description, rect: b.rect,
293
+ wcag: b.wcagCriteria,
294
+ ...(outside ? { outsideScreenshot: true } : {}),
295
+ };
296
+ }),
297
+ // Keep entries that carry EITHER the inline image or a path to it.
298
+ // Filtering on `screenshot` alone silently dropped every entry once
299
+ // the default stopped inlining base64, taking the barrierRects with
300
+ // them. (2026-07-29)
301
+ })).filter((s) => s.screenshot || s.screenshotPath),
182
302
  };
183
303
  // Add guidance if we limited the request
184
304
  if (wasLimited) {
185
305
  response.note = `Limited to 1 persona to avoid timeout. For full coverage, call again with: ${remainingPersonas.slice(0, 3).join(", ")}${remainingPersonas.length > 3 ? `, and ${remainingPersonas.length - 3} more` : ""}`;
186
306
  response.remainingPersonas = remainingPersonas;
187
307
  }
188
- // Auto-save handled by tier-gate wrapper
189
- return {
190
- content: [
191
- {
192
- type: "text",
193
- text: JSON.stringify(response, null, 2),
194
- },
195
- ],
196
- };
308
+ // Auto-save handled by tier-gate wrapper.
309
+ //
310
+ // The UI resource is APPENDED to the text block, never substituted for
311
+ // it: content[0] stays the same JSON every CLI and CI caller already
312
+ // parses. `generateEmpathyAuditHtmlReport` is the report the --html flag
313
+ // has always produced — self-contained, no remote refs — so this is a
314
+ // transport seam rather than new UI. If it cannot be produced safely,
315
+ // htmlUiResource returns null and the response is exactly as before.
316
+ // (2026-07-29)
317
+ const content = [
318
+ { type: "text", text: JSON.stringify(response, null, 2) },
319
+ ];
320
+ if (uiResourcesEnabled(uiResource)) {
321
+ // The overlay goes FIRST. It is the thing that makes a score
322
+ // falsifiable by eye — barriers drawn on the page at their real
323
+ // positions — and the text report is the detail behind it.
324
+ try {
325
+ const { buildBarrierOverlayHtml } = await import("../../visual/barrier-overlay-html.js");
326
+ const shots = (response.pageScreenshots ?? []);
327
+ for (const shot of shots) {
328
+ const overlayHtml = buildBarrierOverlayHtml({
329
+ imageUrl: shot.screenshotUrl,
330
+ imageWidth: shot.viewportSize?.width,
331
+ captureHeight: shot.captureHeight ?? shot.viewportSize?.height,
332
+ barrierRects: shot.barrierRects ?? [],
333
+ persona: String(shot.persona ?? testedPersona),
334
+ pageUrl: String(result.url),
335
+ score: result.overallScore,
336
+ });
337
+ attachUiResource(content, htmlUiResource(`ui://cbrowser/barrier-overlay/${encodeURIComponent(String(shot.persona ?? testedPersona))}`, overlayHtml ?? undefined, { frameSize: ["100%", "820px"] }));
338
+ }
339
+ }
340
+ catch { /* overlay is additive; never fail the audit for it */ }
341
+ let reportHtml;
342
+ try {
343
+ const { generateEmpathyAuditHtmlReport } = await import("../../analysis/accessibility-empathy.js");
344
+ reportHtml = generateEmpathyAuditHtmlReport(result);
345
+ }
346
+ catch {
347
+ reportHtml = undefined;
348
+ }
349
+ attachUiResource(content, htmlUiResource(`ui://cbrowser/empathy-audit/${encodeURIComponent(testedPersona)}`, reportHtml, { frameSize: ["100%", "760px"] }));
350
+ }
351
+ return { content };
197
352
  }
198
353
  catch (error) {
199
354
  // Categorize the error for better user feedback
@@ -747,9 +902,11 @@ export function registerAuditTools(server, context) {
747
902
  const page = await browser.getPage();
748
903
  const ssPath = join(tmpdir(), `story-${Date.now()}.png`);
749
904
  await page.screenshot({ path: ssPath, fullPage: false });
750
- const webDir = "/var/www/cbrowser-web/heatmaps";
751
- if (!existsSync(webDir))
752
- mkdirSync(webDir, { recursive: true });
905
+ // webDir removed: this pointed at /var/www/cbrowser-web/heatmaps, a
906
+ // FIFTH directory nginx does not serve (it serves /heatmaps/ from
907
+ // /var/www/cbrowser-data/heatmaps). All four story overlays below wrote
908
+ // there and returned cbrowser.ai URLs that 404'd. (2026-07-29)
909
+ // (directory creation is the artifact store's job now)
753
910
  const ts = Date.now();
754
911
  const images = [];
755
912
  const urls = {};
@@ -768,8 +925,9 @@ export function registerAuditTools(server, context) {
768
925
  const { generateHeatmapOverlay } = await import("../../visual/heatmap-overlay.js");
769
926
  const heatB64 = await generateHeatmapOverlay(ssPath, attnResult.saliencyMap.cells, attnResult.saliencyMap.rows, attnResult.saliencyMap.cols, `${persona} Attention`);
770
927
  const heatId = `story-attn-${persona}-${ts}`;
771
- writeFileSync(join(webDir, `${heatId}.png`), Buffer.from(heatB64, "base64"));
772
- urls.attention = `https://cbrowser.ai/heatmaps/${heatId}.png`;
928
+ const w_attention = writeArtifact(Buffer.from(heatB64, "base64"), `${heatId}.png`);
929
+ if (w_attention)
930
+ urls.attention = w_attention.url;
773
931
  images.push({ type: "image", data: heatB64, mimeType: "image/png" });
774
932
  // Auto-save to gallery
775
933
  try {
@@ -815,8 +973,9 @@ export function registerAuditTools(server, context) {
815
973
  const cssVW = await page.evaluate(() => window.innerWidth).catch(() => 1920);
816
974
  const motorB64 = await generateMotorOverlay(ssPath, motorElements, persona, cssVW);
817
975
  const motorId = `story-motor-${persona}-${ts}`;
818
- writeFileSync(join(webDir, `${motorId}.png`), Buffer.from(motorB64, "base64"));
819
- urls.motor = `https://cbrowser.ai/heatmaps/${motorId}.png`;
976
+ const w_motor = writeArtifact(Buffer.from(motorB64, "base64"), `${motorId}.png`);
977
+ if (w_motor)
978
+ urls.motor = w_motor.url;
820
979
  images.push({ type: "image", data: motorB64, mimeType: "image/png" });
821
980
  try {
822
981
  const { saveVisualReport } = await import("../visual-report-saver.js");
@@ -892,8 +1051,9 @@ export function registerAuditTools(server, context) {
892
1051
  const cssVW2 = await page.evaluate(() => window.innerWidth).catch(() => 1920);
893
1052
  const qualB64 = await generateAttentionQualityOverlay(ssPath, overlayTargets, persona, cssVW2);
894
1053
  const qualId = `story-quality-${persona}-${ts}`;
895
- writeFileSync(join(webDir, `${qualId}.png`), Buffer.from(qualB64, "base64"));
896
- urls.quality = `https://cbrowser.ai/heatmaps/${qualId}.png`;
1054
+ const w_quality = writeArtifact(Buffer.from(qualB64, "base64"), `${qualId}.png`);
1055
+ if (w_quality)
1056
+ urls.quality = w_quality.url;
897
1057
  images.push({ type: "image", data: qualB64, mimeType: "image/png" });
898
1058
  try {
899
1059
  const { saveVisualReport } = await import("../visual-report-saver.js");
@@ -963,8 +1123,9 @@ export function registerAuditTools(server, context) {
963
1123
  const cssVW3 = await page.evaluate(() => window.innerWidth).catch(() => 1920);
964
1124
  const combinedB64 = await generateCombinedStoryOverlay(ssPath, attentionData?.saliencyMap?.cells ?? null, attentionData?.saliencyMap?.rows ?? 0, attentionData?.saliencyMap?.cols ?? 0, motorEls, qualTargets, persona, cssVW3);
965
1125
  const combId = `story-combined-${persona}-${ts}`;
966
- writeFileSync(join(webDir, `${combId}.png`), Buffer.from(combinedB64, "base64"));
967
- urls.combined = `https://cbrowser.ai/heatmaps/${combId}.png`;
1126
+ const w_combined = writeArtifact(Buffer.from(combinedB64, "base64"), `${combId}.png`);
1127
+ if (w_combined)
1128
+ urls.combined = w_combined.url;
968
1129
  images.push({ type: "image", data: combinedB64, mimeType: "image/png" });
969
1130
  try {
970
1131
  const { saveVisualReport } = await import("../visual-report-saver.js");