@theoremjs/studio 0.0.0-stage → 0.3.2

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 (473) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -2
  3. package/agreement.d.ts +14 -0
  4. package/agreement.js +80 -0
  5. package/asks.d.ts +47 -0
  6. package/asks.js +54 -0
  7. package/browser-connection.d.ts +49 -0
  8. package/browser-connection.js +215 -0
  9. package/browser-engine.d.ts +11 -0
  10. package/browser-engine.js +11 -0
  11. package/browser-execution.d.ts +6 -0
  12. package/browser-execution.js +18 -0
  13. package/browser-live.d.ts +4 -0
  14. package/browser-live.js +132 -0
  15. package/browser-transport.d.ts +33 -0
  16. package/browser-transport.js +229 -0
  17. package/browser.d.ts +4 -0
  18. package/browser.js +4 -0
  19. package/canonical.d.ts +10 -0
  20. package/canonical.js +25 -0
  21. package/compile-workspace.d.ts +28 -0
  22. package/compile-workspace.js +295 -0
  23. package/compile.d.ts +65 -0
  24. package/compile.js +1605 -0
  25. package/concierge-demo.d.ts +17 -0
  26. package/concierge-demo.js +1230 -0
  27. package/conversation-key.d.ts +5 -0
  28. package/conversation-key.js +10 -0
  29. package/decision-example-images.d.ts +30 -0
  30. package/decision-example-images.js +30 -0
  31. package/deno.json +3 -0
  32. package/draft.d.ts +407 -0
  33. package/draft.js +505 -0
  34. package/editors.d.ts +29 -0
  35. package/editors.js +51 -0
  36. package/example-embedding-files.d.ts +11 -0
  37. package/example-embedding-files.js +20 -0
  38. package/example.d.ts +36 -0
  39. package/example.js +357 -0
  40. package/guardrail-probe.d.ts +100 -0
  41. package/guardrail-probe.js +722 -0
  42. package/held-credentials.d.ts +28 -0
  43. package/held-credentials.js +87 -0
  44. package/installed.mjs +40 -0
  45. package/lent-keys.d.ts +122 -0
  46. package/lent-keys.js +47 -0
  47. package/line-diff.d.ts +15 -0
  48. package/line-diff.js +41 -0
  49. package/live-connection.d.ts +22 -0
  50. package/live-connection.js +27 -0
  51. package/live-session-bridge.d.ts +15 -0
  52. package/live-session-bridge.js +202 -0
  53. package/map-line.d.ts +19 -0
  54. package/map-line.js +42 -0
  55. package/map.d.ts +154 -0
  56. package/map.js +525 -0
  57. package/media-accept.d.ts +19 -0
  58. package/media-accept.js +65 -0
  59. package/mod.d.ts +39 -0
  60. package/mod.js +33 -0
  61. package/origins.d.ts +52 -0
  62. package/origins.js +165 -0
  63. package/package.json +85 -4
  64. package/page/assets/Banner-BISz3nXA.js +2 -0
  65. package/page/assets/BottomSheet-BwfBDtje.js +1 -0
  66. package/page/assets/Card-Lp-0P5eL.js +1 -0
  67. package/page/assets/CardWall-CBRLdDZW.js +1 -0
  68. package/page/assets/Center-nHS1JSux.js +1 -0
  69. package/page/assets/CheckboxInput-Cj5pk-KI.js +2 -0
  70. package/page/assets/CodeBlock-C7XJ4ioE.js +51 -0
  71. package/page/assets/CollapsibleGroup-N7_SOjbm.js +2 -0
  72. package/page/assets/ContextMenu-CBu9H-9H.js +2 -0
  73. package/page/assets/DateInput-BGrQ5tPC.js +1 -0
  74. package/page/assets/Dialog-DELWQo-v.js +1 -0
  75. package/page/assets/EmptyState-C8W4JKBZ.js +1 -0
  76. package/page/assets/HoverCard-D1Y1ltWQ.js +1 -0
  77. package/page/assets/Icon-JrysT8aU.js +3 -0
  78. package/page/assets/IconAlertTriangle-BqAEqqdU.js +1 -0
  79. package/page/assets/IconButton-DlOGzJIX.js +1 -0
  80. package/page/assets/IconEye-BB5aY-wQ.js +1 -0
  81. package/page/assets/IconFileText-6zOFKKrF.js +1 -0
  82. package/page/assets/IconSearch-BTx66a40.js +1 -0
  83. package/page/assets/IconShield-DDOh7Gr5.js +1 -0
  84. package/page/assets/ItemSwipeLayer-Ckn4X6h9.js +1 -0
  85. package/page/assets/PaneState-BvA2tLQg.js +1 -0
  86. package/page/assets/PaneState-eUzUkNE8.css +1 -0
  87. package/page/assets/ScrollableArea-Ckugdj1N.js +1 -0
  88. package/page/assets/Section-DJ97F7QE.js +1 -0
  89. package/page/assets/SegmentedControlItem-h-aaXzyS.js +1 -0
  90. package/page/assets/SidePanel-BL32abk8.js +1 -0
  91. package/page/assets/StackItem-CQviqAfM.js +1 -0
  92. package/page/assets/Text-bum6zWWx.js +2 -0
  93. package/page/assets/TimestampHoverCard-DQIG0RZF.js +1 -0
  94. package/page/assets/ToolResultCharts-CgshSv9Q.js +1 -0
  95. package/page/assets/Tooltip-BH9xiRMl.js +1 -0
  96. package/page/assets/TraceInspector-_yZiz05W.js +1 -0
  97. package/page/assets/TraceOverview-BPO-HIYK.js +57 -0
  98. package/page/assets/VStack-CkVMcNiK.js +1 -0
  99. package/page/assets/_virtual_studio-type-sources-ByxqQaOG.js +12936 -0
  100. package/page/assets/arrive-DvbqpeRS.js +1 -0
  101. package/page/assets/browser-engine-CYq9DQAM.js +9 -0
  102. package/page/assets/browser-transport-D-2jDYe7.js +47 -0
  103. package/page/assets/codicon-DF1abBS2.ttf +0 -0
  104. package/page/assets/compile-egress-BSxOdX4K.js +23 -0
  105. package/page/assets/container.stylex-BmOi-8DQ.js +1 -0
  106. package/page/assets/createReactComponent-CZwsv6DD.js +1 -0
  107. package/page/assets/decision-example-images-CZGr7YIT.js +1 -0
  108. package/page/assets/devWarning-DxuplmRl.js +1 -0
  109. package/page/assets/editor-DjpsE8to.css +1 -0
  110. package/page/assets/editor.api-Cd3ViPQa.js +908 -0
  111. package/page/assets/editor.worker-6GY8RhDc.js +1 -0
  112. package/page/assets/editor.worker-BmdWdrHZ.js +33 -0
  113. package/page/assets/editors-BJX_S59A.js +1 -0
  114. package/page/assets/egress-automata-B5ZGE1mT.js +1 -0
  115. package/page/assets/embedding-space-view-CfQBPDxD.js +140 -0
  116. package/page/assets/encode-files-Bc3HJyAh.js +1 -0
  117. package/page/assets/export-agent-C3yyzXH8.js +43 -0
  118. package/page/assets/figtree-latin-400-normal-BD4aNku5.woff +0 -0
  119. package/page/assets/figtree-latin-400-normal-g7Dtegnw.woff2 +0 -0
  120. package/page/assets/figtree-latin-500-normal-BWnGEVsr.woff2 +0 -0
  121. package/page/assets/figtree-latin-500-normal-B_yy1phn.woff +0 -0
  122. package/page/assets/figtree-latin-600-normal-BM_oTo4n.woff +0 -0
  123. package/page/assets/figtree-latin-600-normal-Cv_xCTDl.woff2 +0 -0
  124. package/page/assets/figtree-latin-700-normal-BCCt2T1q.woff +0 -0
  125. package/page/assets/figtree-latin-700-normal-th6qEP7c.woff2 +0 -0
  126. package/page/assets/focusOutline.stylex-BT0kfa4t.js +1 -0
  127. package/page/assets/focusableSelector-DXZeMU85.js +1 -0
  128. package/page/assets/groupStyles-C7Ter7zC.js +1 -0
  129. package/page/assets/guardrail-tester-Gut5xK4a.js +9 -0
  130. package/page/assets/host-patterns-C9RMP16Q.js +1 -0
  131. package/page/assets/index-CvvmFKgS.js +105 -0
  132. package/page/assets/index-DOaHlqBo.css +1 -0
  133. package/page/assets/isRtlElement-BG33-k_H.js +1 -0
  134. package/page/assets/labels-provider-4TIWDvsE.js +3 -0
  135. package/page/assets/lent-keys-C32ems-I.js +1 -0
  136. package/page/assets/live-ingress-lDUGPwth.js +1 -0
  137. package/page/assets/live-messages-CnkL4GWi.js +1 -0
  138. package/page/assets/mergeProps-ptS-gIMz.js +1 -0
  139. package/page/assets/padding.stylex-B76907Zf.js +1 -0
  140. package/page/assets/preload-helper-DpV6A1kQ.js +1 -0
  141. package/page/assets/reader-BOCCYaRp.js +7 -0
  142. package/page/assets/register-CDgivxvX.js +2 -0
  143. package/page/assets/rolldown-runtime-hePW80VL.js +1 -0
  144. package/page/assets/schemas-BQ9Jydzb.js +56 -0
  145. package/page/assets/shaped-data-BcarWqd5.js +16 -0
  146. package/page/assets/sharedResizeObserver-4U9Xq5Oq.js +1 -0
  147. package/page/assets/stub-DhdcU_Ew.js +471 -0
  148. package/page/assets/studio-code-pane-C_SAbcOa.js +8 -0
  149. package/page/assets/studio-console-DZwlEsvr.js +3 -0
  150. package/page/assets/studio-decision-B0FnCAXA.js +1 -0
  151. package/page/assets/studio-embedding-CmA51HVo.js +2 -0
  152. package/page/assets/studio-host-console-By7hqu2l.js +2 -0
  153. package/page/assets/studio-live-BrcVQcyV.js +1 -0
  154. package/page/assets/studio-map-B_xSia-z.js +1 -0
  155. package/page/assets/studio-map-Bb06Tw1F.css +1 -0
  156. package/page/assets/studio-monaco-CbKf90i6.css +1 -0
  157. package/page/assets/studio-monaco-xfyfezQS.js +34 -0
  158. package/page/assets/studio-ts.worker-B-EXG4Sv.js +67741 -0
  159. package/page/assets/studio-ts.worker-BXtW3z-O.js +1 -0
  160. package/page/assets/studio-type-checker-BOvxudki.js +23 -0
  161. package/page/assets/themeProps-3IqahpCr.js +1 -0
  162. package/page/assets/tokens.stylex-DkHf0P9C.js +1 -0
  163. package/page/assets/tool-schema-idaOe2nI.js +18 -0
  164. package/page/assets/trace-patterns-DnYJa0nI.js +1 -0
  165. package/page/assets/trace-record-C3zNksA_.js +1 -0
  166. package/page/assets/trace-schema-9CdEyG6Q.js +1 -0
  167. package/page/assets/tree-DqyAMsbI.js +3 -0
  168. package/page/assets/turn-client-Du3Um3Mg.js +2 -0
  169. package/page/assets/types-BhfXQCLx.js +1 -0
  170. package/page/assets/typescript-20GgZ8PJ.js +1 -0
  171. package/page/assets/use-one-call-Hcs8YBp1.js +1 -0
  172. package/page/assets/useAdaptivePresentation-BzmPCddb.js +1 -0
  173. package/page/assets/useClipboard-BgGRxRMJ.js +1 -0
  174. package/page/assets/useContainerReveal-BFy8N_QU.js +1 -0
  175. package/page/assets/useDevWarning-Z9SoixOP.js +1 -0
  176. package/page/assets/useLayerDismissal-B_G7IWpC.js +1 -0
  177. package/page/assets/useMediaQuery-BCT0NUeZ.js +1 -0
  178. package/page/assets/useScrollLock-Dyf6PHJt.js +1 -0
  179. package/page/assets/useScrollableArea-0NzdiBCq.js +1 -0
  180. package/page/assets/useTooltip-CJM_xhLB.js +1 -0
  181. package/page/assets/workspace-DZALqEDX.js +1 -0
  182. package/page/assets/zip-DTcEfps6.js +1 -0
  183. package/page/index.html +90 -0
  184. package/page.mjs +49 -0
  185. package/people.d.ts +87 -0
  186. package/people.js +110 -0
  187. package/policy.d.ts +43 -0
  188. package/policy.js +102 -0
  189. package/ports.d.mts +32 -0
  190. package/ports.mjs +85 -0
  191. package/probe-battery.d.ts +13 -0
  192. package/probe-battery.js +404 -0
  193. package/providers.d.ts +149 -0
  194. package/providers.js +306 -0
  195. package/read-source.d.ts +48 -0
  196. package/read-source.js +1301 -0
  197. package/read-steps.d.ts +23 -0
  198. package/read-steps.js +2 -0
  199. package/reader.d.ts +11 -0
  200. package/reader.js +11 -0
  201. package/registrations.d.ts +83 -0
  202. package/registrations.js +1 -0
  203. package/requirements.d.ts +22 -0
  204. package/requirements.js +38 -0
  205. package/run-payload-core.d.ts +28 -0
  206. package/run-payload-core.js +123 -0
  207. package/run-payload.d.ts +32 -0
  208. package/run-payload.js +26 -0
  209. package/runtime-scope.d.ts +84 -0
  210. package/runtime-scope.js +308 -0
  211. package/runtime.d.ts +84 -0
  212. package/runtime.js +387 -0
  213. package/section-notes.d.ts +33 -0
  214. package/section-notes.js +38 -0
  215. package/serve-page.mjs +96 -0
  216. package/server/draft-decide.d.ts +72 -0
  217. package/server/draft-decide.js +111 -0
  218. package/server/draft-embedding.d.ts +3 -0
  219. package/server/draft-embedding.js +35 -0
  220. package/server/draft-handler.d.ts +82 -0
  221. package/server/draft-handler.js +213 -0
  222. package/server/draft-probe.d.ts +14 -0
  223. package/server/draft-probe.js +61 -0
  224. package/server/draft-replies.d.ts +22 -0
  225. package/server/draft-replies.js +46 -0
  226. package/server/edited-session.d.ts +28 -0
  227. package/server/edited-session.js +83 -0
  228. package/server/edits.d.ts +38 -0
  229. package/server/edits.js +195 -0
  230. package/server/handler.d.ts +133 -0
  231. package/server/handler.js +598 -0
  232. package/server/lent-draft.d.ts +19 -0
  233. package/server/lent-draft.js +23 -0
  234. package/server/live.d.ts +15 -0
  235. package/server/live.js +96 -0
  236. package/server/open-editor.d.ts +63 -0
  237. package/server/open-editor.js +115 -0
  238. package/server/origins.d.ts +18 -0
  239. package/server/origins.js +330 -0
  240. package/server/project-source.d.ts +208 -0
  241. package/server/project-source.js +1042 -0
  242. package/server/project.d.ts +22 -0
  243. package/server/project.js +56 -0
  244. package/server/run-traces.d.ts +20 -0
  245. package/server/run-traces.js +96 -0
  246. package/server/save-new.d.ts +29 -0
  247. package/server/save-new.js +514 -0
  248. package/server/save-plan.d.ts +46 -0
  249. package/server/save-plan.js +1134 -0
  250. package/server/save-remove.d.ts +21 -0
  251. package/server/save-remove.js +528 -0
  252. package/server/save-session.d.ts +96 -0
  253. package/server/save-session.js +418 -0
  254. package/server/save-wire.d.ts +235 -0
  255. package/server/save-wire.js +8 -0
  256. package/server/save.d.ts +52 -0
  257. package/server/save.js +209 -0
  258. package/server/serve.d.ts +23 -0
  259. package/server/serve.js +413 -0
  260. package/server/test-connection.d.ts +17 -0
  261. package/server/test-connection.js +362 -0
  262. package/server/test-key.d.ts +2 -0
  263. package/server/test-key.js +83 -0
  264. package/server/watch.d.ts +28 -0
  265. package/server/watch.js +93 -0
  266. package/server/zod-edit.d.ts +74 -0
  267. package/server/zod-edit.js +558 -0
  268. package/server.d.ts +8 -0
  269. package/server.js +8 -0
  270. package/setups.mjs +170 -0
  271. package/shared-carry.d.ts +114 -0
  272. package/shared-carry.js +523 -0
  273. package/shared-settings.d.ts +34 -0
  274. package/shared-settings.js +70 -0
  275. package/source.d.ts +75 -0
  276. package/source.js +414 -0
  277. package/start.mjs +169 -0
  278. package/stream-lines.d.ts +9 -0
  279. package/stream-lines.js +27 -0
  280. package/stub.d.ts +6 -0
  281. package/stub.js +33 -0
  282. package/surface.d.ts +53 -0
  283. package/surface.js +631 -0
  284. package/system-markup.d.ts +20 -0
  285. package/system-markup.js +51 -0
  286. package/tool-schema.d.ts +23 -0
  287. package/tool-schema.js +69 -0
  288. package/tools.d.ts +16 -0
  289. package/tools.js +91 -0
  290. package/traces.d.ts +24 -0
  291. package/traces.js +29 -0
  292. package/transport.d.ts +57 -0
  293. package/transport.js +187 -0
  294. package/tree.d.ts +32 -0
  295. package/tree.js +83 -0
  296. package/types.d.ts +62 -0
  297. package/types.js +6 -0
  298. package/ui/acting-as-button.d.ts +2 -0
  299. package/ui/acting-as-button.js +16 -0
  300. package/ui/add-provider.d.ts +13 -0
  301. package/ui/add-provider.js +20 -0
  302. package/ui/boot-mark.css +53 -0
  303. package/ui/boot-mark.d.ts +19 -0
  304. package/ui/boot-mark.js +128 -0
  305. package/ui/boot-paint.d.ts +8 -0
  306. package/ui/boot-paint.js +8 -0
  307. package/ui/brand-icons.d.ts +57 -0
  308. package/ui/brand-icons.js +148 -0
  309. package/ui/built/theme.css +830 -0
  310. package/ui/built/theme.css.d.ts +9 -0
  311. package/ui/built/theorem-studio.d.ts +13 -0
  312. package/ui/built/theorem-studio.js +705 -0
  313. package/ui/built/theorem-studio.variants.d.ts +28 -0
  314. package/ui/code/monaco-contributions.d.ts +45 -0
  315. package/ui/code/studio-code.d.ts +7 -0
  316. package/ui/code/studio-code.js +253 -0
  317. package/ui/code/studio-monaco.d.ts +44 -0
  318. package/ui/code/studio-monaco.js +48 -0
  319. package/ui/code/studio-ts.worker.d.ts +1 -0
  320. package/ui/code/studio-ts.worker.js +16 -0
  321. package/ui/code/studio-type-checker.d.ts +12 -0
  322. package/ui/code/studio-type-checker.js +308 -0
  323. package/ui/code/studio-type-sources.d.ts +26 -0
  324. package/ui/confirm-write.d.ts +12 -0
  325. package/ui/confirm-write.js +45 -0
  326. package/ui/diff-block.d.ts +21 -0
  327. package/ui/diff-block.js +17 -0
  328. package/ui/figtree.css +59 -0
  329. package/ui/file-place.d.ts +4 -0
  330. package/ui/file-place.js +26 -0
  331. package/ui/guardrail-tester.d.ts +18 -0
  332. package/ui/guardrail-tester.js +248 -0
  333. package/ui/inspector-context.d.ts +119 -0
  334. package/ui/inspector-context.js +84 -0
  335. package/ui/inspector.d.ts +247 -0
  336. package/ui/inspector.js +284 -0
  337. package/ui/lib/acting-as.d.ts +25 -0
  338. package/ui/lib/acting-as.js +90 -0
  339. package/ui/lib/boot-sequence.d.ts +32 -0
  340. package/ui/lib/boot-sequence.js +57 -0
  341. package/ui/lib/export-agent.d.ts +11 -0
  342. package/ui/lib/export-agent.js +335 -0
  343. package/ui/lib/facet-icons.d.ts +20 -0
  344. package/ui/lib/facet-icons.js +21 -0
  345. package/ui/lib/panel-motion.d.ts +8 -0
  346. package/ui/lib/panel-motion.js +74 -0
  347. package/ui/lib/project-address.d.ts +14 -0
  348. package/ui/lib/project-address.js +24 -0
  349. package/ui/lib/project-starting.d.ts +13 -0
  350. package/ui/lib/project-starting.js +30 -0
  351. package/ui/lib/project-traces.d.ts +17 -0
  352. package/ui/lib/project-traces.js +54 -0
  353. package/ui/lib/read-map.d.ts +30 -0
  354. package/ui/lib/read-map.js +173 -0
  355. package/ui/lib/read-progress.d.ts +14 -0
  356. package/ui/lib/read-progress.js +12 -0
  357. package/ui/lib/refusal-lines.d.ts +3 -0
  358. package/ui/lib/refusal-lines.js +8 -0
  359. package/ui/lib/studio-activity.d.ts +5 -0
  360. package/ui/lib/studio-activity.js +17 -0
  361. package/ui/lib/studio-console.d.ts +27 -0
  362. package/ui/lib/studio-console.js +9 -0
  363. package/ui/lib/studio-conversation.d.ts +20 -0
  364. package/ui/lib/studio-conversation.js +130 -0
  365. package/ui/lib/studio-examples.d.ts +31 -0
  366. package/ui/lib/studio-examples.js +25 -0
  367. package/ui/lib/studio-labels.d.ts +6 -0
  368. package/ui/lib/studio-labels.js +13 -0
  369. package/ui/lib/studio-map-kept.d.ts +17 -0
  370. package/ui/lib/studio-map-kept.js +48 -0
  371. package/ui/lib/studio-open.d.ts +22 -0
  372. package/ui/lib/studio-open.js +54 -0
  373. package/ui/lib/studio-page.d.ts +33 -0
  374. package/ui/lib/studio-page.js +121 -0
  375. package/ui/lib/studio-prefs.d.ts +26 -0
  376. package/ui/lib/studio-prefs.js +87 -0
  377. package/ui/lib/studio-project.d.ts +106 -0
  378. package/ui/lib/studio-project.js +177 -0
  379. package/ui/lib/studio-restore.d.ts +49 -0
  380. package/ui/lib/studio-restore.js +251 -0
  381. package/ui/lib/studio-run-open.d.ts +15 -0
  382. package/ui/lib/studio-run-open.js +16 -0
  383. package/ui/lib/studio-session.d.ts +34 -0
  384. package/ui/lib/studio-session.js +17 -0
  385. package/ui/lib/studio-settings.d.ts +15 -0
  386. package/ui/lib/studio-settings.js +48 -0
  387. package/ui/lib/studio-store.d.ts +64 -0
  388. package/ui/lib/studio-store.js +391 -0
  389. package/ui/lib/studio-watch.d.ts +12 -0
  390. package/ui/lib/studio-watch.js +27 -0
  391. package/ui/lib/tab-fills.d.ts +6 -0
  392. package/ui/lib/tab-fills.js +14 -0
  393. package/ui/lib/tool-credentials.d.ts +3 -0
  394. package/ui/lib/tool-credentials.js +23 -0
  395. package/ui/lib/tool-probe.d.ts +19 -0
  396. package/ui/lib/tool-probe.js +104 -0
  397. package/ui/lib/type-icons.d.ts +35 -0
  398. package/ui/lib/type-icons.js +38 -0
  399. package/ui/lib/when-idle.d.ts +5 -0
  400. package/ui/lib/when-idle.js +14 -0
  401. package/ui/lib/zip.d.ts +5 -0
  402. package/ui/lib/zip.js +8 -0
  403. package/ui/map-token.css +11 -0
  404. package/ui/map-token.d.ts +17 -0
  405. package/ui/map-token.js +29 -0
  406. package/ui/mcp-icon.d.ts +6 -0
  407. package/ui/mcp-icon.js +8 -0
  408. package/ui/motion.css +113 -0
  409. package/ui/pan.d.ts +32 -0
  410. package/ui/pan.js +90 -0
  411. package/ui/profile-editor.d.ts +33 -0
  412. package/ui/profile-editor.js +2638 -0
  413. package/ui/provider-editor.d.ts +15 -0
  414. package/ui/provider-editor.js +92 -0
  415. package/ui/read-map.css +48 -0
  416. package/ui/read-map.d.ts +7 -0
  417. package/ui/read-map.js +163 -0
  418. package/ui/refusal-detail.d.ts +4 -0
  419. package/ui/refusal-detail.js +21 -0
  420. package/ui/shared-change.d.ts +9 -0
  421. package/ui/shared-change.js +28 -0
  422. package/ui/shell-motion.css +121 -0
  423. package/ui/shell-motion.d.ts +73 -0
  424. package/ui/shell-motion.js +194 -0
  425. package/ui/studio-code-pane.d.ts +19 -0
  426. package/ui/studio-code-pane.js +44 -0
  427. package/ui/studio-compare.d.ts +80 -0
  428. package/ui/studio-compare.js +326 -0
  429. package/ui/studio-conflicts.d.ts +17 -0
  430. package/ui/studio-conflicts.js +87 -0
  431. package/ui/studio-connection.d.ts +64 -0
  432. package/ui/studio-connection.js +286 -0
  433. package/ui/studio-decision.d.ts +6 -0
  434. package/ui/studio-decision.js +49 -0
  435. package/ui/studio-embedding.d.ts +6 -0
  436. package/ui/studio-embedding.js +30 -0
  437. package/ui/studio-host-console.d.ts +6 -0
  438. package/ui/studio-host-console.js +17 -0
  439. package/ui/studio-host.d.ts +73 -0
  440. package/ui/studio-host.js +12 -0
  441. package/ui/studio-live.d.ts +17 -0
  442. package/ui/studio-live.js +26 -0
  443. package/ui/studio-map.css +273 -0
  444. package/ui/studio-map.d.ts +17 -0
  445. package/ui/studio-map.js +397 -0
  446. package/ui/studio-nav.d.ts +24 -0
  447. package/ui/studio-nav.js +17 -0
  448. package/ui/studio-no-project.d.ts +5 -0
  449. package/ui/studio-no-project.js +9 -0
  450. package/ui/studio-run.css +76 -0
  451. package/ui/studio-run.d.ts +14 -0
  452. package/ui/studio-run.js +119 -0
  453. package/ui/studio-runner.d.ts +45 -0
  454. package/ui/studio-runner.js +187 -0
  455. package/ui/studio-save.d.ts +49 -0
  456. package/ui/studio-save.js +307 -0
  457. package/ui/studio-screen.d.ts +61 -0
  458. package/ui/studio-screen.js +2078 -0
  459. package/ui/studio-settings.d.ts +31 -0
  460. package/ui/studio-settings.js +288 -0
  461. package/ui/studio-shell.css +27 -0
  462. package/ui/studio-shell.d.ts +43 -0
  463. package/ui/studio-shell.js +87 -0
  464. package/ui/studio-theme.d.ts +7 -0
  465. package/ui/studio-theme.js +94 -0
  466. package/ui/studio.css +427 -0
  467. package/ui/theorem-mark.css +84 -0
  468. package/ui/theorem-mark.d.ts +22 -0
  469. package/ui/theorem-mark.js +21 -0
  470. package/vite.d.mts +30 -0
  471. package/vite.mjs +277 -0
  472. package/workspace.d.ts +230 -0
  473. package/workspace.js +1054 -0
