cbrowser 18.78.0 → 18.80.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 (235) hide show
  1. package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
  2. package/dist/analysis/accessibility-empathy.js +395 -66
  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 +78 -0
  6. package/dist/analysis/agent-ready-audit.js.map +1 -1
  7. package/dist/analysis/page-understanding.d.ts +139 -1
  8. package/dist/analysis/page-understanding.d.ts.map +1 -1
  9. package/dist/analysis/page-understanding.js +179 -12
  10. package/dist/analysis/page-understanding.js.map +1 -1
  11. package/dist/artifact-store.d.ts +19 -5
  12. package/dist/artifact-store.d.ts.map +1 -1
  13. package/dist/artifact-store.js +41 -11
  14. package/dist/artifact-store.js.map +1 -1
  15. package/dist/browser.d.ts +9 -0
  16. package/dist/browser.d.ts.map +1 -1
  17. package/dist/browser.js +81 -5
  18. package/dist/browser.js.map +1 -1
  19. package/dist/cli.js +55 -10
  20. package/dist/cli.js.map +1 -1
  21. package/dist/cognitive/index.d.ts.map +1 -1
  22. package/dist/cognitive/index.js +3 -2
  23. package/dist/cognitive/index.js.map +1 -1
  24. package/dist/config.d.ts +57 -0
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +234 -7
  27. package/dist/config.js.map +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/mcp-server-remote.d.ts.map +1 -1
  31. package/dist/mcp-server-remote.js +108 -4
  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 +62 -938
  35. package/dist/mcp-server.js.map +1 -1
  36. package/dist/mcp-tools/account-personas.d.ts +41 -0
  37. package/dist/mcp-tools/account-personas.d.ts.map +1 -0
  38. package/dist/mcp-tools/account-personas.js +98 -0
  39. package/dist/mcp-tools/account-personas.js.map +1 -0
  40. package/dist/mcp-tools/base/analysis-tools.d.ts.map +1 -1
  41. package/dist/mcp-tools/base/analysis-tools.js +12 -2
  42. package/dist/mcp-tools/base/analysis-tools.js.map +1 -1
  43. package/dist/mcp-tools/base/artifact-tools.d.ts +3 -0
  44. package/dist/mcp-tools/base/artifact-tools.d.ts.map +1 -0
  45. package/dist/mcp-tools/base/artifact-tools.js +78 -0
  46. package/dist/mcp-tools/base/artifact-tools.js.map +1 -0
  47. package/dist/mcp-tools/base/audit-tools.d.ts +11 -0
  48. package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
  49. package/dist/mcp-tools/base/audit-tools.js +595 -340
  50. package/dist/mcp-tools/base/audit-tools.js.map +1 -1
  51. package/dist/mcp-tools/base/browser-management-tools.d.ts.map +1 -1
  52. package/dist/mcp-tools/base/browser-management-tools.js +18 -0
  53. package/dist/mcp-tools/base/browser-management-tools.js.map +1 -1
  54. package/dist/mcp-tools/base/bug-analysis-tools.d.ts.map +1 -1
  55. package/dist/mcp-tools/base/bug-analysis-tools.js +59 -3
  56. package/dist/mcp-tools/base/bug-analysis-tools.js.map +1 -1
  57. package/dist/mcp-tools/base/capture-tools.d.ts +37 -0
  58. package/dist/mcp-tools/base/capture-tools.d.ts.map +1 -1
  59. package/dist/mcp-tools/base/capture-tools.js +267 -6
  60. package/dist/mcp-tools/base/capture-tools.js.map +1 -1
  61. package/dist/mcp-tools/base/cognitive-tools.d.ts +12 -0
  62. package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
  63. package/dist/mcp-tools/base/cognitive-tools.js +63 -15
  64. package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
  65. package/dist/mcp-tools/base/extraction-tools.d.ts.map +1 -1
  66. package/dist/mcp-tools/base/extraction-tools.js +33 -2
  67. package/dist/mcp-tools/base/extraction-tools.js.map +1 -1
  68. package/dist/mcp-tools/base/index.d.ts +2 -0
  69. package/dist/mcp-tools/base/index.d.ts.map +1 -1
  70. package/dist/mcp-tools/base/index.js +9 -0
  71. package/dist/mcp-tools/base/index.js.map +1 -1
  72. package/dist/mcp-tools/base/marketing-tools.js +3 -3
  73. package/dist/mcp-tools/base/marketing-tools.js.map +1 -1
  74. package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
  75. package/dist/mcp-tools/base/persona-comparison-tools.js +529 -40
  76. package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
  77. package/dist/mcp-tools/base/site-knowledge-tools.d.ts.map +1 -1
  78. package/dist/mcp-tools/base/site-knowledge-tools.js +36 -3
  79. package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
  80. package/dist/mcp-tools/base/values-tools.d.ts +113 -0
  81. package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
  82. package/dist/mcp-tools/base/values-tools.js +868 -78
  83. package/dist/mcp-tools/base/values-tools.js.map +1 -1
  84. package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
  85. package/dist/mcp-tools/base/visual-testing-tools.js +130 -10
  86. package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
  87. package/dist/mcp-tools/index.d.ts +3 -1
  88. package/dist/mcp-tools/index.d.ts.map +1 -1
  89. package/dist/mcp-tools/index.js +3 -28
  90. package/dist/mcp-tools/index.js.map +1 -1
  91. package/dist/mcp-tools/persona-creation-tools.d.ts.map +1 -1
  92. package/dist/mcp-tools/persona-creation-tools.js +363 -20
  93. package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
  94. package/dist/mcp-tools/persona-lifecycle-tools.d.ts +3 -0
  95. package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -0
  96. package/dist/mcp-tools/persona-lifecycle-tools.js +406 -0
  97. package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -0
  98. package/dist/mcp-tools/screenshot-utils.d.ts.map +1 -1
  99. package/dist/mcp-tools/screenshot-utils.js +56 -10
  100. package/dist/mcp-tools/screenshot-utils.js.map +1 -1
  101. package/dist/mcp-tools/tier-gate.js +2 -2
  102. package/dist/mcp-tools/tier-gate.js.map +1 -1
  103. package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
  104. package/dist/mcp-tools/tool-categories.js +4 -1
  105. package/dist/mcp-tools/tool-categories.js.map +1 -1
  106. package/dist/mcp-tools/ui-resources.d.ts +167 -0
  107. package/dist/mcp-tools/ui-resources.d.ts.map +1 -0
  108. package/dist/mcp-tools/ui-resources.js +707 -0
  109. package/dist/mcp-tools/ui-resources.js.map +1 -0
  110. package/dist/mcp-tools/widget-kit.d.ts +299 -0
  111. package/dist/mcp-tools/widget-kit.d.ts.map +1 -0
  112. package/dist/mcp-tools/widget-kit.js +2034 -0
  113. package/dist/mcp-tools/widget-kit.js.map +1 -0
  114. package/dist/mcp-ui-resources.d.ts.map +1 -1
  115. package/dist/mcp-ui-resources.js +14 -4
  116. package/dist/mcp-ui-resources.js.map +1 -1
  117. package/dist/persona-questionnaire.d.ts +69 -1
  118. package/dist/persona-questionnaire.d.ts.map +1 -1
  119. package/dist/persona-questionnaire.js +177 -76
  120. package/dist/persona-questionnaire.js.map +1 -1
  121. package/dist/persona-scope.d.ts +20 -0
  122. package/dist/persona-scope.d.ts.map +1 -0
  123. package/dist/persona-scope.js +59 -0
  124. package/dist/persona-scope.js.map +1 -0
  125. package/dist/personas.d.ts +51 -17
  126. package/dist/personas.d.ts.map +1 -1
  127. package/dist/personas.js +947 -286
  128. package/dist/personas.js.map +1 -1
  129. package/dist/recording/capture-player.d.ts +68 -0
  130. package/dist/recording/capture-player.d.ts.map +1 -0
  131. package/dist/recording/capture-player.js +298 -0
  132. package/dist/recording/capture-player.js.map +1 -0
  133. package/dist/recording/engine.d.ts +52 -0
  134. package/dist/recording/engine.d.ts.map +1 -1
  135. package/dist/recording/engine.js +241 -3
  136. package/dist/recording/engine.js.map +1 -1
  137. package/dist/recording/inline-player.d.ts +39 -0
  138. package/dist/recording/inline-player.d.ts.map +1 -0
  139. package/dist/recording/inline-player.js +272 -0
  140. package/dist/recording/inline-player.js.map +1 -0
  141. package/dist/recording/saliency-overlay.d.ts +133 -0
  142. package/dist/recording/saliency-overlay.d.ts.map +1 -0
  143. package/dist/recording/saliency-overlay.js +360 -0
  144. package/dist/recording/saliency-overlay.js.map +1 -0
  145. package/dist/recording/types.d.ts +32 -0
  146. package/dist/recording/types.d.ts.map +1 -1
  147. package/dist/recording/types.js +46 -0
  148. package/dist/recording/types.js.map +1 -1
  149. package/dist/site-model/manager.d.ts +20 -0
  150. package/dist/site-model/manager.d.ts.map +1 -1
  151. package/dist/site-model/manager.js +52 -0
  152. package/dist/site-model/manager.js.map +1 -1
  153. package/dist/trait-reference.d.ts +26 -2
  154. package/dist/trait-reference.d.ts.map +1 -1
  155. package/dist/trait-reference.js +129 -19
  156. package/dist/trait-reference.js.map +1 -1
  157. package/dist/types.d.ts +105 -18
  158. package/dist/types.d.ts.map +1 -1
  159. package/dist/types.js.map +1 -1
  160. package/dist/url-resolution.d.ts +58 -0
  161. package/dist/url-resolution.d.ts.map +1 -0
  162. package/dist/url-resolution.js +143 -0
  163. package/dist/url-resolution.js.map +1 -0
  164. package/dist/values/big-five-inference.d.ts +121 -0
  165. package/dist/values/big-five-inference.d.ts.map +1 -0
  166. package/dist/values/big-five-inference.js +241 -0
  167. package/dist/values/big-five-inference.js.map +1 -0
  168. package/dist/values/big-five-values.d.ts +76 -0
  169. package/dist/values/big-five-values.d.ts.map +1 -0
  170. package/dist/values/big-five-values.js +152 -0
  171. package/dist/values/big-five-values.js.map +1 -0
  172. package/dist/values/index.d.ts +1 -1
  173. package/dist/values/index.d.ts.map +1 -1
  174. package/dist/values/index.js +1 -1
  175. package/dist/values/index.js.map +1 -1
  176. package/dist/values/persona-completeness.d.ts +91 -0
  177. package/dist/values/persona-completeness.d.ts.map +1 -0
  178. package/dist/values/persona-completeness.js +114 -0
  179. package/dist/values/persona-completeness.js.map +1 -0
  180. package/dist/values/persona-diff.d.ts +50 -0
  181. package/dist/values/persona-diff.d.ts.map +1 -0
  182. package/dist/values/persona-diff.js +126 -0
  183. package/dist/values/persona-diff.js.map +1 -0
  184. package/dist/values/persona-values.d.ts +16 -0
  185. package/dist/values/persona-values.d.ts.map +1 -1
  186. package/dist/values/persona-values.js +33 -0
  187. package/dist/values/persona-values.js.map +1 -1
  188. package/dist/values/schwartz-values.d.ts +9 -1
  189. package/dist/values/schwartz-values.d.ts.map +1 -1
  190. package/dist/values/schwartz-values.js +10 -2
  191. package/dist/values/schwartz-values.js.map +1 -1
  192. package/dist/values/value-mappings.d.ts +41 -2
  193. package/dist/values/value-mappings.d.ts.map +1 -1
  194. package/dist/values/value-mappings.js +65 -8
  195. package/dist/values/value-mappings.js.map +1 -1
  196. package/dist/visual/attention-quality.d.ts +27 -0
  197. package/dist/visual/attention-quality.d.ts.map +1 -1
  198. package/dist/visual/attention-quality.js +164 -2
  199. package/dist/visual/attention-quality.js.map +1 -1
  200. package/dist/visual/attention-transport.d.ts +41 -2
  201. package/dist/visual/attention-transport.d.ts.map +1 -1
  202. package/dist/visual/attention-transport.js +98 -26
  203. package/dist/visual/attention-transport.js.map +1 -1
  204. package/dist/visual/barrier-overlay-html.d.ts +10 -0
  205. package/dist/visual/barrier-overlay-html.d.ts.map +1 -1
  206. package/dist/visual/barrier-overlay-html.js.map +1 -1
  207. package/dist/visual/cognitive-models.d.ts +109 -13
  208. package/dist/visual/cognitive-models.d.ts.map +1 -1
  209. package/dist/visual/cognitive-models.js +166 -3
  210. package/dist/visual/cognitive-models.js.map +1 -1
  211. package/dist/visual/cognitive-transport-chain.d.ts +149 -5
  212. package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
  213. package/dist/visual/cognitive-transport-chain.js +558 -30
  214. package/dist/visual/cognitive-transport-chain.js.map +1 -1
  215. package/dist/visual/cognitive-transport.d.ts +45 -3
  216. package/dist/visual/cognitive-transport.d.ts.map +1 -1
  217. package/dist/visual/cognitive-transport.js +126 -12
  218. package/dist/visual/cognitive-transport.js.map +1 -1
  219. package/dist/visual/fixation-corpus.d.ts +52 -0
  220. package/dist/visual/fixation-corpus.d.ts.map +1 -1
  221. package/dist/visual/fixation-corpus.js +95 -0
  222. package/dist/visual/fixation-corpus.js.map +1 -1
  223. package/dist/visual/llm-relevance.d.ts +193 -0
  224. package/dist/visual/llm-relevance.d.ts.map +1 -0
  225. package/dist/visual/llm-relevance.js +480 -0
  226. package/dist/visual/llm-relevance.js.map +1 -0
  227. package/dist/visual/perceptual-transport.d.ts +60 -0
  228. package/dist/visual/perceptual-transport.d.ts.map +1 -1
  229. package/dist/visual/perceptual-transport.js +228 -19
  230. package/dist/visual/perceptual-transport.js.map +1 -1
  231. package/dist/visual/persona-context.d.ts +23 -0
  232. package/dist/visual/persona-context.d.ts.map +1 -0
  233. package/dist/visual/persona-context.js +44 -0
  234. package/dist/visual/persona-context.js.map +1 -0
  235. package/package.json +2 -1