@@ -0,0 +1,2078 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Banner } from '@astryxdesign/core/Banner';
3
+ import { Button } from '@astryxdesign/core/Button';
4
+ import { DropdownMenu } from '@astryxdesign/core/DropdownMenu';
5
+ import { Heading } from '@astryxdesign/core/Heading';
6
+ import { HStack } from '@astryxdesign/core/HStack';
7
+ import { useClipboard } from '@astryxdesign/core/hooks';
8
+ import { Icon } from '@astryxdesign/core/Icon';
9
+ import { IconButton } from '@astryxdesign/core/IconButton';
10
+ import { Layout, LayoutContent, LayoutPanel } from '@astryxdesign/core/Layout';
11
+ import { ResizeHandle, useResizable } from '@astryxdesign/core/Resizable';
12
+ import { ScrollableArea } from '@astryxdesign/core/ScrollableArea';
13
+ import { Section } from '@astryxdesign/core/Section';
14
+ import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl';
15
+ import { Selector } from '@astryxdesign/core/Selector';
16
+ import { StackItem } from '@astryxdesign/core/Stack';
17
+ import { Text } from '@astryxdesign/core/Text';
18
+ import { TextInput } from '@astryxdesign/core/TextInput';
19
+ import { useToast } from '@astryxdesign/core/Toast';
20
+ import { Token } from '@astryxdesign/core/Token';
21
+ import { Tooltip } from '@astryxdesign/core/Tooltip';
22
+ import { TreeList } from '@astryxdesign/core/TreeList';
23
+ import { VStack } from '@astryxdesign/core/VStack';
24
+ import { IconActivity, IconAdjustmentsHorizontal, IconArrowBarToDown, IconArrowLeft, IconBrowserShare, IconCheck, IconCloud, IconCode, IconCopy, IconCopyPlus, IconFile, IconFileSmile, IconFileZip, IconKey, IconMenu2, IconPlayerPlay, IconPlaylistX, IconPlus, IconRestore, IconRotateClockwise, IconSearch, IconShieldSearch, IconSitemap, IconSparkles, IconTool, IconX, } from '@tabler/icons-react';
25
+ import { profileGraphFacet } from '@theoremjs/agents';
26
+ import { InPlace, PaneFailure, RaisedPane, useDisclosureMotion, useLabels, } from '@theoremjs/react/ui';
27
+ import { canonical } from "../canonical.js";
28
+ import { addAgent, addArchitectExample, addProvider, agentNodeId, compileWorkspace, createBlankDraft, createConsoleExampleDraft, createDecisionExampleDraft, createExampleDraft, createGeminiEmbeddingExampleDraft, createLiveExampleDraft, createNarratorExampleDraft, createStudioRunId, draftFacets, duplicateAgent, editedSince, excludeFacet, includableFacets, includeFacet, libraryDraft, localProviderAddress, mergedWithFiles, modelBindingNodeId, modelRoute, modelSharers, modelSlots, nodeOrigins, onStudioLocal, openOutcome, originLabel, originPlace, originWay, providerIdOf, providerNodeId, providerUse, rebased, removeAgent, removeLibraryTool, removeProvider, renameProvider, reopened, resetAgent, resetAll, resetLibraryTool, resetProvider, sampleToolInput, saveStudioRunPayload, scopedNodeId, setToolAllowed, sharedAsk, sharedAt, sharedEntries, studioNodeRef, studioSource, studioTree, toolSpecKeyOf, withAgentDraft, withFilesChosen, withProvider, workspaceNodeRef, workspaceRunAgent, workspaceTree, writeAsk, } from "../mod.js";
29
+ import { studioSurface } from "../surface.js";
30
+ import './studio.css';
31
+ import { lazy, memo, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
32
+ import { flushSync } from 'react-dom';
33
+ import { ActingAsButton } from "./acting-as-button.js";
34
+ import { AddProviderButton } from "./add-provider.js";
35
+ import { IconTypeSafe, modelFamilyMark } from "./brand-icons.js";
36
+ import { useConfirmWrite } from "./confirm-write.js";
37
+ import { ConfirmWrite, ISSUE_ROW_ATTRIBUTE, LeavePage, ListBadges, LocalConnection, ProbedAgent, ProjectAsks, QuestionLink, RowOrigins, RunsOn, WorkspaceContext, } from "./inspector-context.js";
38
+ import { ActingAsContext, chatKeyOf, useActingAsState } from "./lib/acting-as.js";
39
+ import { FACET_ICON } from "./lib/facet-icons.js";
40
+ import { movePanel } from "./lib/panel-motion.js";
41
+ import { screenEndpoint } from "./lib/project-address.js";
42
+ import { recordsTraces } from "./lib/project-traces.js";
43
+ import { clearConversation, restoreConversation, saveConversation, } from "./lib/studio-conversation.js";
44
+ import { STUDIO_EXAMPLES } from "./lib/studio-examples.js";
45
+ import { pageInputsOf, sentPageValues, usePageValues } from "./lib/studio-page.js";
46
+ import { getPref, setPref } from "./lib/studio-prefs.js";
47
+ import { NO_ASKS, openInEditor, openProject, ProjectContext, projectCode, readFiles, useProject, } from "./lib/studio-project.js";
48
+ import { dropAside, keepReading, keptReading, setAside } from "./lib/studio-restore.js";
49
+ import { projectKey } from "./lib/studio-session.js";
50
+ import { closeSettings, openSettings, settingsScreen, useSettingsDoor, } from "./lib/studio-settings.js";
51
+ import { createStudioStore } from "./lib/studio-store.js";
52
+ import { subscribeToFiles } from "./lib/studio-watch.js";
53
+ import { toolCredential } from "./lib/tool-credentials.js";
54
+ import { runToolProbe } from "./lib/tool-probe.js";
55
+ import { PROFILE_TYPE_ICON, toolTypeIcon } from "./lib/type-icons.js";
56
+ import { warmWhenIdle } from "./lib/when-idle.js";
57
+ import { addToolSpec, ProfileEditor } from "./profile-editor.js";
58
+ import { ProviderEditor } from "./provider-editor.js";
59
+ import { SharedChange } from "./shared-change.js";
60
+ import { ChooseConflictsContext, FileConflicts } from "./studio-conflicts.js";
61
+ import { StudioKeys, useStudioConnection, } from "./studio-connection.js";
62
+ import { StudioHostContext, useStudioHost, } from "./studio-host.js";
63
+ import { NAV_WIDTH } from "./studio-nav.js";
64
+ import { StudioRunner } from "./studio-runner.js";
65
+ import { ProjectSave, ProjectTokens, useProjectSave } from "./studio-save.js";
66
+ // The tester, Settings, the map and the code view load when they first open, or once the page is idle.
67
+ const testerPanel = () => import("./guardrail-tester.js");
68
+ const settingsPanel = () => import("./studio-settings.js");
69
+ const mapPanel = () => import("./studio-map.js");
70
+ const codePane = () => import("./studio-code-pane.js");
71
+ const StudioMap = lazy(() => mapPanel().then((map) => ({ default: map.StudioMap })));
72
+ const CodeBody = lazy(() => codePane().then((code) => ({ default: code.CodeBody })));
73
+ const ProviderCode = lazy(() => codePane().then((code) => ({ default: code.ProviderCode })));
74
+ const GuardrailTester = lazy(() => testerPanel().then((tester) => ({ default: tester.GuardrailTester })));
75
+ const StudioSettings = lazy(() => settingsPanel().then((settings) => ({ default: settings.StudioSettings })));
76
+ /** A node's icon; Identity shows the profile type's once one is picked. */
77
+ function nodeIcon(draft, ref) {
78
+ const type = draft.identity.profileType;
79
+ if (ref.facet === 'identity' && type)
80
+ return PROFILE_TYPE_ICON[type];
81
+ if (ref.facet === 'modelBinding') {
82
+ const binding = draft.modelBindings.find((each) => each.key === ref.key);
83
+ if (!binding)
84
+ return FACET_ICON.modelBinding;
85
+ const definition = draft.providers.find((each) => each.id === binding.provider)?.definition;
86
+ if (definition === 'typesafe')
87
+ return IconTypeSafe;
88
+ return modelFamilyMark(binding.apiId) ?? FACET_ICON.modelBinding;
89
+ }
90
+ if (ref.facet !== 'toolSpec')
91
+ return FACET_ICON[ref.facet];
92
+ const tool = draft.toolSpecs.find((spec) => spec.key === ref.key);
93
+ return tool ? toolTypeIcon(tool.toolType) : IconTool;
94
+ }
95
+ /** A row's hover action: stops at the button so the row itself isn't selected. */
96
+ function actionButton(label, icon, onPress) {
97
+ return (_jsx(IconButton, { label: label, variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: icon, size: "sm" }), tooltip: label, onClick: (event) => {
98
+ event.stopPropagation();
99
+ onPress();
100
+ } }));
101
+ }
102
+ function rowAction(label, icon, onPress) {
103
+ return _jsx("span", { className: "studio-tree-action", children: actionButton(label, icon, onPress) });
104
+ }
105
+ function treeItem(tree, node, isTop = false) {
106
+ const { draft, scope, selectedId, onSelect, setDraft } = tree;
107
+ const facet = node.ref.facet;
108
+ const id = scope(node.id);
109
+ const isOptional = isTop && facet !== 'toolSpec' && profileGraphFacet(facet)?.optional === true;
110
+ return {
111
+ id,
112
+ label: node.label,
113
+ startContent: _jsx(Icon, { icon: nodeIcon(draft, node.ref), size: "sm", color: "secondary" }),
114
+ endContent: isOptional
115
+ ? rowAction(`Remove ${node.label}`, IconX, () => {
116
+ setDraft((current) => excludeFacet(current, facet));
117
+ })
118
+ : undefined,
119
+ className: isOptional ? 'studio-tree-row' : undefined,
120
+ isSelected: id === selectedId,
121
+ isExpanded: node.children.length > 0,
122
+ onClick: () => {
123
+ onSelect(id);
124
+ },
125
+ children: node.children.length
126
+ ? node.children.map((child) => treeItem(tree, child))
127
+ : undefined,
128
+ };
129
+ }
130
+ /** An optional section the type allows but the draft leaves out: dimmed; a click adds it. */
131
+ function offItem({ scope, onSelect, setDraft }, facet) {
132
+ const label = profileGraphFacet(facet)?.label ?? facet;
133
+ const add = () => {
134
+ setDraft((current) => includeFacet(current, facet));
135
+ onSelect(scope(facet));
136
+ };
137
+ return {
138
+ id: scope(facet),
139
+ label: _jsx("span", { className: "studio-tree-off", children: label }),
140
+ startContent: (_jsx(Icon, { icon: FACET_ICON[facet], size: "sm", color: "disabled" })),
141
+ endContent: rowAction(`Add ${label}`, IconPlus, add),
142
+ className: 'studio-tree-row',
143
+ onClick: add,
144
+ };
145
+ }
146
+ /**
147
+ * One agent: its root row, labelled with its id, holds every section the profile type allows, in
148
+ * catalog order, optional ones left out dimmed. Only the open agent shows its sections. Its tools
149
+ * are picked in its Tools section; the library lists them once for every agent.
150
+ */
151
+ function agentItem(tree, isOpen, actions) {
152
+ const draft = { ...tree.draft, toolSpecs: [] };
153
+ const root = studioTree(draft);
154
+ const shown = new Map(root.children.map((node) => [node.id, node]));
155
+ const off = includableFacets(draft);
156
+ const all = draftFacets({ ...draft, included: [...draft.included, ...off] }).filter((facet) => facet !== 'identity');
157
+ const children = all.map((facet) => {
158
+ const node = shown.get(facet);
159
+ return node ? treeItem(tree, node, true) : offItem(tree, facet);
160
+ });
161
+ return {
162
+ ...treeItem(tree, { ...root, children: [] }),
163
+ endContent: actions,
164
+ className: 'studio-tree-row',
165
+ isExpanded: isOpen,
166
+ children: isOpen ? children : undefined,
167
+ };
168
+ }
169
+ function agentItems(state) {
170
+ const { workspace, focus, selectedId, onSelect, update, setDraft } = state;
171
+ const isOnly = workspace.agents.length === 1;
172
+ return workspace.agents.map((agent) => {
173
+ const draft = libraryDraft(workspace, agent.key) ?? createBlankDraft();
174
+ const name = agent.identity.agentId || 'this agent';
175
+ return agentItem({
176
+ draft,
177
+ scope: (id) => scopedNodeId(agent.key, id),
178
+ selectedId,
179
+ onSelect,
180
+ setDraft,
181
+ }, agent.key === focus, _jsxs("span", { className: "studio-tree-action", children: [actionButton(`Duplicate ${name}`, IconCopyPlus, () => {
182
+ update((current) => duplicateAgent(current, agent.key));
183
+ }), !isOnly &&
184
+ actionButton(`Remove ${name}`, IconX, () => {
185
+ update((current) => removeAgent(current, agent.key));
186
+ })] }));
187
+ });
188
+ }
189
+ /** The tool library, every agent picks its tools from these: those whose name or description holds `query`. */
190
+ function toolItems({ workspace, selectedId, onSelect, update }, query) {
191
+ const needle = query.trim().toLowerCase();
192
+ return workspaceTree(workspace).tools.flatMap((node) => {
193
+ const tool = workspace.toolSpecs.find((spec) => spec.key === toolSpecKeyOf(node.id));
194
+ const text = `${node.label} ${tool?.description ?? ''}`.toLowerCase();
195
+ if (needle && !text.includes(needle))
196
+ return [];
197
+ return [
198
+ {
199
+ id: node.id,
200
+ label: node.label,
201
+ startContent: (_jsx(Icon, { icon: tool ? toolTypeIcon(tool.toolType) : IconTool, size: "sm", color: "secondary" })),
202
+ endContent: rowAction(`Remove ${node.label}`, IconX, () => {
203
+ if (tool)
204
+ update((current) => removeLibraryTool(current, tool.key));
205
+ }),
206
+ className: 'studio-tree-row',
207
+ isSelected: node.id === selectedId,
208
+ onClick: () => {
209
+ onSelect(node.id);
210
+ },
211
+ style: CHEVRON_COLUMN,
212
+ },
213
+ ];
214
+ });
215
+ }
216
+ /** Where a shared setting is declared: its real name and its file. */
217
+ function declaredAt({ setting }) {
218
+ return `${setting.name} · ${setting.file}:${String(setting.line)}`;
219
+ }
220
+ /**
221
+ * The providers every agent's models pick from, each over the agents with a model on it. A
222
+ * provider's row opens its own settings; an agent's opens its first model there.
223
+ */
224
+ function providerItems(tree) {
225
+ const { workspace, selectedId, onSelect, update, ownsProviders } = tree;
226
+ return workspace.providers.map((provider) => {
227
+ const id = providerNodeId(provider.id);
228
+ // One a model runs on stays: its row lists the agents to move first.
229
+ const isFree = ownsProviders && providerUse(workspace, provider.id).models === 0;
230
+ const users = workspace.agents.flatMap((agent) => {
231
+ const binding = agent.modelBindings.find((each) => each.provider === provider.id);
232
+ if (!binding)
233
+ return [];
234
+ return [
235
+ {
236
+ id: `${id}:${agent.key}`,
237
+ label: agent.identity.agentId || 'New agent',
238
+ startContent: (_jsx(Icon, { icon: PROFILE_TYPE_ICON[agent.identity.profileType || 'text'], size: "sm", color: "secondary" })),
239
+ onClick: () => {
240
+ onSelect(agentNodeId(agent.key, modelBindingNodeId(binding.key)));
241
+ },
242
+ },
243
+ ];
244
+ });
245
+ return {
246
+ id,
247
+ label: provider.id,
248
+ startContent: _jsx(Icon, { icon: IconCloud, size: "sm", color: "secondary" }),
249
+ endContent: isFree
250
+ ? rowAction(`Remove ${provider.id}`, IconX, () => {
251
+ update((current) => removeProvider(current, provider.id));
252
+ const [first] = workspace.agents;
253
+ if (selectedId === id && first)
254
+ onSelect(agentNodeId(first.key));
255
+ })
256
+ : undefined,
257
+ className: isFree ? 'studio-tree-row' : undefined,
258
+ isSelected: selectedId === id,
259
+ onClick: () => {
260
+ onSelect(id);
261
+ },
262
+ isExpanded: true,
263
+ children: users,
264
+ };
265
+ });
266
+ }
267
+ /**
268
+ * Which list shows, following the selection, and the tool search. The open row stays in view: a
269
+ * tool far down the library, one an issue opened, or one a cleared search or the toggle shows
270
+ * again. Revealed after the list renders.
271
+ */
272
+ function useWorkspaceList(selectedId, listRef) {
273
+ const [query, setQuery] = useState('');
274
+ const revealSelected = useCallback(() => {
275
+ requestAnimationFrame(() => {
276
+ listRef.current
277
+ ?.querySelector('[aria-selected="true"]')
278
+ ?.scrollIntoView({ block: 'nearest' });
279
+ });
280
+ }, [listRef]);
281
+ useEffect(() => {
282
+ // Nothing is selected while Keys is open.
283
+ if (selectedId)
284
+ revealSelected();
285
+ }, [selectedId, revealSelected]);
286
+ return { query, setQuery, revealSelected };
287
+ }
288
+ /**
289
+ * Which list shows: the tools library once a tool is open, the providers once a provider is, and
290
+ * whatever the toggle last chose. Following the selection, it switches when the open row changes
291
+ * between an agent, a tool, and a provider.
292
+ */
293
+ function useListShown(selectedId) {
294
+ const isTool = toolSpecKeyOf(selectedId) !== undefined;
295
+ const isProvider = providerIdOf(selectedId) !== undefined;
296
+ const [list, setList] = useState(isTool ? 'tools' : isProvider ? 'providers' : 'agents');
297
+ const [shownFor, setShownFor] = useState(selectedId);
298
+ if (shownFor !== selectedId) {
299
+ setShownFor(selectedId);
300
+ // A provider's rows open the models on it, so an agent's row keeps the providers shown.
301
+ setList((current) => isTool ? 'tools' : isProvider ? 'providers' : current === 'tools' ? 'agents' : current);
302
+ }
303
+ return { list, setList };
304
+ }
305
+ /** The toggle between the agents, the tool library, and the providers, each with its count. */
306
+ function WorkspaceListToggle({ workspace, list, onChange, }) {
307
+ return (_jsx(SegmentedControl, { label: "Workspace list", size: "sm", layout: "fill", value: list, onChange: (next) => {
308
+ onChange(next === 'tools' || next === 'providers' ? next : 'agents');
309
+ }, children: WORKSPACE_LISTS.map(({ value, name, icon }) => {
310
+ const count = String({
311
+ agents: workspace.agents.length,
312
+ tools: workspace.toolSpecs.length,
313
+ providers: workspace.providers.length,
314
+ }[value]);
315
+ return (_jsx(Tooltip, { content: `${name} · ${count}`, children: _jsx(SegmentedControlItem, { value: value, label: `${name}, ${count}`, isLabelHidden: true, icon: _jsx(Icon, { icon: icon, size: "sm" }) }) }, value));
316
+ }) }));
317
+ }
318
+ /** The sidebar's lists, as icons: a name and its count would not fit the nav's width. */
319
+ const WORKSPACE_LISTS = [
320
+ { value: 'agents', name: 'Agents', icon: IconFileSmile },
321
+ { value: 'tools', name: 'Tools', icon: IconTool },
322
+ { value: 'providers', name: 'Providers', icon: IconCloud },
323
+ ];
324
+ /** Makes the draft first, since an example may load its pictures, then adds it to whichever workspace is current. */
325
+ const addsDraft = (draft) => async () => {
326
+ const made = await draft();
327
+ return (workspace) => addAgent(workspace, made);
328
+ };
329
+ /** The examples, in the order the menu lists them. The architect brings its narrator. */
330
+ const EXAMPLE_AGENTS = [
331
+ {
332
+ id: 'concierge',
333
+ ...STUDIO_EXAMPLES.concierge,
334
+ icon: PROFILE_TYPE_ICON.text,
335
+ add: addsDraft(createExampleDraft),
336
+ },
337
+ {
338
+ id: 'live-concierge',
339
+ ...STUDIO_EXAMPLES['live-concierge'],
340
+ icon: PROFILE_TYPE_ICON.live,
341
+ add: addsDraft(createLiveExampleDraft),
342
+ },
343
+ {
344
+ id: 'architect',
345
+ ...STUDIO_EXAMPLES.architect,
346
+ icon: PROFILE_TYPE_ICON.text,
347
+ add: () => Promise.resolve(addArchitectExample),
348
+ },
349
+ {
350
+ id: 'narrator',
351
+ ...STUDIO_EXAMPLES.narrator,
352
+ icon: PROFILE_TYPE_ICON.speech,
353
+ add: addsDraft(createNarratorExampleDraft),
354
+ },
355
+ {
356
+ id: 'console',
357
+ ...STUDIO_EXAMPLES.console,
358
+ icon: PROFILE_TYPE_ICON.host,
359
+ add: addsDraft(createConsoleExampleDraft),
360
+ },
361
+ {
362
+ id: 'decision',
363
+ ...STUDIO_EXAMPLES.decision,
364
+ icon: PROFILE_TYPE_ICON.decision,
365
+ add: addsDraft(createDecisionExampleDraft),
366
+ },
367
+ {
368
+ id: 'gemini-embedding-2',
369
+ ...STUDIO_EXAMPLES['gemini-embedding-2'],
370
+ icon: PROFILE_TYPE_ICON.embedding,
371
+ add: addsDraft(createGeminiEmbeddingExampleDraft),
372
+ },
373
+ ];
374
+ /** Adds a blank agent or one of the examples. */
375
+ /** As wide as the row, and no wider: the description does not widen the menu its labels size. */
376
+ const ROW_WIDE = { inlineSize: 0, minInlineSize: '100%' };
377
+ /** A menu row's description on one line. What does not fit shows on hover. */
378
+ const oneLine = (text) => (_jsx(Text, { type: "supporting", color: "secondary", maxLines: 1, hasTruncateTooltip: "end", style: ROW_WIDE, children: text }));
379
+ function AddAgentMenu({ onAddAgent }) {
380
+ return (_jsx(DropdownMenu, { button: {
381
+ label: 'Add agent',
382
+ variant: 'ghost',
383
+ size: 'sm',
384
+ width: '100%',
385
+ icon: _jsx(Icon, { icon: IconPlus, size: "sm" }),
386
+ style: { height: 'calc(var(--size-element-sm) - 4px)' },
387
+ }, hasChevron: false, placement: "below", items: [
388
+ {
389
+ id: 'blank',
390
+ label: 'Blank agent',
391
+ description: oneLine('One empty agent to build from.'),
392
+ icon: _jsx(Icon, { icon: IconFile, size: "sm" }),
393
+ onClick: () => {
394
+ void addsDraft(createBlankDraft)().then(onAddAgent);
395
+ },
396
+ },
397
+ ...EXAMPLE_AGENTS.map((example) => ({
398
+ id: example.id,
399
+ label: example.label,
400
+ description: oneLine(example.description),
401
+ icon: _jsx(Icon, { icon: example.icon, size: "sm" }),
402
+ onClick: () => {
403
+ void example.add().then(onAddAgent);
404
+ },
405
+ })),
406
+ ] }));
407
+ }
408
+ /** Adds a tool to the library, joined to the open agent, and opens it. */
409
+ function AddToolButton({ tree, draft }) {
410
+ return (_jsx(Button, { label: "Add tool", variant: "ghost", size: "sm", width: "100%", icon: _jsx(Icon, { icon: IconPlus, size: "sm" }), style: { height: 'calc(var(--size-element-sm) - 4px)' }, onClick: () => {
411
+ addToolSpec(draft, tree.setDraft, tree.onSelect);
412
+ } }));
413
+ }
414
+ /** Filters the tool library by name or description. */
415
+ function ToolSearchInput({ query, onChange }) {
416
+ return (_jsx(TextInput, { label: "Search tools", isLabelHidden: true, size: "sm", placeholder: "Search tools", value: query, onChange: onChange, startIcon: IconSearch, hasClear: true }));
417
+ }
418
+ /** The shown list's rows, or why there are none. */
419
+ function WorkspaceListBody({ tree, list, tools, query, listRef, }) {
420
+ return (_jsx(ScrollableArea, { ref: listRef, label: "Workspace", height: "100%", children: list === 'agents' ? (_jsx(TreeList, { density: "compact", "aria-label": "Agents", items: agentItems(tree) })) : list === 'providers' ? (_jsx(TreeList, { density: "compact", "aria-label": "Providers", items: providerItems(tree) })) : tools.length > 0 ? (_jsx(TreeList, { density: "compact", "aria-label": "Tools", items: tools })) : (_jsx(Text, { type: "supporting", color: "secondary", children: query.trim() ? `No tool matches “${query.trim()}”.` : 'No tools yet.' })) }));
421
+ }
422
+ /**
423
+ * The workspace's lists, one at a time: its agents, the tool library they share, and the providers.
424
+ * The toggle follows the selection, so opening a tool from elsewhere (an issue, a new tool) shows it.
425
+ */
426
+ function WorkspaceTreeLists({ tree, draft, onAddAgent, listRef, }) {
427
+ const { list, setList } = tree;
428
+ const { query, setQuery, revealSelected } = useWorkspaceList(tree.selectedId, listRef);
429
+ const tools = list === 'tools' ? toolItems(tree, query) : [];
430
+ return (_jsxs(VStack, { gap: 2, height: "100%", children: [_jsx(WorkspaceListToggle, { workspace: tree.workspace, list: list, onChange: (next) => {
431
+ setList(next);
432
+ revealSelected();
433
+ } }), list === 'agents' ? _jsx(AddAgentMenu, { onAddAgent: onAddAgent }) : null, list === 'tools' && (_jsx(ToolSearchInput, { query: query, onChange: (next) => {
434
+ setQuery(next);
435
+ revealSelected();
436
+ } })), list === 'tools' ? _jsx(AddToolButton, { tree: tree, draft: draft }) : null, list === 'providers' && tree.ownsProviders ? (_jsx(AddProviderButton, { taken: tree.workspace.providers.map((provider) => provider.id), onAdd: (provider) => {
437
+ tree.update((current) => addProvider(current, provider));
438
+ tree.onSelect(providerNodeId(provider.id));
439
+ } })) : null, _jsx(StackItem, { size: "fill", children: _jsx(WorkspaceListBody, { tree: tree, list: list, tools: tools, query: query, listRef: listRef }) })] }));
440
+ }
441
+ /** The longest agent name the preview's header shows in full. */
442
+ const CHAT_NAME_MAX = 24;
443
+ /** `name`, cut to `CHAT_NAME_MAX` characters with an ellipsis when it is longer. */
444
+ function shortName(name) {
445
+ return name.length > CHAT_NAME_MAX ? `${name.slice(0, CHAT_NAME_MAX - 1)}…` : name;
446
+ }
447
+ /** Which agent the preview chats with, once there is more than one. */
448
+ function ChatPicker({ agents, chatWith, onChange, }) {
449
+ if (agents.length < 2)
450
+ return null;
451
+ return (_jsx(Selector, { label: "Chat with", isLabelHidden: true, variant: "ghost", size: "sm",
452
+ // As wide as the name, so the chevron sits right after it.
453
+ width: "fit-content", value: chatWith, options: agents,
454
+ // why: The header shows a short name; the list under it shows each name in full.
455
+ renderValue: (option) => (_jsx("span", { title: option.label, children: shortName(option.label ?? option.value) })), onChange: onChange }));
456
+ }
457
+ /** The agents the preview can chat with: the same list while their keys and names stand. */
458
+ function useChatAgents(agents) {
459
+ const names = JSON.stringify(agents.map((agent) => [agent.key, agent.identity.agentId]));
460
+ return useMemo(() => JSON.parse(names).map(([value, agentId]) => ({
461
+ value,
462
+ label: agentId || 'Unnamed agent',
463
+ })), [names]);
464
+ }
465
+ /** A tree group's heading and its add button. */
466
+ /** The indent TreeList gives a leaf row beside a branch: the chevron's width and its gap. */
467
+ const CHEVRON_COLUMN = {
468
+ '--_tree-indent': 'calc(var(--spacing-4) + var(--spacing-2))',
469
+ };
470
+ /** The tree's label for a node, e.g. the agent's id for Identity. */
471
+ function nodeLabel(node, id) {
472
+ if (node.id === id)
473
+ return node.label;
474
+ for (const child of node.children) {
475
+ const label = nodeLabel(child, id);
476
+ if (label !== undefined)
477
+ return label;
478
+ }
479
+ return undefined;
480
+ }
481
+ /**
482
+ * The editor's title: the facet's name, e.g. Identity rather than the agent's id the tree shows;
483
+ * for a model or tool entry, that entry's own name.
484
+ */
485
+ function editorTitle(draft, id) {
486
+ const ref = studioNodeRef(draft, id);
487
+ if (!ref)
488
+ return undefined;
489
+ if ('key' in ref)
490
+ return nodeLabel(studioTree(draft), id);
491
+ return profileGraphFacet(ref.facet)?.label;
492
+ }
493
+ /** The editor's heading: Tools with the tools list and no tool open; else the open node's title. */
494
+ function headingOf(list, editing, title) {
495
+ return list === 'tools' && toolSpecKeyOf(editing) === undefined
496
+ ? profileGraphFacet('tools')?.label
497
+ : title;
498
+ }
499
+ /** Waits before compiling after an edit, so typing doesn't recompile on every key. */
500
+ const COMPILE_DEBOUNCE_MS = 300;
501
+ /** The part of a compile the agent runs from: what the run tab and the preview both take. */
502
+ function runPayload({ agentId, profile, customTools, structured, questions, providers, dependencies, }) {
503
+ return { agentId, profile, customTools, structured, questions, providers, dependencies };
504
+ }
505
+ /** An agent's id as the workspace names it, for finding its compile. */
506
+ function agentIdOf(workspace, key) {
507
+ return workspace.agents.find((agent) => agent.key === key)?.identity.agentId.trim() ?? '';
508
+ }
509
+ /** One agent's compile, from a workspace that compiled. */
510
+ function compiledAgent(compiled, workspace, key) {
511
+ const id = agentIdOf(workspace, key);
512
+ return compiled.agents.find((agent) => agent.agentId === id);
513
+ }
514
+ /**
515
+ * The editor's node id for a workspace one: the open agent's own ids, a library tool's or a
516
+ * provider's as it is.
517
+ * `undefined` for another agent's.
518
+ */
519
+ function innerNodeId(id, focus) {
520
+ if (toolSpecKeyOf(id) !== undefined || providerIdOf(id) !== undefined)
521
+ return id;
522
+ const root = agentNodeId(focus);
523
+ if (id === root)
524
+ return 'identity';
525
+ return id.startsWith(`${root}/`) ? id.slice(root.length + 1) : undefined;
526
+ }
527
+ /** Hands the compiled agent to a new tab through this browser's storage; the run route reads it back. */
528
+ function openInNewTab(payload, project) {
529
+ const runId = createStudioRunId();
530
+ saveStudioRunPayload(payload, runId);
531
+ // A project's agent runs the project's code on its own page too.
532
+ const named = project ? `&project=${encodeURIComponent(project.name)}` : '';
533
+ globalThis.open(`/studio/run?run=${encodeURIComponent(runId)}${named}`, '_blank', 'noopener');
534
+ }
535
+ let exporter;
536
+ /** The export and its zip, loaded when the page is idle or on the first export. */
537
+ const exportKit = () => Promise.all([import("./lib/export-agent.js"), import("./lib/zip.js")]).then(([agent, zip]) => (exporter = { ...agent, ...zip }));
538
+ /** Runs `use` with the export code: at once when it has loaded, so a copy stays inside the click. */
539
+ function withExporter(use) {
540
+ if (exporter)
541
+ use(exporter);
542
+ else
543
+ void exportKit().then(use);
544
+ }
545
+ /** The workspace's files as a .zip, named for the agent being chatted with. */
546
+ function downloadExport(kit, files, agentId) {
547
+ download(`${agentId}.zip`, new Blob([kit.zipFiles(files)], { type: 'application/zip' }));
548
+ }
549
+ /** Downloads `blob` as `filename`. */
550
+ function download(filename, blob) {
551
+ const url = URL.createObjectURL(blob);
552
+ const link = document.createElement('a');
553
+ link.href = url;
554
+ link.download = filename;
555
+ link.click();
556
+ URL.revokeObjectURL(url);
557
+ }
558
+ /** `value`, once it has stopped changing for `ms`. */
559
+ function useDebounced(value, ms) {
560
+ const [settled, setSettled] = useState(value);
561
+ useEffect(() => {
562
+ const timer = setTimeout(() => {
563
+ setSettled(value);
564
+ }, ms);
565
+ return () => {
566
+ clearTimeout(timer);
567
+ };
568
+ }, [value, ms]);
569
+ return settled;
570
+ }
571
+ /** Calls `measure` with the node whenever it resizes; a callback ref, so it follows remounts. */
572
+ function useMeasure(measure) {
573
+ const [value, setValue] = useState();
574
+ const ref = useCallback((node) => {
575
+ if (!node)
576
+ return;
577
+ const observer = new ResizeObserver(() => {
578
+ setValue(measure(node));
579
+ });
580
+ observer.observe(node);
581
+ return () => {
582
+ observer.disconnect();
583
+ };
584
+ }, [measure]);
585
+ return [ref, value];
586
+ }
587
+ /** The next node with an issue after `selectedId`, wrapping, so repeated clicks walk through them. */
588
+ function nextIssueNode(issues, selectedId) {
589
+ const nodes = [...new Set(issues.map((issue) => issue.nodeId))];
590
+ return nodes[(nodes.indexOf(selectedId) + 1) % nodes.length] ?? selectedId;
591
+ }
592
+ /** The layout has no padding, so this is the content box Astryx resolves panel percentages on. */
593
+ const measureWidth = (node) => node.clientWidth;
594
+ /** The side panel's default share of the layout, the tree beside the editor: the golden split. */
595
+ const SIDE_DEFAULT_PERCENT = 38.2;
596
+ /** The profile tree's width inside the side panel; the editor takes the rest. */
597
+ const TREE_WIDTH = NAV_WIDTH;
598
+ /**
599
+ * The frame's class; on a phone it names the sheet open over the editor. While the map is open the
600
+ * panel takes the whole frame, over the preview.
601
+ */
602
+ function frameClass(sheet, map, settings) {
603
+ return [
604
+ 'studio-frame',
605
+ sheet && `studio-${sheet}-open`,
606
+ map && 'studio-map-open',
607
+ settings && 'studio-settings-open',
608
+ ]
609
+ .filter(Boolean)
610
+ .join(' ');
611
+ }
612
+ /** Two badges per list row at the panel's default width or wider; one once it is narrowed. */
613
+ function badgesPerRow(panelSize, layoutWidth) {
614
+ if (layoutWidth === undefined)
615
+ return 2;
616
+ return panelSize >= Math.round((SIDE_DEFAULT_PERCENT / 100) * layoutWidth) ? 2 : 1;
617
+ }
618
+ /** The last workspace that compiled; it holds while the current one has issues. */
619
+ function useLastGood(compile) {
620
+ const [lastGood, setLastGood] = useState(() => compile.result.ok ? { workspace: compile.workspace, compiled: compile.result } : null);
621
+ // invariant: one object per good compile, so the payload, and the transport made from it, last
622
+ // until the agent changes; a new one aborts whatever the preview is running.
623
+ if (compile.result.ok && compile.result !== lastGood?.compiled) {
624
+ const good = { workspace: compile.workspace, compiled: compile.result };
625
+ setLastGood(good);
626
+ return good;
627
+ }
628
+ return lastGood;
629
+ }
630
+ /** The views of the editor column, in the order its one button lists them; the map opens beside either. */
631
+ const EDITOR_VIEWS = [
632
+ { id: 'editor', label: 'Editor', icon: IconAdjustmentsHorizontal, description: 'Edit as a form' },
633
+ { id: 'code', label: 'Code', icon: IconCode, description: 'Edit as TypeScript' },
634
+ ];
635
+ /** The agent with every use of key slot `from` pointed at `to`; `''` lets go of it. */
636
+ function swapKeySlot(draft, from, to) {
637
+ const swap = (slot) => (slot === from ? to : slot);
638
+ return {
639
+ ...draft,
640
+ models: {
641
+ ...draft.models,
642
+ key: swap(draft.models.key),
643
+ fallbackKey: swap(draft.models.fallbackKey),
644
+ },
645
+ modelBindings: draft.modelBindings.map((binding) => ({
646
+ ...binding,
647
+ keySlot: swap(binding.keySlot),
648
+ fallbackKeySlot: swap(binding.fallbackKeySlot),
649
+ })),
650
+ };
651
+ }
652
+ /** "1 issue" or "N issues", or nothing at none. */
653
+ const issuesLabel = (count) => count === 0 ? undefined : count === 1 ? '1 issue' : `${String(count)} issues`;
654
+ /** "1 issue" or "N issues" while the draft doesn't compile; nothing once it does. */
655
+ function issueCount(compiled) {
656
+ return compiled.ok ? undefined : issuesLabel(compiled.issues.length);
657
+ }
658
+ /** What a host that follows along is told about the draft on screen. */
659
+ function screenReport(draft, compiled, section) {
660
+ return {
661
+ agent: draft.identity.handle || draft.identity.agentId || 'unnamed',
662
+ type: draft.identity.profileType || 'not chosen',
663
+ issues: compiled.ok ? 0 : compiled.issues.length,
664
+ section,
665
+ };
666
+ }
667
+ /**
668
+ * The studio as th30's surface host: th30 works on the open agent, its draft from the store;
669
+ * everything else comes from the page.
670
+ */
671
+ function studioSurfaceHost(store, page, chat) {
672
+ return {
673
+ getDraft: store.getDraft,
674
+ getRevision: store.getRevision,
675
+ getRunsOn: () => page.current.on,
676
+ update: (next) => {
677
+ store.updateDraft(next, 'th30');
678
+ const reach = store.getPending();
679
+ if (!reach)
680
+ return undefined;
681
+ const ask = sharedAsk(reach, store.getWorkspace());
682
+ const asked = `Not made yet. The person is being asked: ${ask.title} ${ask.line}`;
683
+ return `${asked} Tell them, and wait for their answer: do not make this change again.`;
684
+ },
685
+ replaceDraft: (next, message) => {
686
+ const workspace = store.getWorkspace();
687
+ page.current.replaceWorkspace(withAgentDraft(workspace, store.getFocus(), next), message, 'th30');
688
+ },
689
+ select: (id) => {
690
+ store.select(scopedNodeId(store.getFocus(), id));
691
+ },
692
+ changesSince: (since) => store.changesSince(since).map((change) => ({
693
+ revision: change.revision,
694
+ by: change.by === 'th30' ? 'agent' : 'person',
695
+ sections: change.sections,
696
+ })),
697
+ subscribe: store.subscribe,
698
+ ...keyAndToolMembers(store, page),
699
+ ...runMembers(store, page, chat),
700
+ };
701
+ }
702
+ /** th30's reach into the keys and the tool library: read and test a key, open Keys, probe a tool. */
703
+ function keyAndToolMembers(store, page) {
704
+ return {
705
+ key: (slot) => page.current.connection.vault[slot] ?? '',
706
+ openKeys: () => {
707
+ page.current.setKeysOpen(true);
708
+ },
709
+ testKey: async (slot) => {
710
+ const key = page.current.connection.vault[slot]?.trim();
711
+ if (!key)
712
+ return { ok: false, error: 'No key in this slot.' };
713
+ try {
714
+ const response = await fetch(`${screenEndpoint()}/test-key`, {
715
+ method: 'POST',
716
+ headers: { 'Content-Type': 'application/json' },
717
+ body: JSON.stringify({ key }),
718
+ });
719
+ return await response.json();
720
+ }
721
+ catch {
722
+ return { ok: false, error: "Couldn't reach the studio server." };
723
+ }
724
+ },
725
+ toolCredential,
726
+ testTool: async (key, input) => {
727
+ const tool = store.getDraft().toolSpecs.find((candidate) => candidate.key === key);
728
+ if (!tool)
729
+ return { ok: false, error: 'No such tool.' };
730
+ const sample = JSON.stringify(input ?? sampleToolInput(tool.toolName, tool.inputJson) ?? {});
731
+ // A tool that writes runs only once the builder read the input and said so.
732
+ const ask = writeAsk(tool, sample);
733
+ if (ask && !(await page.current.confirmWrite(ask))) {
734
+ return { ok: false, error: 'The builder chose not to run it.' };
735
+ }
736
+ return runToolProbe(tool, sample, toolCredential(key), store.getDraft().guardrails);
737
+ },
738
+ };
739
+ }
740
+ /** th30's reach into the conversation and the chatted agent: send, start over, open in a new tab, get code. */
741
+ function runMembers(store, page, chat) {
742
+ /** What the chatted agent runs, with the agents it names. */
743
+ const runNow = () => {
744
+ const workspace = store.getWorkspace();
745
+ const result = compileWorkspace(workspace, page.current.on);
746
+ return result.ok
747
+ ? workspaceRunAgent(result, agentIdOf(workspace, workspace.chatWith))
748
+ : undefined;
749
+ };
750
+ return {
751
+ send: (text) => chat.current?.send(text) ?? Promise.resolve(null),
752
+ newConversation: () => {
753
+ page.current.newConversation();
754
+ },
755
+ launch: () => {
756
+ const result = runNow();
757
+ if (!result)
758
+ return;
759
+ openInNewTab({
760
+ ...runPayload(result),
761
+ connectionMode: page.current.on.mode,
762
+ localBaseUrl: page.current.connection.local.baseUrl,
763
+ }, page.current.project);
764
+ },
765
+ exportAgent: (format) => {
766
+ const workspace = store.getWorkspace();
767
+ const result = compileWorkspace(workspace, page.current.on);
768
+ const agent = result.ok ? compiledAgent(result, workspace, workspace.chatWith) : undefined;
769
+ if (!result.ok || !agent)
770
+ return Promise.resolve(false);
771
+ const code = projectCode(page.current.project, workspace);
772
+ withExporter((kit) => {
773
+ if (format === 'zip') {
774
+ downloadExport(kit, kit.exportFiles(result, agent, code), agent.agentId);
775
+ }
776
+ else if (format === 'copy') {
777
+ page.current.copy(kit.exportText(kit.exportFiles(result, agent, code)), 'the files');
778
+ }
779
+ else {
780
+ page.current.copy(kit.llmBrief(result, agent, code), 'the files and their brief');
781
+ }
782
+ });
783
+ return Promise.resolve(true);
784
+ },
785
+ };
786
+ }
787
+ /**
788
+ * Reveals the node the issue pill opened. Its first failing row is revealed once the editor shows
789
+ * that node, which can be a render after the click, and a frame later, once the editor's new scroll
790
+ * area scrolls.
791
+ */
792
+ function useIssueReveal(selected) {
793
+ const editorRef = useRef(null);
794
+ const [issueReveal, setIssueReveal] = useState();
795
+ const revealed = useRef(undefined);
796
+ useEffect(() => {
797
+ if (!issueReveal || issueReveal === revealed.current || issueReveal.node !== selected)
798
+ return;
799
+ revealed.current = issueReveal;
800
+ const frame = requestAnimationFrame(() => {
801
+ const row = editorRef.current?.querySelector(`[${ISSUE_ROW_ATTRIBUTE}]`);
802
+ row?.scrollIntoView({ block: 'center' });
803
+ row?.querySelector('[aria-invalid="true"]')?.focus({ preventScroll: true });
804
+ });
805
+ return () => {
806
+ cancelAnimationFrame(frame);
807
+ };
808
+ }, [issueReveal, selected]);
809
+ return { editorRef, reveal: setIssueReveal, revealed: issueReveal };
810
+ }
811
+ const issueKey = (issue) => `${issue.nodeId}\n${issue.field ?? ''}\n${String(issue.index ?? '')}`;
812
+ /**
813
+ * The issues the editor's rows show: the ones the builder has left behind. One that turns up on
814
+ * the page they are on (a detector just added, a field not filled in yet) stays quiet until they
815
+ * move to another page, or press the issue pill (`revealed`). `leavePage` is for a page inside a
816
+ * node, which `page` doesn't name.
817
+ */
818
+ function useLeftIssues(issues, page, revealed) {
819
+ // The issues as of the last render: at a move, the ones that were there before it.
820
+ const before = useRef(issues);
821
+ const [left, setLeft] = useState(() => new Set(issues.map(issueKey)));
822
+ const leavePage = useCallback(() => {
823
+ const keys = new Set(before.current.map(issueKey));
824
+ return () => {
825
+ setLeft(keys);
826
+ };
827
+ }, []);
828
+ // biome-ignore lint/correctness/useExhaustiveDependencies: a move or a press of the pill is what lets them show
829
+ useLayoutEffect(() => {
830
+ leavePage()();
831
+ }, [page, revealed, leavePage]);
832
+ useLayoutEffect(() => {
833
+ before.current = issues;
834
+ });
835
+ const shown = useMemo(() => issues.filter((issue) => left.has(issueKey(issue))), [issues, left]);
836
+ return { shown, leavePage };
837
+ }
838
+ /** Get code (a .zip, or copied) and open in a new tab, for the agent being chatted with; off while the workspace has issues. */
839
+ function ExportActions({ store, compiled, chatted, chattedId, blocked, copy, connection, }) {
840
+ const project = useProject();
841
+ return (_jsxs(_Fragment, { children: [_jsx(IconButton, { label: "Open in a new tab", variant: "ghost", icon: _jsx(Icon, { icon: IconBrowserShare, size: "sm" }), isDisabled: !compiled, tooltip: blocked ?? 'Open this agent on its own full page, in a new tab', onClick: () => {
842
+ const run = compiled && workspaceRunAgent(compiled, chattedId);
843
+ if (run)
844
+ openInNewTab({ ...runPayload(run), ...connection }, project);
845
+ } }), _jsx(ExportMenu, { code: () => projectCode(project, store.getWorkspace()), compiled: compiled, chatted: chatted, blocked: blocked, copy: copy })] }));
846
+ }
847
+ /** Get code: a .zip, Copy, or Copy for LLM. */
848
+ function ExportMenu({ code, compiled, chatted, blocked, copy, }) {
849
+ return (_jsx(DropdownMenu, { button: {
850
+ label: 'Get code',
851
+ variant: 'ghost',
852
+ isIconOnly: true,
853
+ icon: _jsx(Icon, { icon: IconArrowBarToDown, size: "sm" }),
854
+ tooltip: blocked ??
855
+ 'Download every agent as a .zip, or copy its files, or copy them with a brief for an LLM',
856
+ isDisabled: !compiled,
857
+ }, menuWidth: "15rem", hasChevron: false, placement: "below", alignment: "end", items: [
858
+ {
859
+ id: 'download',
860
+ label: 'Download',
861
+ description: oneLine('Every agent, as a .zip.'),
862
+ icon: _jsx(Icon, { icon: IconFileZip, size: "sm" }),
863
+ onClick: () => {
864
+ if (compiled && chatted) {
865
+ withExporter((kit) => {
866
+ downloadExport(kit, kit.exportFiles(compiled, chatted, code()), chatted.agentId);
867
+ });
868
+ }
869
+ },
870
+ },
871
+ {
872
+ id: 'copy',
873
+ label: 'Copy',
874
+ description: oneLine('Every file, each under its path, to paste into your code.'),
875
+ icon: _jsx(Icon, { icon: IconCopy, size: "sm" }),
876
+ onClick: () => {
877
+ if (compiled && chatted) {
878
+ withExporter((kit) => {
879
+ copy(kit.exportText(kit.exportFiles(compiled, chatted, code())), 'the files');
880
+ });
881
+ }
882
+ },
883
+ },
884
+ {
885
+ id: 'copy-llm',
886
+ label: 'Copy for LLM',
887
+ description: oneLine('Every file with a brief: what to install, where each goes, what to ask you.'),
888
+ icon: _jsx(Icon, { icon: IconSparkles, size: "sm" }),
889
+ onClick: () => {
890
+ if (compiled && chatted) {
891
+ withExporter((kit) => {
892
+ copy(kit.llmBrief(compiled, chatted, code()), 'the files and their brief');
893
+ });
894
+ }
895
+ },
896
+ },
897
+ ] }));
898
+ }
899
+ /** The workspace's store, kept in this tab's sessionStorage, and the open agent's draft from it. */
900
+ function useStudioWorkspace(start, project) {
901
+ // The settings the project's profiles share, on the agents it opened as: a change to one is
902
+ // made on each of them.
903
+ const [shared] = useState(() => sharedEntries(start.workspace, project?.shared ?? [], project?.origins.sites, project?.unregistered));
904
+ // The page's tools read the store synchronously. The editor works on the open agent's draft, with the
905
+ // whole tool library.
906
+ const [store] = useState(() => createStudioStore(start, project?.origins.sites?.profiles, project?.origins.sites?.tools));
907
+ const workspace = useSyncExternalStore(store.subscribe, store.getWorkspace, store.getWorkspace);
908
+ const draft = useSyncExternalStore(store.subscribe, store.getDraft, store.getDraft);
909
+ const setDraft = useCallback((next) => {
910
+ store.updateDraft(next);
911
+ }, [store]);
912
+ const update = useCallback((change) => {
913
+ store.update(change);
914
+ }, [store]);
915
+ useEffect(() => {
916
+ const flush = () => {
917
+ store.flush();
918
+ };
919
+ globalThis.addEventListener('pagehide', flush);
920
+ return () => {
921
+ globalThis.removeEventListener('pagehide', flush);
922
+ flush();
923
+ };
924
+ }, [store]);
925
+ const { files, refreshOrigins, filesChanged, conflict, settle, showConflict, watchFiles } = useProjectFiles(project, store);
926
+ // Where the files write each shared value, as they were last read.
927
+ const sites = files?.origins.sites;
928
+ useEffect(() => {
929
+ store.setSites(sites?.profiles, sites?.tools);
930
+ }, [store, sites]);
931
+ return {
932
+ store,
933
+ workspace,
934
+ draft,
935
+ focus: store.getFocus(),
936
+ setDraft,
937
+ update,
938
+ shared,
939
+ origins: files?.origins,
940
+ asks: files?.asks,
941
+ files,
942
+ refreshOrigins,
943
+ filesChanged,
944
+ conflict,
945
+ settle,
946
+ showConflict,
947
+ watchFiles,
948
+ };
949
+ }
950
+ /**
951
+ * What the project's files say: the profiles they hold, the settings they set in code, and the
952
+ * tools the studio makes ask. They are read again when the files may have moved: after a Save or
953
+ * an undo, and when the builder comes back to the page from their editor. `filesChanged` is for
954
+ * the studio's own moves (a Save, an undo, opening the files again): the workspace's starts then
955
+ * stand on this reading, which the tab keeps with the edits. The local server also says when the
956
+ * builder's editor writes a file, unless the watch is off. What the editor changed is merged
957
+ * into the workspace setting by setting: the builder's edits stay, and a setting both changed
958
+ * waits in `conflict` until the builder chooses with `settle`. They can close the choice and come
959
+ * back to it with `showConflict`: their edits stand meanwhile.
960
+ */
961
+ function useProjectFiles(project, store) {
962
+ const [files, setFiles] = useState(project);
963
+ const [pending, setConflict] = useState();
964
+ const [isChoosing, showConflict] = useState(true);
965
+ const conflict = useMemo(() => pending && { ...pending, open: isChoosing }, [pending, isChoosing]);
966
+ const toast = useToast();
967
+ /** The studio's own moves still being read: a reading that lands meanwhile is theirs. */
968
+ const ours = useRef(0);
969
+ /** The files as last read, which name an agent the builder removed since. */
970
+ const lastRead = useRef(undefined);
971
+ const keep = useCallback((read) => {
972
+ // Files that say what they said keep the reading, so nothing that runs on it starts again.
973
+ setFiles((held) => (canonical(held) === canonical(read) ? held : read));
974
+ }, []);
975
+ const reread = useCallback((isOurs, changed = []) => {
976
+ if (!project)
977
+ return;
978
+ if (isOurs)
979
+ ours.current += 1;
980
+ readFiles()
981
+ .then(({ files: read, workspace }) => {
982
+ const stoodOn = store.standsOn();
983
+ if (isOurs)
984
+ store.standOn(read.print);
985
+ else if (ours.current > 0)
986
+ return;
987
+ else if (stoodOn === undefined) {
988
+ // Edits that stand on no reading cannot be merged: the files open, and the edits are offered back.
989
+ const before = store.getWorkspace();
990
+ store.takeFiles(reopened(workspace, before));
991
+ store.standOn(read.print);
992
+ offerEditsBack(toast, store, before, projectKey(project.name));
993
+ }
994
+ else if (read.print !== stoodOn) {
995
+ const before = store.getWorkspace();
996
+ // After a reload the reading the edits stand on is the one the tab kept.
997
+ const stood = lastRead.current ?? keptReading(projectKey(project.name), stoodOn);
998
+ const merge = mergedWithFiles(before, workspace, stood);
999
+ const named = changedFiles(changed);
1000
+ if (merge.conflicts.length > 0) {
1001
+ // The builder's edits stand, and nothing moves under them, until they choose.
1002
+ setConflict({ merge, read, workspace, files: named });
1003
+ showConflict(true);
1004
+ return;
1005
+ }
1006
+ store.takeFiles(merge.workspace);
1007
+ store.standOn(read.print);
1008
+ const said = filesToast(named, merge.updated.length, editedSince(before, stoodOn));
1009
+ if (said && getPref('filesToast') === 'on')
1010
+ toast({ body: said });
1011
+ }
1012
+ // Files that went back to what the edits stand on leave nothing to choose.
1013
+ setConflict(undefined);
1014
+ lastRead.current = workspace;
1015
+ keepReading(projectKey(project.name), read.print, workspace);
1016
+ keep(read);
1017
+ })
1018
+ .catch((err) => {
1019
+ // What shows still stands on the last reading, which may be older than the files now.
1020
+ toast({
1021
+ type: 'error',
1022
+ body:
1023
+ // A fetch that reaches no server fails with a TypeError.
1024
+ err instanceof TypeError
1025
+ ? "The studio's server didn't answer, so your files show as it last read them."
1026
+ : `Couldn't read your files again (${err instanceof Error ? err.message : String(err)}), so they show as last read.`,
1027
+ });
1028
+ })
1029
+ .finally(() => {
1030
+ if (isOurs)
1031
+ ours.current -= 1;
1032
+ });
1033
+ }, [project, store, toast, keep]);
1034
+ const refreshOrigins = useCallback(() => {
1035
+ reread(false);
1036
+ }, [reread]);
1037
+ const filesChanged = useCallback(() => {
1038
+ reread(true);
1039
+ }, [reread]);
1040
+ const settle = useCallback((theirs) => {
1041
+ if (!conflict)
1042
+ return;
1043
+ store.takeFiles(withFilesChosen(conflict.merge, theirs));
1044
+ store.standOn(conflict.read.print);
1045
+ lastRead.current = conflict.workspace;
1046
+ if (project)
1047
+ keepReading(projectKey(project.name), conflict.read.print, conflict.workspace);
1048
+ keep(conflict.read);
1049
+ setConflict(undefined);
1050
+ }, [conflict, project, store, keep]);
1051
+ useEffect(() => {
1052
+ globalThis.addEventListener('focus', refreshOrigins);
1053
+ return () => {
1054
+ globalThis.removeEventListener('focus', refreshOrigins);
1055
+ };
1056
+ }, [refreshOrigins]);
1057
+ const [watch, setWatch] = useState({ available: false, on: getPref('watch') === 'on' });
1058
+ const watching = useRef(watch.on);
1059
+ const setWatching = useCallback((on) => {
1060
+ watching.current = on;
1061
+ setPref('watch', on ? 'on' : 'off');
1062
+ setWatch((held) => ({ ...held, on }));
1063
+ // What the files changed while the watch was off comes in now.
1064
+ if (on)
1065
+ reread(false);
1066
+ }, [reread]);
1067
+ useEffect(() => {
1068
+ if (!project)
1069
+ return;
1070
+ // Edits the tab kept may stand on files that changed while the page was closed.
1071
+ reread(false);
1072
+ return subscribeToFiles(project.endpoint, {
1073
+ open: () => {
1074
+ setWatch((held) => (held.available ? held : { ...held, available: true }));
1075
+ },
1076
+ files: (changed) => {
1077
+ if (watching.current)
1078
+ reread(false, changed);
1079
+ },
1080
+ });
1081
+ }, [project, reread]);
1082
+ const watchFiles = useMemo(() => (watch.available ? { on: watch.on, set: setWatching } : undefined), [watch, setWatching]);
1083
+ return { files, refreshOrigins, filesChanged, conflict, settle, showConflict, watchFiles };
1084
+ }
1085
+ /** The files that changed, as a toast and the conflict dialog name them: the one, or how many. */
1086
+ function changedFiles(files) {
1087
+ const [only] = files;
1088
+ if (only === undefined)
1089
+ return 'source code';
1090
+ return files.length === 1 ? only : `${String(files.length)} files`;
1091
+ }
1092
+ const conflictsLabel = (count) => count === 1 ? '1 conflict' : `${String(count)} conflicts`;
1093
+ /** What a merge with no conflicts says, or nothing when the files changed no setting. */
1094
+ function filesToast(files, updated, hadEdits) {
1095
+ if (updated === 0)
1096
+ return undefined;
1097
+ const settings = updated === 1 ? '1 setting updated' : `${String(updated)} settings updated`;
1098
+ const subject = files === 'source code' ? 'Source code' : files;
1099
+ return `${subject} changed: ${settings}.${hadEdits ? ' Studio edits are kept.' : ''}`;
1100
+ }
1101
+ /** Shows an origin's line in the builder's editor, and says where it is when no editor opens. */
1102
+ function useOpenOrigin(project) {
1103
+ const toast = useToast();
1104
+ return useCallback((origin) => {
1105
+ const place = originPlace(origin);
1106
+ if (!project || !origin.file || !place)
1107
+ return;
1108
+ openInEditor(project, { file: origin.file, line: origin.line })
1109
+ .catch(() => ({ ok: false, reason: 'failed' }))
1110
+ .then((answer) => {
1111
+ const path = answer.ok ? undefined : answer.place;
1112
+ toast({
1113
+ type: answer.ok ? undefined : 'error',
1114
+ body: openOutcome(answer, place),
1115
+ endContent: path && (_jsx(Button, { label: "Copy path", variant: "ghost", size: "sm", onClick: () => {
1116
+ void navigator.clipboard.writeText(path);
1117
+ } })),
1118
+ });
1119
+ });
1120
+ }, [project, toast]);
1121
+ }
1122
+ /** Keys are the workspace's: every agent's slots, so a called agent's key is asked for too. */
1123
+ function useWorkspaceConnection(workspace) {
1124
+ const inProject = useProject() !== null;
1125
+ const { lentKeys, toolHandlers } = useStudioHost();
1126
+ const bindings = workspace.agents.flatMap((agent) => agent.modelBindings.map((binding) => ({
1127
+ ...modelRoute(workspace.providers, agent.identity.profileType, binding),
1128
+ builtInTools: binding.builtInTools,
1129
+ local: onStudioLocal(binding.provider, inProject),
1130
+ })));
1131
+ const namedSlots = workspace.agents
1132
+ .flatMap((agent) => [
1133
+ agent.models.key,
1134
+ agent.models.fallbackKey,
1135
+ ...agent.modelBindings.flatMap((binding) => {
1136
+ const { keySlot, fallbackKeySlot } = modelSlots(workspace.providers, binding);
1137
+ return [keySlot, fallbackKeySlot];
1138
+ }),
1139
+ ])
1140
+ .filter((slot) => Boolean(slot));
1141
+ return useStudioConnection(bindings, undefined, namedSlots, { lentKeys, toolHandlers });
1142
+ }
1143
+ /** The frame: the resizable panel on the left. */
1144
+ function useStudioFrame() {
1145
+ const layoutRef = useRef(null);
1146
+ const [measureLayout, layoutWidth] = useMeasure(measureWidth);
1147
+ const layoutCallbackRef = useCallback((node) => {
1148
+ layoutRef.current = node;
1149
+ return measureLayout(node);
1150
+ }, [measureLayout]);
1151
+ const sidePanel = useResizable({
1152
+ defaultSize: `${String(SIDE_DEFAULT_PERCENT)}%`,
1153
+ minSize: TREE_WIDTH + 320,
1154
+ containerRef: layoutRef,
1155
+ autoSaveId: 'studio.panel',
1156
+ });
1157
+ // The editor's own width, for while the map is open beside it: the panel's, less the tree.
1158
+ useLayoutEffect(() => {
1159
+ layoutRef.current?.style.setProperty('--studio-view-width', `${String(sidePanel.size - TREE_WIDTH)}px`);
1160
+ }, [sidePanel.size]);
1161
+ // The profile tree's branches mount and unmount; ease them both ways.
1162
+ const sidebarRef = useRef(null);
1163
+ useDisclosureMotion(sidebarRef);
1164
+ return {
1165
+ layoutCallbackRef,
1166
+ sidePanel,
1167
+ listBadges: badgesPerRow(sidePanel.size, layoutWidth),
1168
+ sidebarRef,
1169
+ };
1170
+ }
1171
+ /** The open agent's issues and the library's, by the editor's ids. */
1172
+ function editorIssuesOf(compiled, focus) {
1173
+ if (compiled.ok)
1174
+ return [];
1175
+ return compiled.issues.flatMap((issue) => {
1176
+ const nodeId = innerNodeId(issue.nodeId, focus);
1177
+ return nodeId === undefined ? [] : [{ ...issue, nodeId }];
1178
+ });
1179
+ }
1180
+ /**
1181
+ * The workspace compiled once edits settle. Only a workspace that compiles changes the preview;
1182
+ * while one has issues, the last good agent and its conversation stay put. The chatted agent is
1183
+ * found by its key there, so renaming it doesn't lose it.
1184
+ */
1185
+ function useWorkspaceCompile(workspace, focus, connection) {
1186
+ const { mode, on } = connection;
1187
+ const settled = useDebounced(workspace, COMPILE_DEBOUNCE_MS);
1188
+ const compile = useMemo(() => ({ workspace: settled, result: compileWorkspace(settled, on) }), [settled, on]);
1189
+ const compiled = compile.result;
1190
+ const lastGood = useLastGood(compile);
1191
+ const chatWith = workspace.chatWith;
1192
+ const localBaseUrl = connection.local.baseUrl;
1193
+ const payload = useMemo(() => {
1194
+ const run = lastGood && workspaceRunAgent(lastGood.compiled, agentIdOf(lastGood.workspace, chatWith));
1195
+ return run ? { ...runPayload(run), connectionMode: mode, localBaseUrl } : null;
1196
+ }, [lastGood, chatWith, mode, localBaseUrl]);
1197
+ /** The open agent's compile: what the code view shows and Export takes. */
1198
+ const focused = compiled.ok ? compiledAgent(compiled, compile.workspace, focus) : undefined;
1199
+ const source = useMemo(() => (focused ? studioSource(focused) : null), [focused]);
1200
+ /** The open agent as it compiles now, for a detector's own test. */
1201
+ const probed = useMemo(() => {
1202
+ const run = compiled.ok && workspaceRunAgent(compiled, agentIdOf(compile.workspace, focus));
1203
+ return run ? runPayload(run) : null;
1204
+ }, [compiled, compile.workspace, focus]);
1205
+ // One object per compile, so the panes that read it sit out the renders between.
1206
+ return useMemo(() => {
1207
+ const issues = issueCount(compiled);
1208
+ return {
1209
+ compiled,
1210
+ payload,
1211
+ /** The workspace the payload was compiled from: the edits the preview runs. */
1212
+ tested: lastGood?.workspace,
1213
+ traced: recordsTraces(payload),
1214
+ source,
1215
+ probed,
1216
+ editorIssues: editorIssuesOf(compiled, focus),
1217
+ /** The agent being chatted with: Get code adds its route and chat to the workspace's files. */
1218
+ chatted: compiled.ok ? compiledAgent(compiled, compile.workspace, chatWith) : undefined,
1219
+ chattedId: agentIdOf(compile.workspace, chatWith),
1220
+ issues,
1221
+ blocked: issues && `Fix ${issues} first`,
1222
+ };
1223
+ }, [compiled, compile.workspace, lastGood, payload, source, probed, focus, chatWith]);
1224
+ }
1225
+ /** The preview's conversation with the chatted agent: which runner holds it, and what it resumes. */
1226
+ function useConversationRun(mode, sealer, chatWith, actingAs, question) {
1227
+ // Bumping this remounts every runner: fresh transcripts and trace feeds, the same profiles.
1228
+ const [restarts, setConversation] = useState(0);
1229
+ // Bumping a chat's own count remounts only its runner.
1230
+ const [cleared, setCleared] = useState({});
1231
+ // Each person the runs act as has their own chat with each agent.
1232
+ const chatKey = chatKeyOf(chatWith, actingAs);
1233
+ /** The runners that still stand: another mode or a restart replaces them all. */
1234
+ const generation = `${mode}:${String(restarts)}`;
1235
+ const runKey = `${generation}:${chatKey}:${String(cleared[chatKey] ?? 0)}`;
1236
+ // Each kept conversation resumes in the first runner it has here; a cleared or remade one
1237
+ // starts empty.
1238
+ const [firstRuns] = useState(() => new Map());
1239
+ if (!firstRuns.has(chatKey))
1240
+ firstRuns.set(chatKey, runKey);
1241
+ const isFirstRun = firstRuns.get(chatKey) === runKey;
1242
+ const [seeded] = useState(chatWith);
1243
+ const conversation = useMemo(() => (isFirstRun ? restoreConversation(chatKey, sealer) : undefined), [isFirstRun, chatKey, sealer]);
1244
+ /** A docs seed's question waits in its agent's first composer, when that resumes nothing. */
1245
+ const initialText = isFirstRun && chatWith === seeded && !conversation ? question : undefined;
1246
+ /**
1247
+ * The runners that have sent something; a new one (cleared, or another mode) has no history,
1248
+ * unless it resumed a kept conversation.
1249
+ */
1250
+ const [usedRuns, setUsedRuns] = useState(() => new Set());
1251
+ const isUsed = usedRuns.has(runKey) || conversation !== undefined;
1252
+ return useMemo(() => ({
1253
+ runKey,
1254
+ chatKey,
1255
+ generation,
1256
+ sealer,
1257
+ conversation,
1258
+ initialText,
1259
+ isUsed,
1260
+ markUsed: () => {
1261
+ setUsedRuns((runs) => (runs.has(runKey) ? runs : new Set(runs).add(runKey)));
1262
+ },
1263
+ /** Forgets this chat and starts it again; the profile stays, and other chats are left be. */
1264
+ clear: () => {
1265
+ clearConversation(chatKey);
1266
+ setCleared((counts) => ({ ...counts, [chatKey]: (counts[chatKey] ?? 0) + 1 }));
1267
+ },
1268
+ setConversation,
1269
+ }), [runKey, chatKey, generation, sealer, conversation, initialText, isUsed]);
1270
+ }
1271
+ /**
1272
+ * Once, on arrival: say when a kept draft was set aside for a docs seed, when a project's files
1273
+ * changed under the edits this tab kept, or when what was kept couldn't be read back.
1274
+ */
1275
+ function useArrivalToast({ displaced, discarded, project }, store) {
1276
+ const toast = useToast();
1277
+ const arrival = useRef({ displaced, discarded, name: project?.name });
1278
+ useEffect(() => {
1279
+ const { displaced, discarded, name } = arrival.current;
1280
+ const inProject = name !== undefined;
1281
+ arrival.current = { displaced: undefined, discarded: false, name };
1282
+ if (discarded) {
1283
+ toast({
1284
+ body: inProject
1285
+ ? "The edits this tab kept couldn't be restored."
1286
+ : "Your last draft couldn't be restored.",
1287
+ });
1288
+ }
1289
+ if (!displaced)
1290
+ return;
1291
+ if (name !== undefined) {
1292
+ offerEditsBack(toast, store, displaced, projectKey(name));
1293
+ return;
1294
+ }
1295
+ const dismiss = toast({
1296
+ body: 'Opened the example from the docs.',
1297
+ endContent: (_jsx(Button, { label: "Back to my draft", variant: "ghost", size: "sm", onClick: () => {
1298
+ store.update(displaced);
1299
+ dismiss();
1300
+ } })),
1301
+ });
1302
+ }, [toast, store]);
1303
+ }
1304
+ /** The one offer of set-aside edits a page shows. */
1305
+ const EDITS_ASIDE = 'studio-edits-aside';
1306
+ /**
1307
+ * Says the project's files changed under the builder's edits, and offers the edits back. The
1308
+ * offer stays until the builder answers it, and the tab keeps the edits under `key` meanwhile, so
1309
+ * a reload offers them again. They come back over whatever the studio holds then.
1310
+ */
1311
+ function offerEditsBack(toast, store, edits, key) {
1312
+ const dismiss = toast({
1313
+ body: 'Your files changed since you made your edits here, so the studio opened the files.',
1314
+ isAutoHide: false,
1315
+ // A later change to the files replaces the offer with the edits it set aside.
1316
+ uniqueID: EDITS_ASIDE,
1317
+ // Closing the offer lets the edits go.
1318
+ onHide: () => {
1319
+ dropAside(key);
1320
+ },
1321
+ endContent: (_jsx(Button, { label: "Bring my edits back", variant: "ghost", size: "sm", onClick: () => {
1322
+ store.update(rebased(edits, store.getWorkspace()));
1323
+ dismiss();
1324
+ } })),
1325
+ });
1326
+ setAside(key, edits);
1327
+ }
1328
+ const NARROW = { map: false, settings: false };
1329
+ const isWide = (panel) => panel.map || panel.settings;
1330
+ /**
1331
+ * Which view stands in the editor column, whether the map is open beside it, and opening a node
1332
+ * there. The map takes the preview's place: the panel widens over it the way the shell moves after
1333
+ * load, with the tree and the editor where they were, and draws back when the map closes.
1334
+ */
1335
+ function useEditorView(store) {
1336
+ const [keysOpen, setKeysOpen] = useState(false);
1337
+ const [editorView, setEditorView] = useState('editor');
1338
+ const [mapOpen, setMap] = useState(false);
1339
+ const [settingsOpen, setSettings] = useState(false);
1340
+ /** What was asked for last, what is on screen, and the moves still to make. */
1341
+ const wanted = useRef(NARROW);
1342
+ const shown = useRef(NARROW);
1343
+ const moves = useRef(Promise.resolve());
1344
+ const move = useCallback((next) => {
1345
+ wanted.current = { ...wanted.current, ...next };
1346
+ moves.current = moves.current.then(async () => {
1347
+ // Only the last one asked for is worth moving to.
1348
+ const to = wanted.current;
1349
+ const was = shown.current;
1350
+ if (to.map === was.map && to.settings === was.settings)
1351
+ return;
1352
+ shown.current = to;
1353
+ const swap = () => {
1354
+ flushSync(() => {
1355
+ setMap(to.map);
1356
+ setSettings(to.settings);
1357
+ });
1358
+ };
1359
+ // Settings over an open map, and back: the panel is already as wide as it goes.
1360
+ if (isWide(to) === isWide(was))
1361
+ swap();
1362
+ else
1363
+ await movePanel(isWide(to), swap);
1364
+ });
1365
+ }, []);
1366
+ const setMapOpen = useCallback((next) => {
1367
+ move({ map: next });
1368
+ }, [move]);
1369
+ const settingsWanted = useStudioSettings();
1370
+ useEffect(() => {
1371
+ move({ settings: settingsWanted });
1372
+ }, [move, settingsWanted]);
1373
+ /**
1374
+ * Opens a node from the tree, the editor, or the map, closing Keys over it. The map stays open
1375
+ * beside the editor; on a phone it covers the editor, so it closes.
1376
+ */
1377
+ const open = useCallback((id) => {
1378
+ setKeysOpen(false);
1379
+ store.select(id);
1380
+ setEditorView('editor');
1381
+ if (!globalThis.matchMedia('(min-width: 768px)').matches)
1382
+ setMapOpen(false);
1383
+ }, [store, setMapOpen]);
1384
+ return {
1385
+ keysOpen,
1386
+ setKeysOpen,
1387
+ editorView,
1388
+ setEditorView,
1389
+ mapOpen,
1390
+ setMapOpen,
1391
+ settingsOpen,
1392
+ open,
1393
+ };
1394
+ }
1395
+ /** What the page does for its buttons and for th30: swap in a workspace (with undo), and copy. */
1396
+ function usePageActions(store, view, setConversation) {
1397
+ const toast = useToast();
1398
+ const { copy: writeClipboard } = useClipboard();
1399
+ const { setKeysOpen, setEditorView } = view;
1400
+ /** Swaps in a whole new workspace; the toast can put the old one back. */
1401
+ const replaceWorkspace = useCallback((next, message, by = 'visitor') => {
1402
+ const previous = store.getWorkspace();
1403
+ store.update(next, by);
1404
+ setKeysOpen(false);
1405
+ setEditorView('editor');
1406
+ // New agents start new conversations; the old transcripts don't carry over.
1407
+ clearConversation();
1408
+ setConversation((count) => count + 1);
1409
+ const dismiss = toast({
1410
+ body: message,
1411
+ endContent: (_jsx(Button, { label: "Undo", variant: "ghost", size: "sm", onClick: () => {
1412
+ store.update(previous);
1413
+ dismiss();
1414
+ } })),
1415
+ });
1416
+ }, [store, setKeysOpen, setEditorView, setConversation, toast]);
1417
+ const copy = useCallback((text, what) => {
1418
+ void writeClipboard(text).then((copied) => toast(copied
1419
+ ? { body: `Copied ${what}.` }
1420
+ : { body: "Couldn't reach the clipboard.", type: 'error' }));
1421
+ }, [toast, writeClipboard]);
1422
+ return { replaceWorkspace, copy };
1423
+ }
1424
+ /**
1425
+ * th30 sees and works in the studio through its surface, mounted while the page is open. Its
1426
+ * tools read the page through a ref, so they register once and always see the latest.
1427
+ */
1428
+ function useStudioSurface(store, page) {
1429
+ const { mountSurface } = useStudioHost();
1430
+ const chatRef = useRef(null);
1431
+ const pageRef = useRef(page);
1432
+ pageRef.current = page;
1433
+ useEffect(() => mountSurface?.(studioSurface(studioSurfaceHost(store, pageRef, chatRef))), [store, mountSurface]);
1434
+ return chatRef;
1435
+ }
1436
+ /** What the open agent's editor knows of the others, and its allow list. */
1437
+ function useWorkspaceContext(agents, focus, update, ownsProviders) {
1438
+ return useMemo(() => {
1439
+ const self = agents.find((agent) => agent.key === focus);
1440
+ return {
1441
+ agents: agents.map((agent) => ({
1442
+ key: agent.key,
1443
+ agentId: agent.identity.agentId.trim(),
1444
+ type: agent.identity.profileType,
1445
+ })),
1446
+ self: focus,
1447
+ allowed: self?.tools.allow ?? [],
1448
+ setAllowed: (toolKey, allowed) => {
1449
+ update((current) => setToolAllowed(current, focus, toolKey, allowed));
1450
+ },
1451
+ addProvider: ownsProviders
1452
+ ? (provider) => {
1453
+ update((current) => addProvider(current, provider));
1454
+ }
1455
+ : undefined,
1456
+ updateProvider: ownsProviders
1457
+ ? (provider) => {
1458
+ update((current) => withProvider(current, provider));
1459
+ }
1460
+ : undefined,
1461
+ sharing: (bindingKey, id, apiId) => modelSharers({ agents }, focus, bindingKey, id, apiId),
1462
+ };
1463
+ }, [agents, focus, update, ownsProviders]);
1464
+ }
1465
+ /** The node the editor has open: the selected one while it exists, else the open agent's identity. */
1466
+ function selectedNode(workspace, focus) {
1467
+ return workspaceNodeRef(workspace, workspace.selected) ? workspace.selected : agentNodeId(focus);
1468
+ }
1469
+ /** A phone-only button that opens a sheet over the editor, or closes it (`null`). */
1470
+ function SheetButton({ label, icon, sheet, setSheet, }) {
1471
+ return (_jsx("span", { className: "studio-phone", children: _jsx(IconButton, { label: label, variant: "ghost", icon: _jsx(Icon, { icon: icon, size: "sm" }), onClick: () => {
1472
+ setSheet(sheet);
1473
+ } }) }));
1474
+ }
1475
+ /**
1476
+ * The profile tree's column: the studio's name and version over the agents and tools. It sits
1477
+ * out renders that change nothing it shows, such as the code view or a new compile.
1478
+ */
1479
+ const TreeColumn = memo(function TreeColumn({ tree, draft, onAddAgent, listRef, setSheet, }) {
1480
+ const project = useProject();
1481
+ const { kernelVersion } = useStudioHost();
1482
+ return (_jsx(Section, { variant: "transparent", width: TREE_WIDTH, height: "100%", padding: 4, dividers: ['end'], children: _jsxs(VStack, { gap: 4, height: "100%", children: [_jsxs(HStack, { gap: 2, vAlign: "start", children: [_jsx(StackItem, { size: "fill", children: _jsxs(VStack, { gap: 1, children: [_jsx(Heading, { level: 3, children: "theorem studio" }), _jsx(Text, { type: "supporting", color: "secondary", children: `${project ? `${project.name} · ` : ''}@theoremjs/agents ${kernelVersion}` })] }) }), _jsx(SheetButton, { label: "Close sections", icon: IconX, sheet: null, setSheet: setSheet })] }), _jsx(StackItem, { size: "fill", children: _jsx(WorkspaceTreeLists, { tree: tree, draft: draft, onAddAgent: onAddAgent, listRef: listRef }) })] }) }));
1483
+ });
1484
+ /**
1485
+ * The issue count, which goes to the next issue after the selected node and shows it there. Hidden
1486
+ * with none. It counts every issue, so what blocks a run or Save always has a way to it.
1487
+ */
1488
+ function IssueToken({ compile, selected, onIssue, }) {
1489
+ const { compiled } = compile;
1490
+ if (compiled.ok)
1491
+ return null;
1492
+ const issues = issuesLabel(compiled.issues.length);
1493
+ if (!issues)
1494
+ return null;
1495
+ return (_jsx(Token, { label: issues, color: "orange", description: "Go to the next issue", onClick: () => {
1496
+ onIssue(nextIssueNode(compiled.issues, selected));
1497
+ } }));
1498
+ }
1499
+ /** Replaces the workspace with one blank agent or one of the examples. */
1500
+ /**
1501
+ * The one button for what the panel shows: the form or the code, which closes Keys, and the map
1502
+ * beside either, which opens and closes.
1503
+ */
1504
+ function ViewToggleButton({ view }) {
1505
+ const current = EDITOR_VIEWS.find((each) => each.id === view.editorView) ?? EDITOR_VIEWS[0];
1506
+ return (_jsx(DropdownMenu, { button: {
1507
+ label: current.label,
1508
+ variant: 'ghost',
1509
+ isIconOnly: true,
1510
+ icon: _jsx(Icon, { icon: current.icon, size: "sm" }),
1511
+ tooltip: 'Switch between the editor, the code, and the map',
1512
+ }, menuWidth: "15rem", hasChevron: false, placement: "below", alignment: "end", items: [
1513
+ ...EDITOR_VIEWS.map((each) => ({
1514
+ id: each.id,
1515
+ label: each.label,
1516
+ description: oneLine(each.description),
1517
+ icon: _jsx(Icon, { icon: each.icon, size: "sm" }),
1518
+ endContent: each.id === current.id && !view.keysOpen ? (_jsx(Icon, { icon: IconCheck, size: "sm" })) : undefined,
1519
+ onClick: () => {
1520
+ view.setKeysOpen(false);
1521
+ view.setEditorView(each.id);
1522
+ },
1523
+ })),
1524
+ {
1525
+ id: 'map',
1526
+ label: 'Map',
1527
+ description: oneLine('See how everything connects'),
1528
+ icon: _jsx(Icon, { icon: IconSitemap, size: "sm" }),
1529
+ endContent: view.mapOpen ? _jsx(Icon, { icon: IconCheck, size: "sm" }) : undefined,
1530
+ onClick: () => {
1531
+ view.setMapOpen(!view.mapOpen);
1532
+ },
1533
+ },
1534
+ ] }));
1535
+ }
1536
+ /**
1537
+ * Reset for what is open: a library tool, a provider, or else the agent. It goes back to how it joined the
1538
+ * workspace, and nothing else changes. `run` is absent while it is still as it started. `all` does
1539
+ * it for everything: every agent and tool back to its start, or on a project every unsaved edit
1540
+ * gone once the builder says yes. It is absent while there is nothing to put back.
1541
+ */
1542
+ function useReset({ workspace, focus, update }) {
1543
+ const toast = useToast();
1544
+ const save = useProjectSave();
1545
+ const toolKey = toolSpecKeyOf(workspace.selected);
1546
+ const provider = providerIdOf(workspace.selected);
1547
+ const what = provider !== undefined ? 'provider' : toolKey === undefined ? 'agent' : 'tool';
1548
+ const next = useMemo(() => {
1549
+ if (provider !== undefined)
1550
+ return resetProvider(workspace, provider);
1551
+ return toolKey === undefined
1552
+ ? resetAgent(workspace, focus)
1553
+ : resetLibraryTool(workspace, toolKey);
1554
+ }, [workspace, focus, toolKey, provider]);
1555
+ const everything = useMemo(() => resetAll(workspace), [workspace]);
1556
+ const put = (to, body) => to === workspace
1557
+ ? undefined
1558
+ : () => {
1559
+ update(() => to);
1560
+ const dismiss = toast({
1561
+ body,
1562
+ endContent: (_jsx(Button, { label: "Undo", variant: "ghost", size: "sm", onClick: () => {
1563
+ update(() => workspace);
1564
+ dismiss();
1565
+ } })),
1566
+ });
1567
+ };
1568
+ return {
1569
+ what,
1570
+ run: put(next, `Reset the ${what} to how it started.`),
1571
+ all: save ? save.discard : put(everything, 'Reset every agent and tool to how it started.'),
1572
+ inProject: save !== null,
1573
+ };
1574
+ }
1575
+ /** Opens the project's files again in place of every unsaved edit. Undo brings the edits back. */
1576
+ function useDiscardAll({ store, filesChanged }) {
1577
+ const toast = useToast();
1578
+ return useCallback(() => {
1579
+ openProject().then(({ workspace: files }) => {
1580
+ const before = store.getWorkspace();
1581
+ store.takeFiles(reopened(files, before));
1582
+ filesChanged();
1583
+ const dismiss = toast({
1584
+ body: 'Discarded every unsaved edit.',
1585
+ endContent: (_jsx(Button, { label: "Undo", variant: "ghost", size: "sm", onClick: () => {
1586
+ store.update(before);
1587
+ dismiss();
1588
+ } })),
1589
+ });
1590
+ }, () => {
1591
+ toast({
1592
+ type: 'error',
1593
+ body: 'The studio server did not answer, so your edits are still here.',
1594
+ });
1595
+ });
1596
+ }, [store, filesChanged, toast]);
1597
+ }
1598
+ /** The editor column's header: its title, the next issue, Keys, what to start from, and the view toggle. */
1599
+ function EditorToolbar({ heading, compile, selected, view, onIssue, reset, project, setSheet, }) {
1600
+ return (_jsx(Section, { variant: "transparent", padding: 3, dividers: ['bottom'], children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(SheetButton, { label: "Sections", icon: IconMenu2, sheet: "tree", setSheet: setSheet }), _jsx(StackItem, { size: "fill", children: heading && (_jsx("span", { title: heading, children: _jsx(Heading, { level: 4, maxLines: 1, children: heading }) })) }), _jsx(StackItem, { size: "static", children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(IssueToken, { compile: compile, selected: selected, onIssue: onIssue }), project] }) }), _jsx(KeysButton, { view: view }), _jsx(DropdownMenu, { button: {
1601
+ label: 'Reset',
1602
+ variant: 'ghost',
1603
+ isIconOnly: true,
1604
+ icon: _jsx(Icon, { icon: IconRotateClockwise, size: "sm" }),
1605
+ tooltip: reset.run || reset.all
1606
+ ? `Reset this ${reset.what}, or everything`
1607
+ : 'Everything is as it started',
1608
+ isDisabled: !reset.run && !reset.all,
1609
+ }, menuWidth: "15rem", hasChevron: false, placement: "below", alignment: "end", items: [
1610
+ {
1611
+ id: 'this',
1612
+ label: `Reset this ${reset.what}`,
1613
+ description: oneLine('Back to how it started. Undo brings your changes back.'),
1614
+ icon: _jsx(Icon, { icon: IconRotateClockwise, size: "sm" }),
1615
+ isDisabled: !reset.run,
1616
+ onClick: reset.run,
1617
+ },
1618
+ {
1619
+ id: 'all',
1620
+ label: reset.inProject ? 'Discard all edits' : 'Reset everything',
1621
+ description: oneLine(reset.inProject
1622
+ ? 'Every unsaved edit goes, and the studio opens your files as they are.'
1623
+ : 'Every agent and tool, back to how it started.'),
1624
+ icon: _jsx(Icon, { icon: IconRestore, size: "sm" }),
1625
+ isDisabled: !reset.all,
1626
+ onClick: reset.all,
1627
+ },
1628
+ ] }), _jsx(ViewToggleButton, { view: view }), _jsx(SheetButton, { label: "Preview", icon: IconPlayerPlay, sheet: "preview", setSheet: setSheet })] }) }));
1629
+ }
1630
+ /** The door to the Keys panel in the editor's header. A host with Settings keeps Keys there, so it has none. */
1631
+ function KeysButton({ view }) {
1632
+ const { hasSettings } = useStudioHost();
1633
+ if (hasSettings)
1634
+ return null;
1635
+ return (_jsx(IconButton, { label: "Keys", variant: "ghost", icon: _jsx(Icon, { icon: IconKey, size: "sm" }), "aria-pressed": view.keysOpen, tooltip: "Add your own model keys to run this agent with", onClick: () => {
1636
+ view.setKeysOpen((open) => !open);
1637
+ view.setEditorView('editor');
1638
+ } }));
1639
+ }
1640
+ /** The Keys panel, in the editor's column. */
1641
+ function KeysBody({ connection, state, }) {
1642
+ return (_jsx(ScrollableArea, { label: "Keys", height: "100%", children: _jsx(KeySlots, { connection: connection, state: state }) }));
1643
+ }
1644
+ /** The key slots, renamed or let go across every agent. */
1645
+ function KeySlots({ connection, state, }) {
1646
+ const { setDraft, update } = state;
1647
+ return (_jsx(StudioKeys, { connection: connection, onAddSlot: (slot) => {
1648
+ setDraft((current) => current.models.key ? current : { ...current, models: { ...current.models, key: slot } });
1649
+ }, onRenameSlot: (from, to) => {
1650
+ update((current) => ({
1651
+ ...current,
1652
+ agents: current.agents.map((agent) => swapKeySlot(agent, from, to)),
1653
+ }));
1654
+ }, onRemoveSlot: (slot) => {
1655
+ update((current) => ({
1656
+ ...current,
1657
+ agents: current.agents.map((agent) => swapKeySlot(agent, slot, '')),
1658
+ }));
1659
+ } }));
1660
+ }
1661
+ /**
1662
+ * Whether Settings is asked for, for a host that has a door to them. They open in the studio's
1663
+ * panel, with the tree, the editor, and the preview kept under them: a run goes on, and the editor
1664
+ * is where it was left.
1665
+ */
1666
+ function useStudioSettings() {
1667
+ const { hasSettings } = useStudioHost();
1668
+ useEffect(() => (hasSettings ? settingsScreen() : undefined), [hasSettings]);
1669
+ useEffect(() => {
1670
+ if (hasSettings)
1671
+ warmWhenIdle(settingsPanel);
1672
+ }, [hasSettings]);
1673
+ const door = useSettingsDoor();
1674
+ return hasSettings === true && door.open;
1675
+ }
1676
+ const HIDDEN = { display: 'none' };
1677
+ /** Opens Keys where the host keeps them: in Settings, or in the editor's column. */
1678
+ function useOpenKeys(setKeysOpen) {
1679
+ const { hasSettings } = useStudioHost();
1680
+ return useCallback((open) => {
1681
+ if (!hasSettings)
1682
+ setKeysOpen(open);
1683
+ else if (open)
1684
+ openSettings('keys');
1685
+ else
1686
+ closeSettings();
1687
+ }, [hasSettings, setKeysOpen]);
1688
+ }
1689
+ /** Settings for the open project, with what they reach in the studio. */
1690
+ function SettingsPane({ state, connection, project, setConversation, }) {
1691
+ const { kernelVersion } = useStudioHost();
1692
+ const { store } = state;
1693
+ const reset = useReset(state);
1694
+ const toast = useToast();
1695
+ const quiet = useSyncExternalStore(store.subscribe, store.isQuiet, store.isQuiet);
1696
+ const shared = useMemo(() => ({
1697
+ asks: !quiet,
1698
+ set: (asks) => {
1699
+ store.setQuiet(!asks);
1700
+ },
1701
+ }), [quiet, store]);
1702
+ const clearChats = useCallback(() => {
1703
+ clearConversation();
1704
+ setConversation((count) => count + 1);
1705
+ toast({ body: "Cleared this tab's chats." });
1706
+ }, [setConversation, toast]);
1707
+ return (_jsx(Suspense, { fallback: null, children: _jsx(StudioSettings, { project: project, version: kernelVersion, watch: state.watchFiles, shared: shared, actions: { clearChats, discard: reset.inProject ? reset.all : undefined }, keys: _jsx(KeySlots, { connection: connection, state: state }) }) }));
1708
+ }
1709
+ /** The open node's editor, keyed by node so each one opens at its top. */
1710
+ function EditorBody({ state, connection, selected, editing, issues, listBadges, editorRef, open, }) {
1711
+ const { workspace, draft, focus, setDraft, update } = state;
1712
+ const project = useProject();
1713
+ const workspaceContext = useWorkspaceContext(workspace.agents, focus, update, project !== null);
1714
+ const provider = shownProvider(workspace, editing, project !== null, connection);
1715
+ return (_jsx(ScrollableArea, { label: "Editor", height: "100%", ref: editorRef, children: _jsx(ListBadges, { value: listBadges, children: _jsx(RunsOn.Provider, { value: connection.on, children: _jsx(LocalConnection.Provider, { value: connection, children: _jsx(WorkspaceContext.Provider, { value: workspaceContext, children: provider ? (_jsx(ProviderEditor, { workspace: workspace, provider: provider, issues: issues.filter((issue) => issue.nodeId === editing),
1716
+ // The studio's own providers are the same for everyone, so they only show.
1717
+ onChange: project
1718
+ ? (next) => {
1719
+ update((current) => withProvider(current, next));
1720
+ }
1721
+ : undefined,
1722
+ // A provider the files do not hold yet is the studio's to name; code names the rest.
1723
+ onRename: project && !project.providers.includes(provider.id)
1724
+ ? (id) => {
1725
+ update((current) => renameProvider(current, provider.id, id));
1726
+ }
1727
+ : undefined })) : (_jsx(ProfileEditor, { draft: draft, setDraft: setDraft, selectedId: editing, onSelect: (id) => {
1728
+ open(scopedNodeId(focus, id));
1729
+ }, issues: issues })) }) }) }) }) }, selected));
1730
+ }
1731
+ /** Clears the conversation and its traces; the profile stays. */
1732
+ function ClearHistoryButton({ run }) {
1733
+ return (_jsx(IconButton, { label: "Clear history", variant: "ghost", icon: _jsx(Icon, { icon: IconPlaylistX, size: "sm" }), tooltip: "Clear the conversation and its traces. Your profile stays.", onClick: run.clear }));
1734
+ }
1735
+ /** The preview's header: back to the editor, the agent to chat with, history, trace, and Export. */
1736
+ function PreviewHeader({ store, chatAgents, chatWith, compile, run, connection, trace, testing, copy, setSheet, }) {
1737
+ const testLabel = testing.open ? 'Close guardrail test' : 'Test guardrails';
1738
+ const t = useLabels();
1739
+ const traceLabel = t(trace.open ? '@theorem.panel.trace.hide' : '@theorem.panel.trace.show');
1740
+ const { compiled } = compile;
1741
+ return (_jsx(Section, { variant: "transparent", paddingInline: 3, paddingBlock: 0, children: _jsxs(HStack, { gap: 2, vAlign: "center", children: [_jsx(SheetButton, { label: "Back to the editor", icon: IconArrowLeft, sheet: null, setSheet: setSheet }), _jsx(StackItem, { size: "fill", children: _jsx(ChatPicker, { agents: chatAgents, chatWith: chatWith, onChange: store.chatWith }) }), _jsx(StackItem, { size: "static", children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(ActingAsButton, {}), compile.payload && run.isUsed && !testing.open && _jsx(ClearHistoryButton, { run: run }), compile.payload && (_jsx(IconButton, { label: testLabel, variant: "ghost", tooltip: testLabel, icon: _jsx(Icon, { icon: IconShieldSearch, size: "sm" }), "aria-pressed": testing.open, onClick: testing.toggle })), _jsx(ExportActions, { store: store, compiled: compiled.ok ? compiled : undefined, chatted: compile.chatted, chattedId: compile.chattedId, blocked: compile.blocked, copy: copy, connection: {
1742
+ connectionMode: connection.mode,
1743
+ localBaseUrl: connection.local.baseUrl,
1744
+ } }), compile.traced && !testing.open ? (_jsx(IconButton, { label: traceLabel, variant: trace.open ? 'secondary' : 'ghost', tooltip: traceLabel, icon: _jsx(Icon, { icon: IconActivity, size: "sm" }), "aria-pressed": trace.open, onClick: trace.toggle })) : null] }) })] }) }));
1745
+ }
1746
+ /**
1747
+ * Why the agent to chat with does not run: the errors in its config, or the workspace's when its
1748
+ * own has none, each a way to where it is set. Nothing while a config that compiles settles.
1749
+ */
1750
+ function ConfigIssues({ compile, chatWith, onIssue, }) {
1751
+ const { compiled } = compile;
1752
+ if (compiled.ok)
1753
+ return null;
1754
+ const root = agentNodeId(chatWith);
1755
+ const own = compiled.issues.filter((issue) => issue.nodeId === root || issue.nodeId.startsWith(`${root}/`));
1756
+ const shown = own.length > 0 ? own : compiled.issues;
1757
+ const errors = shown.length === 1 ? 'an error' : `${String(shown.length)} errors`;
1758
+ return (_jsx(PaneFailure, { title: `The agent config has ${errors}`, description: "Fix it to run the agent.", children: _jsx(VStack, { gap: 2, children: shown.map((issue) => (_jsx(Banner, { status: "error", title: issue.message, endContent: _jsx(Button, { label: "Go to it", variant: "ghost", size: "sm", onClick: () => {
1759
+ onIssue(issue.nodeId);
1760
+ } }) }, `${issue.nodeId}:${issue.field ?? ''}:${issue.message}`))) }) }));
1761
+ }
1762
+ /**
1763
+ * The runner for the compiled agent, its conversation saved as it goes, or the guardrail tester
1764
+ * over it; while none compiles, why.
1765
+ */
1766
+ function PreviewBody({ compile, run, connection, chatRef, chatWith, profileId, traceOpen, testing, onIssue, }) {
1767
+ const { payload, traced, tested } = compile;
1768
+ /** Why the project's server does not run this profile: its files say, not the editor. */
1769
+ const unserved = useProject()?.problems.find((problem) => problem.profile === profileId);
1770
+ const pageInputs = useMemo(() => (payload ? pageInputsOf(payload) : null), [payload]);
1771
+ const [pageValues] = usePageValues(chatWith);
1772
+ const sent = useMemo(() => sentPageValues(pageInputs, pageValues), [pageInputs, pageValues]);
1773
+ const shown = payload && !unserved
1774
+ ? {
1775
+ chatKey: run.chatKey,
1776
+ generation: run.generation,
1777
+ props: {
1778
+ payload,
1779
+ onActivity: run.markUsed,
1780
+ conversation: run.conversation,
1781
+ initialText: run.initialText,
1782
+ onConversationChange: (ref) => {
1783
+ saveConversation(run.chatKey, run.sealer, ref);
1784
+ },
1785
+ slots: sent.slots,
1786
+ context: sent.context,
1787
+ tested,
1788
+ },
1789
+ }
1790
+ : null;
1791
+ const kept = useKeptRuns(run, shown);
1792
+ const blocked = unserved ? (_jsx(PaneFailure, { title: `${unserved.profile} does not run here yet`, description: unserved.message })) : payload ? null : (_jsx(ConfigIssues, { compile: compile, chatWith: chatWith, onIssue: onIssue }));
1793
+ return (_jsx(InPlace, { view: testing && payload ? (_jsx(RaisedPane, { children: (isWide) => (_jsx(Suspense, { fallback: null, children: _jsx(GuardrailTester, { payload: payload, isWide: isWide }) })) })) : null, children: _jsx(VStack, { height: "100%", children: _jsxs(StackItem, { size: "fill", children: [blocked, kept.map(([key, { props }]) => {
1794
+ const isShown = shown !== null && key === run.runKey;
1795
+ return (_jsx("div", { style: { display: isShown ? 'contents' : 'none' }, children: _jsx(StudioRunner, { ...props, mode: connection.mode, runtime: connection.runtime, trace: traced ? traceOpen : undefined, chatRef: isShown ? chatRef : undefined }) }, key));
1796
+ })] }) }) }));
1797
+ }
1798
+ /**
1799
+ * Every runner the preview has shown, each as it was last shown. Switching agents hides one and
1800
+ * shows another, so neither loses its conversation, results, trace or a call still running. A
1801
+ * cleared chat drops its own runner; another mode or a new workspace drops them all.
1802
+ */
1803
+ function useKeptRuns(run, shown) {
1804
+ const [kept] = useState(() => new Map());
1805
+ for (const [key, each] of kept) {
1806
+ const replaced = each.chatKey === run.chatKey && key !== run.runKey;
1807
+ if (each.generation !== run.generation || replaced)
1808
+ kept.delete(key);
1809
+ }
1810
+ if (shown)
1811
+ kept.set(run.runKey, shown);
1812
+ return [...kept];
1813
+ }
1814
+ /**
1815
+ * The compiled agent to chat with, under its header; while none compiles, why. It sits out renders
1816
+ * that change nothing it shows: typing in the editor, opening a node, Keys.
1817
+ */
1818
+ const PreviewPane = memo(function PreviewPane({ store, chatAgents, chatWith, compile, run, connection, chatRef, copy, setSheet, onIssue, }) {
1819
+ const [traceOpen, setTraceOpen] = useState(false);
1820
+ const [testing, setTesting] = useState(false);
1821
+ return (_jsx(LayoutContent, { className: "studio-preview shell-fade", isScrollable: false, padding: 0, children: _jsxs(VStack, { height: "100%", children: [_jsx(PreviewHeader, { store: store, chatAgents: chatAgents, chatWith: chatWith, compile: compile, run: run, connection: connection, trace: {
1822
+ open: traceOpen,
1823
+ toggle: () => {
1824
+ setTraceOpen((open) => !open);
1825
+ },
1826
+ }, testing: {
1827
+ open: testing,
1828
+ toggle: () => {
1829
+ setTesting((open) => !open);
1830
+ },
1831
+ }, copy: copy, setSheet: setSheet }), _jsx(StackItem, { size: "fill", children: _jsx(PreviewBody, { compile: compile, run: run, connection: connection, chatRef: chatRef, chatWith: chatWith, profileId: chatAgents.find((agent) => agent.value === chatWith)?.label, traceOpen: traceOpen, testing: testing, onIssue: onIssue }) })] }) }));
1832
+ });
1833
+ /** Tells the host what is on screen as it changes, and that it closed. */
1834
+ function useScreenReport(onReport, { agent, type, issues, section }) {
1835
+ useEffect(() => {
1836
+ onReport?.({ agent, type, issues, section });
1837
+ }, [onReport, agent, type, issues, section]);
1838
+ useEffect(() => () => onReport?.(null), [onReport]);
1839
+ }
1840
+ /** Everything the page holds: the workspace, its connection, the editor's view, the compile, and the run. */
1841
+ /** What the page opens on: the studio's own loader data, or an open project's. */
1842
+ function useStudioPage(opened) {
1843
+ const { onReport } = useStudioHost();
1844
+ const state = useStudioWorkspace(opened.start, opened.project);
1845
+ const { store, workspace, draft, focus } = state;
1846
+ const connection = useWorkspaceConnection(workspace);
1847
+ const view = useEditorView(store);
1848
+ const frame = useStudioFrame();
1849
+ const selected = selectedNode(workspace, focus);
1850
+ /** The open node as the editor names it: the open agent's own id, or a library tool's. */
1851
+ const editing = innerNodeId(selected, focus) ?? 'identity';
1852
+ const issueReveal = useIssueReveal(selected);
1853
+ const compile = useWorkspaceCompile(workspace, focus, connection);
1854
+ /** On a phone, the tree or the preview, each over the editor; neither shows beside it there. */
1855
+ const [sheet, setSheet] = useState(null);
1856
+ const { open: openNode } = view;
1857
+ const { reveal } = issueReveal;
1858
+ /** Opens the node an issue is on and shows it there, from the editor's count or the preview. */
1859
+ const goToIssue = useCallback((node) => {
1860
+ openNode(node);
1861
+ reveal({ node });
1862
+ setSheet(null);
1863
+ }, [openNode, reveal]);
1864
+ const [askedQuestion, setAskedQuestion] = useState();
1865
+ const { chatWith } = workspace;
1866
+ /** Opens the decision being run, in the editor, at the question an answer is for. */
1867
+ const questionLink = useMemo(() => ({
1868
+ asked: askedQuestion,
1869
+ open: (id) => {
1870
+ openNode(scopedNodeId(chatWith, 'decision'));
1871
+ setAskedQuestion(id);
1872
+ setSheet(null);
1873
+ },
1874
+ shown: () => {
1875
+ setAskedQuestion(undefined);
1876
+ },
1877
+ }), [askedQuestion, chatWith, openNode]);
1878
+ // Settings take the panel; a phone's sheet over it would hide them.
1879
+ const { settingsOpen } = view;
1880
+ useEffect(() => {
1881
+ if (settingsOpen)
1882
+ setSheet(null);
1883
+ }, [settingsOpen]);
1884
+ // The project as its files are now: a Save changes which profiles they hold.
1885
+ const project = useMemo(() => (opened.project ? { ...opened.project, ...state.files } : null), [opened.project, state.files]);
1886
+ const actingAs = useActingAsState(project);
1887
+ const run = useConversationRun(connection.mode,
1888
+ // A run in the page seals with the tab's key; the hosted demo seals on its server.
1889
+ connection.runtime ? 'page' : 'demo', workspace.chatWith, actingAs.actingAs, opened.question);
1890
+ useArrivalToast(opened, store);
1891
+ const { replaceWorkspace, copy } = usePageActions(store, view, run.setConversation);
1892
+ const asking = useConfirmWrite();
1893
+ const openKeys = useOpenKeys(view.setKeysOpen);
1894
+ const chatRef = useStudioSurface(store, {
1895
+ confirmWrite: asking.confirm,
1896
+ on: connection.on,
1897
+ connection,
1898
+ replaceWorkspace,
1899
+ copy,
1900
+ setKeysOpen: openKeys,
1901
+ newConversation: run.clear,
1902
+ project: opened.project ?? null,
1903
+ });
1904
+ const title = providerIdOf(editing) ??
1905
+ (toolSpecKeyOf(editing) === undefined
1906
+ ? editorTitle(draft, editing)
1907
+ : workspaceTree(state.workspace).tools.find((node) => node.id === editing)?.label);
1908
+ useScreenReport(onReport, screenReport(draft, compile.compiled, title));
1909
+ return {
1910
+ state,
1911
+ connection,
1912
+ view,
1913
+ frame,
1914
+ selected,
1915
+ editing,
1916
+ issueReveal,
1917
+ goToIssue,
1918
+ questionLink,
1919
+ compile,
1920
+ sheet,
1921
+ setSheet,
1922
+ run,
1923
+ copy,
1924
+ chatRef,
1925
+ title,
1926
+ asking,
1927
+ project,
1928
+ actingAs,
1929
+ };
1930
+ }
1931
+ /**
1932
+ * The profile tree beside the editor (or code) for the draft in a panel on the left; the compiled
1933
+ * agent on the right, under Get code. The draft compiles as it changes; while it doesn't
1934
+ * compile, the agent stays the last one that did.
1935
+ */
1936
+ function Studio({ opened }) {
1937
+ const page = useStudioPage(opened);
1938
+ const { state, connection, view, frame, selected, editing, issueReveal, compile } = page;
1939
+ const { goToIssue, sheet, setSheet, run, copy, chatRef, title, asking, project, actingAs } = page;
1940
+ const { questionLink } = page;
1941
+ const { store, draft } = state;
1942
+ const tree = useWorkspaceTree(state, view, selected, setSheet);
1943
+ const chatAgents = useChatAgents(state.workspace.agents);
1944
+ const addAgentFrom = useAddAgent(store, state.update, view.open);
1945
+ const { setMapOpen } = view;
1946
+ const closeMap = useCallback(() => {
1947
+ setMapOpen(false);
1948
+ }, [setMapOpen]);
1949
+ return (_jsx(ProjectContext.Provider, { value: project, children: _jsx(ActingAsContext.Provider, { value: actingAs, children: _jsx(ConfirmWrite, { value: asking.confirm, children: _jsxs(ProjectAsks, { value: state.asks ?? NO_ASKS, children: [asking.dialog, _jsx(SharedChange, { store: state.store }), _jsx(SaveScope, { project: project, state: state, blocked: Boolean(compile.blocked), children: _jsx(QuestionLink, { value: questionLink, children: _jsx(Layout, { ref: frame.layoutCallbackRef, className: frameClass(sheet, view.mapOpen, view.settingsOpen), padding: 0, start: _jsx(SidePanel, { frame: frame, tree: tree, draft: draft, onAddAgent: addAgentFrom, setSheet: setSheet, settings: view.settingsOpen &&
1950
+ project && (_jsx(SettingsPane, { state: state, connection: connection, project: project, setConversation: run.setConversation })), map: view.mapOpen && (_jsx(Suspense, { fallback: null, children: _jsx(StudioMap, { workspace: state.workspace, shared: state.shared, selected: view.keysOpen ? '' : selected, onOpen: view.open, onClose: closeMap }) })), children: _jsx(EditorColumn, { heading: view.keysOpen ? 'Keys' : headingOf(tree.list, editing, title), state: state, connection: connection, view: view, compile: compile, selected: selected, editing: editing, frame: frame, issueReveal: issueReveal, goToIssue: goToIssue, setSheet: setSheet }) }), content: _jsx(PreviewPane, { store: store, chatAgents: chatAgents, chatWith: state.workspace.chatWith, compile: compile, run: run, connection: connection, chatRef: chatRef, copy: copy, setSheet: setSheet, onIssue: goToIssue }) }) }) })] }) }) }) }));
1951
+ }
1952
+ /** Save for the screen under it, when the studio is open on a project. */
1953
+ function SaveScope({ project, state, blocked, children, }) {
1954
+ const discard = useDiscardAll(state);
1955
+ const { showConflict } = state;
1956
+ const choose = useCallback(() => {
1957
+ showConflict(true);
1958
+ }, [showConflict]);
1959
+ if (!project)
1960
+ return children;
1961
+ return (_jsxs(ProjectSave, { project: project, workspace: state.workspace, update: state.update, onFilesChanged: state.filesChanged, onDiscard: discard,
1962
+ // A Save would be built on files that changed: it waits for the builder's choice.
1963
+ blocked: blocked || Boolean(state.conflict), children: [_jsx(ChooseConflictsContext.Provider, { value: state.conflict ? choose : undefined, children: children }), state.conflict?.open && (_jsx(FileConflicts, { conflicts: state.conflict.merge.conflicts, files: state.conflict.files, onApply: state.settle, onClose: () => {
1964
+ state.showConflict(false);
1965
+ } }))] }));
1966
+ }
1967
+ /** Adds an agent and opens it. */
1968
+ function useAddAgent(store, update, open) {
1969
+ return useCallback((add) => {
1970
+ update(add);
1971
+ open(store.getWorkspace().selected);
1972
+ }, [store, update, open]);
1973
+ }
1974
+ /** What the tree reads of the page; opening a node closes the phone's sheet, and Keys selects nothing. */
1975
+ function useWorkspaceTree(state, view, selected, setSheet) {
1976
+ const { workspace, focus, update, setDraft } = state;
1977
+ const { open } = view;
1978
+ const selectedId = view.keysOpen ? '' : selected;
1979
+ const { list, setList } = useListShown(selectedId);
1980
+ const ownsProviders = useProject() !== null;
1981
+ return useMemo(() => ({
1982
+ workspace,
1983
+ focus,
1984
+ selectedId,
1985
+ list,
1986
+ ownsProviders,
1987
+ setList,
1988
+ onSelect: (id) => {
1989
+ open(id);
1990
+ setSheet(null);
1991
+ },
1992
+ update,
1993
+ setDraft,
1994
+ }), [workspace, focus, selectedId, list, ownsProviders, setList, open, setSheet, update, setDraft]);
1995
+ }
1996
+ /** The resizable side panel: the tree beside the editor column (`children`), and its handle. */
1997
+ function SidePanel({ frame, tree, draft, onAddAgent, setSheet, settings, map, children, }) {
1998
+ return (_jsxs(_Fragment, { children: [_jsx(LayoutPanel, { resizable: frame.sidePanel.props, padding: 0, className: "studio-side", role: "navigation", label: "Studio", isScrollable: false, children: _jsxs(Section, { variant: "raised", height: "100%", padding: 0, "data-shell-frame": "", children: [settings, _jsxs(HStack, { height: "100%", className: "shell-fade", style: settings ? HIDDEN : undefined, children: [_jsx(StackItem, { size: "static", className: "studio-tree", children: _jsx(TreeColumn, { tree: tree, draft: draft, onAddAgent: onAddAgent, listRef: frame.sidebarRef, setSheet: setSheet }) }), _jsx(StackItem, { size: "fill", className: "studio-view", children: children }), map && (_jsxs(_Fragment, { children: [_jsx(ResizeHandle, { className: "studio-map-handle", direction: "horizontal", isAlwaysVisible: false, resizable: frame.sidePanel.props, label: "Resize editor" }), _jsx(StackItem, { size: "fill", className: "studio-map-pane", children: map })] }))] })] }) }), _jsx(ResizeHandle, { className: "studio-side-handle shell-fade", direction: "horizontal", isAlwaysVisible: false, resizable: frame.sidePanel.props, label: "Resize profile" })] }));
1999
+ }
2000
+ /**
2001
+ * Over a section that is a shared setting: its name, where the project declares it, and how many
2002
+ * other profiles change with it.
2003
+ */
2004
+ function SharedLine({ entry, workspace, selected, onOpen, }) {
2005
+ const others = entry.agents.flatMap((key) => {
2006
+ const agent = workspace.agents.find((each) => each.key === key);
2007
+ return agent && !selected.startsWith(`${agentNodeId(key)}/`)
2008
+ ? [agent.identity.agentId || 'New agent']
2009
+ : [];
2010
+ });
2011
+ const count = others.length;
2012
+ return (_jsx(Section, { variant: "transparent", padding: 3, children: _jsx(Banner, { status: "info", title: `${entry.setting.label} · used by ${String(count)} other ${count === 1 ? 'profile' : 'profiles'}`, description: `${declaredAt(entry)}. A change here changes ${others.join(', ')} too.`, endContent: onOpen && _jsx(Button, { label: "Open", variant: "ghost", size: "sm", onClick: onOpen }) }) }));
2013
+ }
2014
+ /** Over a node one place in the project's files sets whole: what sets it, where, and the way there. */
2015
+ function OriginBanner({ origin, partly, onOpen, }) {
2016
+ const place = originPlace(origin);
2017
+ // The cause is the title. Here: what it does to this page, then the way out.
2018
+ const effect = partly ? 'The greyed rows are read-only here.' : 'Read-only here.';
2019
+ const way = place ? `Edit it at ${place}.` : originWay(origin);
2020
+ return (_jsx(Section, { variant: "transparent", padding: 3, children: _jsx(Banner, { status: "info", title: originLabel(origin), description: `${effect} ${way}`, endContent: place && _jsx(Button, { label: "Open", variant: "ghost", size: "sm", onClick: onOpen }) }) }));
2021
+ }
2022
+ /** The editor column: its toolbar over the Keys panel, the open node's editor, or its code. */
2023
+ function EditorColumn({ heading, state, connection, view, compile, selected, editing, frame, issueReveal, goToIssue, setSheet, }) {
2024
+ const { editorRef, revealed } = issueReveal;
2025
+ // Keys stands over the open node, so it doesn't say what sets it.
2026
+ const over = view.keysOpen;
2027
+ const shared = over ? undefined : sharedAt(state.shared, selected);
2028
+ const { shown, leavePage } = useLeftIssues(compile.editorIssues, selected, revealed);
2029
+ const reset = useReset(state);
2030
+ const project = useProject();
2031
+ const openOrigin = useOpenOrigin(project);
2032
+ const rowOrigins = useMemo(() => {
2033
+ const scope = state.origins && nodeOrigins(state.workspace, state.origins, selected);
2034
+ return scope ? { scope, open: openOrigin } : null;
2035
+ }, [state.origins, state.workspace, selected, openOrigin]);
2036
+ const whole = rowOrigins?.scope.section;
2037
+ const said = over ? undefined : (whole ?? rowOrigins?.scope.partly);
2038
+ return (_jsxs(VStack, { height: "100%", children: [_jsx(EditorToolbar, { heading: heading, compile: compile, selected: selected, view: view, onIssue: goToIssue, reset: reset, project: project && (_jsxs(_Fragment, { children: [state.conflict && (_jsx(Token, { label: conflictsLabel(state.conflict.merge.conflicts.length), color: "orange", description: "Choose between studio edits and source code", onClick: () => {
2039
+ state.showConflict(true);
2040
+ } })), _jsx(ProjectTokens, { project: project, unloaded: state.files?.unloaded })] })), setSheet: setSheet }), shared && (_jsx(SharedLine, { entry: shared, workspace: state.workspace, selected: selected, onOpen: project
2041
+ ? () => {
2042
+ openOrigin({
2043
+ path: [],
2044
+ kind: 'code',
2045
+ file: shared.setting.file,
2046
+ line: shared.setting.line,
2047
+ });
2048
+ }
2049
+ : undefined })), said && (_jsx(OriginBanner, { origin: said, partly: !whole, onOpen: () => {
2050
+ openOrigin(said);
2051
+ } })), _jsx(StackItem, { size: "fill", children: _jsx(RowOrigins, { value: rowOrigins, children: _jsx(LeavePage, { value: leavePage, children: _jsx(EditorColumnBody, { state: state, connection: connection, view: view, compile: compile, rowIssues: shown, selected: selected, editing: editing, frame: frame, editorRef: editorRef }) }) }) })] }));
2052
+ }
2053
+ /** The provider open at `editing`, as the editor and the code view show it. */
2054
+ function shownProvider(workspace, editing, inProject, connection) {
2055
+ const held = workspace.providers.find((each) => each.id === providerIdOf(editing));
2056
+ // The studio's own local provider is reached where the local connection says, as a run reaches it.
2057
+ return held && onStudioLocal(held.id, inProject)
2058
+ ? { ...held, connection: { baseURL: localProviderAddress(connection.local.baseUrl) } }
2059
+ : held;
2060
+ }
2061
+ /** Under the editor's header: the Keys panel, the open node's editor, or its code. */
2062
+ function EditorColumnBody({ state, connection, view, compile, rowIssues, selected, editing, frame, editorRef, }) {
2063
+ const project = useProject();
2064
+ const provider = shownProvider(state.workspace, editing, project !== null, connection);
2065
+ if (view.keysOpen)
2066
+ return _jsx(KeysBody, { connection: connection, state: state });
2067
+ if (view.editorView === 'code') {
2068
+ return (_jsx(Suspense, { fallback: null, children: provider ? (_jsx(ProviderCode, { provider: provider, issues: rowIssues.filter((issue) => issue.nodeId === editing), state: state, isEditable: project !== null })) : (_jsx(CodeBody, { source: compile.source, blocked: compile.blocked, issues: compile.editorIssues, state: state })) }));
2069
+ }
2070
+ return (_jsx(ProbedAgent.Provider, { value: compile.probed, children: _jsx(EditorBody, { state: state, connection: connection, selected: selected, editing: editing, issues: rowIssues, listBadges: frame.listBadges, editorRef: editorRef, open: view.open }) }));
2071
+ }
2072
+ /** The studio, opened on `opened`, with what the page showing it provides. */
2073
+ export function StudioScreen({ opened, host }) {
2074
+ useEffect(() => {
2075
+ warmWhenIdle(testerPanel, mapPanel, codePane, exportKit);
2076
+ }, []);
2077
+ return (_jsx(StudioHostContext.Provider, { value: host, children: _jsx(Studio, { opened: opened }) }));
2078
+ }