@@ -0,0 +1,707 @@
1
+ import { buildWidget, widgetUri, MCP_APP_MIME as KIT_MIME } from "./widget-kit.js";
2
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export const UI_PROBE_URI = "ui://cbrowser/probe";
4
+ export const CAPTURE_UI_PREFIX = "ui://cbrowser/capture/";
5
+ /**
6
+ * Built player HTML, keyed by capture slug.
7
+ *
8
+ * The host reads a ui:// resource in a request separate from the tool call, so
9
+ * the resource handler cannot know which capture is being asked about from the
10
+ * call itself -- the slug in the URI is the only link between them. Hence a
11
+ * store, and hence a per-capture URI rather than one fixed template URI.
12
+ *
13
+ * Bounded because these hold hundreds of KB of embedded frames each and a
14
+ * long-lived server would otherwise accumulate every capture it ever ran.
15
+ */
16
+ const CAPTURE_HTML_LIMIT = 24;
17
+ const captureHtml = new Map();
18
+ /** Store a built player and return the resource URI that will serve it. */
19
+ export function publishCaptureUi(slug, html) {
20
+ captureHtml.set(slug, html);
21
+ while (captureHtml.size > CAPTURE_HTML_LIMIT) {
22
+ const oldest = captureHtml.keys().next().value;
23
+ if (oldest === undefined)
24
+ break;
25
+ captureHtml.delete(oldest);
26
+ }
27
+ return `${CAPTURE_UI_PREFIX}${slug}`;
28
+ }
29
+ export const MCP_APP_MIME = KIT_MIME;
30
+ const PROBE_HTML = `<!doctype html><meta charset="utf-8">
31
+ <style>
32
+ :root{color-scheme:light dark}
33
+ body{font:15px/1.55 ui-sans-serif,system-ui,sans-serif;margin:0;padding:18px}
34
+ .card{border:1px solid rgba(128,128,128,.35);border-radius:10px;padding:16px}
35
+ h1{font-size:1rem;margin:0 0 .5rem}
36
+ code{font-family:ui-monospace,monospace;font-size:.85em}
37
+ .ok{color:#1a7f4b;font-weight:600}
38
+ .note{color:#666;font-size:.86rem;margin-top:.8rem}
39
+ </style>
40
+ <div class="card">
41
+ <h1>CBrowser inline UI probe</h1>
42
+ <p><span class="ok">This rendered as HTML.</span> MCP Apps UI resources display for
43
+ this connector, so the capture player can be delivered the same way instead of as a link.</p>
44
+ <p class="note">Served as <code>text/html;profile=mcp-app</code> from <code>ui://cbrowser/probe</code>.</p>
45
+ <hr style="border:0;border-top:1px solid rgba(128,128,128,.3);margin:14px 0">
46
+ <p><strong>data: URI image test</strong> &mdash; a green bar should appear below.</p>
47
+ <img id="swatch" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAoCAIAAAC6iKlyAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAo0lEQVR4nO3UQW0EARTD0MExlAxqqZdAr918VU8KAsvx836y9+8hPCi/X1EN6IDuP72N0QHdXENGNwcnHd2cRgd0cw0Z3RycdHRzGh3QzTVkdHNw0tHNaXRAN9eQ0c3BSUc3p9EB3VxDRjcHJx3dnEYHdHMNGd0cnHR0cxod0M01ZHRzcNLRzWl0QDfXkNHNwUlHN6fRAd1cQ0Y3BycdzZn+uh+XmLPx0sfXOwAAAABJRU5ErkJggg==" alt="data URI test swatch" width="120" height="40"
48
+ style="border-radius:4px;display:block">
49
+ <p class="note" id="verdict">If the bar is missing, this sandbox's CSP blocks
50
+ <code>data:</code> in <code>img-src</code>, and capture frames need another channel.</p>
51
+ <script>
52
+ // The capture player embeds frames as data: URIs -- nothing is fetched from an
53
+ // origin, because per-capture data cannot come from a static CDN and the
54
+ // sandbox allowlist does not include cbrowser.ai. Whether that works hinges
55
+ // entirely on img-src permitting data:, so the probe reports it rather than
56
+ // leaving it to be discovered after a player is built on the assumption.
57
+ var img = document.getElementById("swatch");
58
+ img.addEventListener("load", function () {
59
+ document.getElementById("verdict").textContent =
60
+ "data: URIs render here (" + img.naturalWidth + "x" + img.naturalHeight +
61
+ "). Capture frames can be embedded inline.";
62
+ });
63
+ img.addEventListener("error", function () {
64
+ document.getElementById("verdict").textContent =
65
+ "data: URI was BLOCKED by CSP. Capture frames need another channel.";
66
+ });
67
+ </script>
68
+ </div>`;
69
+ /**
70
+ * Servers that already have these resources.
71
+ *
72
+ * The call site is genuinely ambiguous: startRemoteMcpServer registers them for
73
+ * every server it builds, and the demo/enterprise entrypoints also need to
74
+ * register them because they are separate builds. Whichever one you delete, the
75
+ * other layer is wrong for some variant -- so registration is idempotent instead.
76
+ * The SDK throws on a duplicate URI, which crash-loops the process at boot.
77
+ */
78
+ const registered = new WeakSet();
79
+ /**
80
+ * Render a status payload as a panel.
81
+ *
82
+ * Deliberately the first UI resource shipped, because it needs no subresources
83
+ * at all -- counts, versions, config strings. That isolates the ui:// delivery
84
+ * path (resource rides in the tool result, host injects, iframe renders) from
85
+ * the separate question of what the sandbox CSP will fetch. One unknown at a
86
+ * time; the capture player stacks two.
87
+ *
88
+ * The JSON block it accompanies is never replaced. status is read by the model
89
+ * at least as often as by a person, and it is read to diff numbers -- a tool
90
+ * that returned only a panel would leave the model able to report that a widget
91
+ * appeared and nothing else.
92
+ */
93
+ /**
94
+ * The status view, as a spec rather than a template.
95
+ *
96
+ * This is the shape every other view now takes: name the fields, name the
97
+ * blocks, and let the kit supply chrome, hydration, theming and safety. What
98
+ * used to be 400 lines of markup and CSS per tool is the object below.
99
+ */
100
+ export const STATUS_SPEC = {
101
+ id: "status",
102
+ // The docs page, not the /tools/ marketing route. Every tool has a docs page
103
+ // -- 128 of them, generated from the wiki, with When to Use / Parameters /
104
+ // Example / Tips -- whereas only six tools have a /tools/ page and the SPA
105
+ // answers 200 with the homepage for the rest, so a link there would look
106
+ // like it worked and silently land somewhere else. (2026-07-31)
107
+ toolPage: "https://cbrowser.ai/docs/tool-status/",
108
+ title: "Status",
109
+ hero: {
110
+ variant: "gradient",
111
+ subtitle: { field: "version" },
112
+ health: {
113
+ checks: [
114
+ { field: "browsers", flag: "installed", failLabel: "browser not installed", nameKey: "name" },
115
+ { field: "directories", flag: "exists", failLabel: "missing directory", nameKey: "name" },
116
+ ],
117
+ },
118
+ facts: [
119
+ { field: "toolCount", label: "tools" },
120
+ { field: "browsers", label: "browsers", countFlag: "installed", attnWhenShort: true },
121
+ { field: "directories", label: "directories", countFlag: "exists", attnWhenShort: true },
122
+ { field: "sessions", label: "sessions" },
123
+ { field: "healCache.totalHeals", label: "self-heals" },
124
+ ],
125
+ },
126
+ blocks: [
127
+ {
128
+ type: "drawer",
129
+ title: "Details",
130
+ blocks: [
131
+ { type: "kv", title: "Configuration", field: "config" },
132
+ { type: "kv", title: "Self-healing cache", field: "healCache" },
133
+ { type: "table", title: "Directories", field: "directories" },
134
+ { type: "table", title: "Browsers", field: "browsers" },
135
+ { type: "rest", title: "Counts and paths" },
136
+ ],
137
+ },
138
+ ],
139
+ footer: { label: "Data directory:", field: "dataDir" },
140
+ };
141
+ /**
142
+ * Persona view: the three trait sections as the account editor draws them,
143
+ * each on its own colour ramp so the section is readable without the heading.
144
+ */
145
+ export const PERSONA_SPEC = {
146
+ id: "persona",
147
+ toolPage: "https://cbrowser.ai/docs/tool-persona-lookup/",
148
+ title: "Persona",
149
+ // The name leads when there is one; "Persona" is the fallback for a payload
150
+ // that has not named itself.
151
+ titleField: "name",
152
+ hero: {
153
+ variant: "gradient",
154
+ subtitle: { field: "description" },
155
+ actions: [
156
+ { label: "Customize this persona", url: "https://cbrowser.ai/account/personas" },
157
+ ],
158
+ facts: [
159
+ { field: "traits", label: "traits" },
160
+ { field: "values", label: "values" },
161
+ { field: "accessibility_traits", label: "accessibility" },
162
+ ],
163
+ },
164
+ blocks: [
165
+ // Editable. The sandbox has no network of its own, but callServerTool
166
+ // reaches the server that rendered this -- the same channel the image
167
+ // blocks already use -- so the sliders write straight back through
168
+ // persona_update rather than asking the reader to copy numbers into a
169
+ // separate tool call. (2026-08-01)
170
+ { type: "editor", title: "Cognitive traits", field: "traits", personaField: "name", ramp: "trait", describe: true },
171
+ { type: "traits", title: "Values", field: "values", ramp: "value" },
172
+ { type: "traits", title: "Accessibility traits", field: "accessibility_traits", ramp: "accessibility", describe: true },
173
+ {
174
+ type: "drawer",
175
+ title: "Details",
176
+ blocks: [
177
+ { type: "kv", title: "Demographics", field: "demographics" },
178
+ { type: "rest", title: "Other fields" },
179
+ ],
180
+ },
181
+ ],
182
+ };
183
+ /**
184
+ * One interactive surface for persona CRUD.
185
+ *
186
+ * The roster, the selected persona's editable traits, save and delete, in a
187
+ * single view. Reads ship with the tool result so switching personas costs no
188
+ * round trip; writes go back through persona_update and persona_delete, which
189
+ * already handle both stores, the built-in guard and the description-drift
190
+ * check. This is a surface over those tools, not a second copy of them --
191
+ * nine duplicate tool families in this subsystem are what a second copy costs.
192
+ * (2026-08-01)
193
+ */
194
+ export const PERSONA_MANAGER_SPEC = {
195
+ id: "persona-manager",
196
+ toolPage: "https://cbrowser.ai/docs/tool-persona-manager/",
197
+ title: "Personas",
198
+ hero: {
199
+ variant: "gradient",
200
+ subtitle: { field: "summary" },
201
+ facts: [
202
+ { field: "personas", label: "personas" },
203
+ { field: "editable", label: "editable" },
204
+ ],
205
+ actions: [{ label: "Manage on cbrowser.ai", url: "https://cbrowser.ai/account/personas" }],
206
+ },
207
+ blocks: [
208
+ { type: "manager", title: "Personas", field: "personas" },
209
+ { type: "note", title: "Creating a persona", field: "createHint" },
210
+ ],
211
+ };
212
+ /**
213
+ * A persona that was just created.
214
+ *
215
+ * The creation tools returned JSON and nothing else, so "what did I just make?"
216
+ * meant reading a trait map by eye. This shows the finished persona the way the
217
+ * lookup widget does, plus the two things only creation knows: which route
218
+ * produced the values, and which traits the caller declared the description
219
+ * could not support.
220
+ *
221
+ * Those declared-unsupported traits are the point. They sit at a population
222
+ * baseline and are indistinguishable from considered values in every later
223
+ * view; naming them at the moment of creation is the only place the
224
+ * distinction is free. (2026-08-01)
225
+ */
226
+ export const PERSONA_CREATED_SPEC = {
227
+ id: "persona-created",
228
+ toolPage: "https://cbrowser.ai/docs/tool-persona-create-from-description/",
229
+ title: "Persona created",
230
+ titleField: "persona_name",
231
+ titlePrefix: "Created: ",
232
+ hero: {
233
+ variant: "gradient",
234
+ subtitle: { field: "description" },
235
+ facts: [
236
+ { field: "traits", label: "traits" },
237
+ { field: "valuesRoute", label: "values route" },
238
+ { field: "unsupportedTraits", label: "unsupported" },
239
+ ],
240
+ actions: [{ label: "Manage personas", url: "https://cbrowser.ai/account/personas" }],
241
+ },
242
+ blocks: [
243
+ { type: "note", title: "What the trait vector says", field: "traitSummary" },
244
+ { type: "traits", title: "Cognitive traits", field: "traits", ramp: "trait", describe: true },
245
+ { type: "traits", title: "Values", field: "values", ramp: "value" },
246
+ { type: "table", title: "Declared unsupported", field: "unsupportedDetail" },
247
+ {
248
+ type: "drawer",
249
+ title: "Details",
250
+ blocks: [
251
+ { type: "kv", title: "Values route", field: "valuesDerivation" },
252
+ { type: "kv", title: "Demographics", field: "demographics" },
253
+ { type: "rest", title: "Other fields" },
254
+ ],
255
+ },
256
+ ],
257
+ };
258
+ /**
259
+ * What an update changed.
260
+ *
261
+ * A diff rather than a snapshot, because the question after an edit is not
262
+ * "what is this persona" but "what did I just do to it" — and the second is not
263
+ * answerable from the first without having memorised the before.
264
+ *
265
+ * Carries the description-drift check: traits are updated and prose is not, so
266
+ * a persona ends up with a vector saying "impatient" under a description that
267
+ * still says "patient". The prose is never rewritten here; the contradiction is
268
+ * reported and the decision left alone. (2026-08-01)
269
+ */
270
+ export const PERSONA_UPDATED_SPEC = {
271
+ id: "persona-updated",
272
+ toolPage: "https://cbrowser.ai/docs/tool-persona-update/",
273
+ title: "Persona updated",
274
+ titleField: "persona",
275
+ titlePrefix: "Updated: ",
276
+ hero: {
277
+ variant: "gradient",
278
+ subtitle: { field: "note" },
279
+ facts: [
280
+ { field: "changed", label: "fields changed" },
281
+ { field: "descriptionDrift", label: "description conflicts" },
282
+ ],
283
+ actions: [{ label: "Manage personas", url: "https://cbrowser.ai/account/personas" }],
284
+ },
285
+ blocks: [
286
+ { type: "table", title: "What changed", field: "changes" },
287
+ { type: "table", title: "Description now contradicts the traits", field: "descriptionDrift" },
288
+ { type: "note", title: "What the trait vector says now", field: "traitSummary" },
289
+ { type: "kv", title: "Where the write landed", field: "stores" },
290
+ {
291
+ type: "drawer",
292
+ title: "Details",
293
+ blocks: [
294
+ { type: "traits", title: "Traits after update", field: "traits", ramp: "trait", describe: true },
295
+ { type: "rest", title: "Other fields" },
296
+ ],
297
+ },
298
+ ],
299
+ };
300
+ /**
301
+ * Attention heatmap view.
302
+ *
303
+ * The image is the finding here -- a JSON list of scores is a lossy
304
+ * description of where a persona actually looks. It is fetched through
305
+ * artifact_fetch rather than embedded or linked: the result cannot carry it
306
+ * past the host's size cap, and the sandbox will not load cbrowser.ai.
307
+ */
308
+ export const ATTENTION_SPEC = {
309
+ id: "attention",
310
+ toolPage: "https://cbrowser.ai/docs/tool-attention-analysis/",
311
+ title: "Attention",
312
+ // Field names taken from a real tool result rather than assumed. The first
313
+ // pass guessed topElements/elementsAnalyzed/url; the payload actually carries
314
+ // topAttentionAreas, attentionQuality and no url at all, so every block was
315
+ // pointed at a field that did not exist.
316
+ titleField: "persona",
317
+ hero: {
318
+ variant: "gradient",
319
+ subtitle: { field: "attentionReasoning" },
320
+ facts: [
321
+ { field: "topAttentionAreas", label: "areas" },
322
+ { field: "attentionQuality", label: "quality" },
323
+ { field: "alignmentScore", label: "alignment" },
324
+ { field: "concentration", label: "concentration" },
325
+ ],
326
+ },
327
+ blocks: [
328
+ {
329
+ type: "image",
330
+ title: "Predicted attention",
331
+ fetchTool: "artifact_fetch",
332
+ fetchArgs: { file: "heatmapFile" },
333
+ caption: "heatmapNote",
334
+ },
335
+ { type: "table", title: "Where attention lands", field: "topAttentionAreas" },
336
+ {
337
+ type: "drawer",
338
+ title: "Details",
339
+ blocks: [{ type: "rest", title: "All fields" }],
340
+ },
341
+ ],
342
+ };
343
+ /**
344
+ * Screenshot view.
345
+ *
346
+ * The image is the whole result here, so the view is mostly frame: the shot
347
+ * at full width, then the handful of facts that say what it is a picture of.
348
+ * Fetched through artifact_fetch rather than read from the result, because a
349
+ * compressed screenshot measured 144,731 base64 characters against a ~150k
350
+ * cap -- it fits today and stops fitting on a denser page, and a view that
351
+ * works until the page gets busy is worse than one that never did.
352
+ */
353
+ export const SCREENSHOT_SPEC = {
354
+ id: "screenshot",
355
+ toolPage: "https://cbrowser.ai/docs/tool-screenshot/",
356
+ title: "Screenshot",
357
+ titleField: "url",
358
+ hero: {
359
+ variant: "gradient",
360
+ subtitle: { field: "title" },
361
+ facts: [
362
+ { field: "viewport", label: "viewport" },
363
+ { field: "viewport_forced", label: "forced to" },
364
+ { field: "fullPage", label: "full page" },
365
+ ],
366
+ },
367
+ blocks: [
368
+ {
369
+ type: "image",
370
+ title: "",
371
+ fetchTool: "artifact_fetch",
372
+ fetchArgs: { file: "screenshotFile" },
373
+ caption: "_screenshotNote",
374
+ },
375
+ {
376
+ type: "drawer",
377
+ title: "Details",
378
+ blocks: [{ type: "rest", title: "All fields" }],
379
+ },
380
+ ],
381
+ footer: { label: "Saved:", field: "screenshot" },
382
+ };
383
+ /**
384
+ * Trait lookup view.
385
+ *
386
+ * The five levels are the answer: what a reader wants from "patience at 0.30"
387
+ * is where that sits relative to the other four bands and what it means
388
+ * behaviourally, which a JSON array of level objects does not convey.
389
+ *
390
+ * Bands use the same red-to-green ramp as the persona meters, so 0.30 reads
391
+ * identically in both views rather than being one colour here and another
392
+ * there.
393
+ */
394
+ export const TRAIT_SPEC = {
395
+ // Served under a URI the host has never seen.
396
+ //
397
+ // The bisect proved the tool side is fine: pointed at ui://cbrowser/status
398
+ // it renders. So the fault is specific to this resource, which parses,
399
+ // registers and renders locally under test. The remaining explanation is a
400
+ // cached negative for the URI -- the host was told to render
401
+ // ui://cbrowser/trait while the resource had not yet deployed, and kept the
402
+ // failure. A fresh URI cannot have a stale entry.
403
+ id: "trait-v2",
404
+ toolPage: "https://cbrowser.ai/docs/tool-persona-trait-lookup/",
405
+ title: "Trait",
406
+ titleField: "trait",
407
+ hero: {
408
+ variant: "gradient",
409
+ subtitle: { field: "description" },
410
+ facts: [
411
+ { field: "value", label: "value" },
412
+ { field: "label", label: "level" },
413
+ ],
414
+ },
415
+ blocks: [
416
+ {
417
+ type: "levels",
418
+ title: "Where this value sits",
419
+ field: "allLevels",
420
+ valueField: "value",
421
+ labelField: "label",
422
+ behaviorsField: "behaviors",
423
+ },
424
+ { type: "note", title: "Research basis", field: "researchBasis" },
425
+ {
426
+ type: "drawer",
427
+ title: "Every level",
428
+ blocks: [{ type: "table", title: "Levels", field: "allLevels" }],
429
+ },
430
+ ],
431
+ };
432
+ /**
433
+ * Bug hunt view. Field names read from a real run, not assumed:
434
+ * bugs[] carries type, severity, description, url, selector, recommendation.
435
+ */
436
+ export const BUGS_SPEC = {
437
+ id: "bugs",
438
+ toolPage: "https://cbrowser.ai/docs/tool-hunt-bugs/",
439
+ title: "Bug hunt",
440
+ hero: {
441
+ variant: "gradient",
442
+ // No subtitle. duration is milliseconds, and rendered as a bare subtitle it
443
+ // read as "8563" under the title with nothing saying what it counted.
444
+ facts: [
445
+ // Distinct findings, not raw DOM occurrences: a carousel repeating five
446
+ // broken images across duplicated slides read as ten. The occurrence
447
+ // count is still in Run info for anyone who wants it.
448
+ { field: "distinctBugs", label: "bugs" },
449
+ { field: "pagesVisited", label: "pages crawled" },
450
+ { field: "duration", label: "ms" },
451
+ ],
452
+ },
453
+ blocks: [
454
+ // Grouped by page. A flat list across a multi-page crawl says nothing
455
+ // about which page a finding belongs to, and page attribution is exactly
456
+ // what was missing when the result was silently truncated to page one.
457
+ { type: "tabs", title: "Findings by page", field: "bugs", groupBy: "url",
458
+ render: { type: "findings", title: "Findings, worst first", field: "bugs",
459
+ textKey: "description", severityKey: "severity", detailKey: "recommendation" } },
460
+ { type: "drawer", title: "Details", blocks: [
461
+ // description and recommendation are shown in full by the findings list
462
+ // above; repeating them here is what made this table 1543px wide.
463
+ { type: "table", title: "Every bug", field: "bugs",
464
+ columns: ["severity", "type", "url", "selector", "occurrences"] },
465
+ { type: "rest", title: "Run info" },
466
+ ] },
467
+ ],
468
+ };
469
+ export function buildBugsTemplate() {
470
+ return buildWidget(BUGS_SPEC);
471
+ }
472
+ /**
473
+ * Empathy audit view.
474
+ *
475
+ * Barriers ranked by severity is the artifact worth showing someone -- it is
476
+ * the output a product team acts on, and nested JSON buries the ordering that
477
+ * makes it actionable.
478
+ */
479
+ export const EMPATHY_SPEC = {
480
+ id: "empathy",
481
+ toolPage: "https://cbrowser.ai/docs/tool-empathy-audit/",
482
+ title: "Empathy audit",
483
+ // The persona is the subject of the audit, so it belongs in the title. The
484
+ // bare view name did not say who was audited, and the tool tests exactly one
485
+ // persona per call.
486
+ titleField: "testedPersona",
487
+ titlePrefix: "Empathy Audit: ",
488
+ hero: {
489
+ variant: "gradient",
490
+ subtitle: { field: "scopeNote" },
491
+ facts: [
492
+ { field: "overallScore", label: "score" },
493
+ { field: "topBarriers", label: "barriers" },
494
+ { field: "resultsSummary", label: "personas" },
495
+ { field: "device", label: "device" },
496
+ ],
497
+ },
498
+ blocks: [
499
+ // The overlay leads. It is what makes a score falsifiable by eye --
500
+ // barriers drawn on the real page at their real positions -- and the
501
+ // ranked list is the detail behind it.
502
+ { type: "overlay", title: "Barriers on the page", field: "pageScreenshots",
503
+ fetchTool: "artifact_fetch" },
504
+ { type: "findings", title: "Barriers, worst first", field: "topBarriers",
505
+ textKey: "description", severityKey: "severityForPersona", detailKey: "type" },
506
+ { type: "drawer", title: "Details", blocks: [
507
+ { type: "table", title: "Per persona", field: "resultsSummary" },
508
+ { type: "table", title: "WCAG violations", field: "allWcagViolations" },
509
+ { type: "table", title: "Remediation", field: "topRemediation" },
510
+ { type: "rest", title: "Run info" },
511
+ ] },
512
+ ],
513
+ };
514
+ /**
515
+ * Cognitive effort: the sequential transport chain, and what running it in
516
+ * sequence costs over running it in parallel.
517
+ *
518
+ * The JSON answers "how expensive is this page for this persona" with a number,
519
+ * and buries the reason inside `cognitiveTransportCost.chainCoefficient`.
520
+ * The chain block puts the reason on screen: one bar per layer with
521
+ * the bottleneck named, then the sum-of-parts against the actual sequential
522
+ * total. The gap between those last two is the claim the whole model rests on.
523
+ */
524
+ export const EFFORT_SPEC = {
525
+ id: "effort",
526
+ toolPage: "https://cbrowser.ai/docs/tool-cognitive-effort/",
527
+ title: "Cognitive effort",
528
+ // Who was measured belongs in the title; the cost is meaningless without them.
529
+ titleField: "persona",
530
+ titlePrefix: "Cognitive Effort: ",
531
+ hero: {
532
+ variant: "gradient",
533
+ subtitle: { field: "interpretation" },
534
+ facts: [
535
+ { field: "cognitiveTransportCost.total", label: "transport cost" },
536
+ { field: "abandonmentRisk", label: "abandonment risk" },
537
+ { field: "bottleneck", label: "bottleneck" },
538
+ { field: "url", label: "page" },
539
+ ],
540
+ },
541
+ blocks: [
542
+ // Leads, because it is the finding. Everything below is detail behind it.
543
+ // Title carries no layer COUNT on purpose. It named a count and went stale
544
+ // within the hour when readability was split into decoding and attention
545
+ // (2026-08-02), so the widget captioned seven bars as six. The bars state
546
+ // the count themselves; a number in prose is a second copy that drifts.
547
+ { type: "chain", title: "The sequential transport chain", field: "layers",
548
+ nameKey: "name", costKey: "cost", capacityKey: "capacityConsumed",
549
+ bottleneckField: "bottleneck",
550
+ additiveField: "cognitiveTransportCost.additive",
551
+ totalField: "cognitiveTransportCost.total",
552
+ // Each bar opens into that layer's overlay on the real page. Layers with
553
+ // no overlay stay inert and say why.
554
+ overlaysField: "layerOverlays", fetchTool: "artifact_fetch" },
555
+ // Surfaced when the run was told, or forced, to treat the persona as new to
556
+ // the site. It changes every number above and is easy to miss in JSON.
557
+ { type: "note", field: "familiarityWarning" },
558
+ { type: "note", field: "languageWarning" },
559
+ // Everything below the chain is behind an accordion.
560
+ //
561
+ // The chain IS the finding; the rest is the evidence behind it, and eight
562
+ // stacked cards made the panel a scroll rather than an answer. Native
563
+ // <details>, so each one is keyboard operable and announced as an expander
564
+ // without any ARIA of our own.
565
+ //
566
+ // The two warnings above stay OUT of the accordions on purpose: a familiarity
567
+ // downgrade or a language mismatch changes how every number below should be
568
+ // read, and a warning behind a click is a warning nobody reads.
569
+ { type: "drawer", title: "Where the cost sits", blocks: [
570
+ { type: "kv", title: "Deficit vs surplus", field: "deficitVsSurplus" },
571
+ { type: "kv", title: "Layer interactions", field: "interactions" },
572
+ ] },
573
+ { type: "drawer", title: "Motor accessibility", blocks: [
574
+ { type: "kv", title: "Summary", fields: ["motorAccessibility.score", "motorAccessibility.barriers"] },
575
+ { type: "table", title: "Hardest targets", field: "motorAccessibility.elements",
576
+ columns: ["element", "hitProbability", "movementTimeMs"] },
577
+ ] },
578
+ { type: "drawer", title: "Readability", blocks: [
579
+ // legibilityQuality, not score: high is GOOD here, while the readability
580
+ // LAYER above is a cost where high is bad. Same word, opposite direction.
581
+ { type: "kv", title: "Summary", fields: ["readability.legibilityQuality", "readability.averageWPM"] },
582
+ { type: "findings", title: "Hardest block", field: "readability.hardestBlock", textKey: "0" },
583
+ ] },
584
+ { type: "drawer", title: "How these numbers are built", blocks: [
585
+ { type: "kv", title: "Chain", fields: ["cognitiveTransportCost.raw", "cognitiveTransportCost.chainCoefficient"] },
586
+ { type: "note", field: "cognitiveTransportCost.chainNote" },
587
+ { type: "note", field: "abandonmentBasis" },
588
+ { type: "note", field: "siteFamiliarityEffect" },
589
+ ] },
590
+ { type: "drawer", title: "Everything else", blocks: [
591
+ { type: "rest", title: "Remaining fields" },
592
+ ] },
593
+ ],
594
+ };
595
+ export function buildEffortTemplate() {
596
+ return buildWidget(EFFORT_SPEC);
597
+ }
598
+ export function buildEmpathyTemplate() {
599
+ return buildWidget(EMPATHY_SPEC);
600
+ }
601
+ export function buildTraitTemplate() {
602
+ return buildWidget(TRAIT_SPEC);
603
+ }
604
+ export function buildScreenshotTemplate() {
605
+ return buildWidget(SCREENSHOT_SPEC);
606
+ }
607
+ export function buildAttentionTemplate() {
608
+ return buildWidget(ATTENTION_SPEC);
609
+ }
610
+ export function buildPersonaTemplate() {
611
+ return buildWidget(PERSONA_SPEC);
612
+ }
613
+ export function buildPersonaManagerTemplate() {
614
+ return buildWidget(PERSONA_MANAGER_SPEC);
615
+ }
616
+ export function buildPersonaCreatedTemplate() {
617
+ return buildWidget(PERSONA_CREATED_SPEC);
618
+ }
619
+ export function buildPersonaUpdatedTemplate() {
620
+ return buildWidget(PERSONA_UPDATED_SPEC);
621
+ }
622
+ export function buildStatusTemplate() {
623
+ return buildWidget(STATUS_SPEC);
624
+ }
625
+ /** Read back a published capture panel by its resource URI. */
626
+ export function readCaptureUi(uri) {
627
+ return captureHtml.get(uri.startsWith(CAPTURE_UI_PREFIX) ? uri.slice(CAPTURE_UI_PREFIX.length) : uri);
628
+ }
629
+ /** Register the inline-UI resources on a server. Safe to call more than once. */
630
+ export function registerUiResources(server) {
631
+ if (registered.has(server))
632
+ return;
633
+ registered.add(server);
634
+ server.registerResource("cbrowser-ui-probe", UI_PROBE_URI, { description: "Inline UI render probe (MCP Apps)", mimeType: MCP_APP_MIME }, async () => ({
635
+ contents: [{ uri: UI_PROBE_URI, mimeType: MCP_APP_MIME, text: PROBE_HTML }],
636
+ }));
637
+ server.registerResource("cbrowser-status-ui", "ui://cbrowser/status", { description: "CBrowser environment status panel", mimeType: MCP_APP_MIME }, async () => ({
638
+ contents: [{
639
+ uri: "ui://cbrowser/status",
640
+ mimeType: MCP_APP_MIME,
641
+ // Read out of band there is no payload to render, so this stands in.
642
+ // The populated panel travels inside the status tool result itself.
643
+ text: buildStatusTemplate(),
644
+ }],
645
+ }));
646
+ server.registerResource("cbrowser-bugs-ui", widgetUri("bugs"), { description: "Bug hunt findings ranked by severity", mimeType: MCP_APP_MIME }, async () => ({
647
+ contents: [{ uri: widgetUri("bugs"), mimeType: MCP_APP_MIME, text: buildBugsTemplate() }],
648
+ }));
649
+ server.registerResource("cbrowser-empathy-ui", widgetUri("empathy"), { description: "Accessibility barriers ranked by severity", mimeType: MCP_APP_MIME }, async () => ({
650
+ contents: [{ uri: widgetUri("empathy"), mimeType: MCP_APP_MIME, text: buildEmpathyTemplate() }],
651
+ }));
652
+ server.registerResource("cbrowser-effort-ui", widgetUri("effort"), { description: "Sequential cognitive transport chain with its bottleneck", mimeType: MCP_APP_MIME }, async () => ({
653
+ contents: [{ uri: widgetUri("effort"), mimeType: MCP_APP_MIME, text: buildEffortTemplate() }],
654
+ }));
655
+ server.registerResource("cbrowser-trait-ui", widgetUri("trait-v2"), { description: "Trait level scale with behavioural bands", mimeType: MCP_APP_MIME }, async () => ({
656
+ contents: [{ uri: widgetUri("trait-v2"), mimeType: MCP_APP_MIME, text: buildTraitTemplate() }],
657
+ }));
658
+ server.registerResource("cbrowser-screenshot-ui", widgetUri("screenshot"), { description: "Screenshot with page and viewport context", mimeType: MCP_APP_MIME }, async () => ({
659
+ contents: [{ uri: widgetUri("screenshot"), mimeType: MCP_APP_MIME, text: buildScreenshotTemplate() }],
660
+ }));
661
+ server.registerResource("cbrowser-attention-ui", widgetUri("attention"), { description: "Attention heatmap with ranked elements", mimeType: MCP_APP_MIME }, async () => ({
662
+ contents: [{ uri: widgetUri("attention"), mimeType: MCP_APP_MIME, text: buildAttentionTemplate() }],
663
+ }));
664
+ server.registerResource("cbrowser-persona-ui", widgetUri("persona"), { description: "Persona traits, values and accessibility profile", mimeType: MCP_APP_MIME }, async () => ({
665
+ contents: [{ uri: widgetUri("persona"), mimeType: MCP_APP_MIME, text: buildPersonaTemplate() }],
666
+ }));
667
+ server.registerResource("cbrowser-persona-manager-ui", widgetUri("persona-manager"), { description: "Interactive persona CRUD: roster, inline trait editor, save and delete", mimeType: MCP_APP_MIME }, async () => ({
668
+ contents: [{ uri: widgetUri("persona-manager"), mimeType: MCP_APP_MIME, text: buildPersonaManagerTemplate() }],
669
+ }));
670
+ server.registerResource("cbrowser-persona-created-ui", widgetUri("persona-created"), { description: "A persona that was just created, with its values route and any traits declared unsupported", mimeType: MCP_APP_MIME }, async () => ({
671
+ contents: [{ uri: widgetUri("persona-created"), mimeType: MCP_APP_MIME, text: buildPersonaCreatedTemplate() }],
672
+ }));
673
+ server.registerResource("cbrowser-persona-updated-ui", widgetUri("persona-updated"), { description: "What a persona update changed, and whether the description still matches the traits", mimeType: MCP_APP_MIME }, async () => ({
674
+ contents: [{ uri: widgetUri("persona-updated"), mimeType: MCP_APP_MIME, text: buildPersonaUpdatedTemplate() }],
675
+ }));
676
+ server.registerResource("cbrowser-capture-ui",
677
+ // A template with no list callback never appears in resources/list, so a
678
+ // host that discovers views by listing would never find a capture panel --
679
+ // and it fails silently, the way a URI mismatch does. Published captures are
680
+ // enumerated instead, which is cheap because the store is bounded at 24.
681
+ new ResourceTemplate(`${CAPTURE_UI_PREFIX}{slug}`, {
682
+ list: async () => ({
683
+ resources: [...captureHtml.keys()].map((slug) => ({
684
+ uri: `${CAPTURE_UI_PREFIX}${slug}`,
685
+ name: `Capture player: ${slug}`,
686
+ mimeType: MCP_APP_MIME,
687
+ })),
688
+ }),
689
+ }), { description: "Inline capture player with embedded frames", mimeType: MCP_APP_MIME }, async (uri, vars) => {
690
+ const slug = String(Array.isArray(vars.slug) ? vars.slug[0] : vars.slug ?? "");
691
+ const html = captureHtml.get(slug);
692
+ return {
693
+ contents: [{
694
+ uri: uri.href,
695
+ mimeType: MCP_APP_MIME,
696
+ // An expired slug is normal, not an error: the store is bounded and a
697
+ // host may re-read an old panel. Say so plainly instead of throwing.
698
+ text: html ?? `<!doctype html><meta charset="utf-8">
699
+ <div style="font:15px/1.5 ui-sans-serif,system-ui,sans-serif;padding:18px">
700
+ <p>This capture is no longer held in memory. Re-run the capture, or open the full
701
+ player link from the original tool result.</p>
702
+ </div>`,
703
+ }],
704
+ };
705
+ });
706
+ }
707
+ //# sourceMappingURL=ui-resources.js.map