@signalcore/ui 0.1.0 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (611) hide show
  1. package/README.md +896 -513
  2. package/dist/AgentAbout.d.ts +186 -0
  3. package/dist/AgentAbout.js +1020 -0
  4. package/dist/AgentAbout.js.map +1 -0
  5. package/dist/AgentCard.d.ts +49 -0
  6. package/dist/AgentCard.js +77 -0
  7. package/dist/AgentCard.js.map +1 -0
  8. package/dist/AgentChat.d.ts +233 -0
  9. package/dist/AgentChat.js +1119 -0
  10. package/dist/AgentChat.js.map +1 -0
  11. package/dist/AgentSpace.d.ts +112 -0
  12. package/dist/AgentSpace.js +339 -0
  13. package/dist/AgentSpace.js.map +1 -0
  14. package/dist/AgentTasks.d.ts +48 -0
  15. package/dist/AgentTasks.js +120 -0
  16. package/dist/AgentTasks.js.map +1 -0
  17. package/dist/BrowserView.d.ts +52 -0
  18. package/dist/BrowserView.js +328 -0
  19. package/dist/BrowserView.js.map +1 -0
  20. package/dist/FileView.d.ts +63 -0
  21. package/dist/FileView.js +449 -0
  22. package/dist/FileView.js.map +1 -0
  23. package/dist/Modes.d.ts +13 -0
  24. package/dist/Modes.js +75 -0
  25. package/dist/Modes.js.map +1 -0
  26. package/dist/VoiceCall.d.ts +91 -0
  27. package/dist/VoiceCall.js +302 -0
  28. package/dist/VoiceCall.js.map +1 -0
  29. package/dist/about.d.ts +212 -0
  30. package/dist/about.js +136 -0
  31. package/dist/about.js.map +1 -0
  32. package/dist/agentIcons.d.ts +33 -0
  33. package/dist/agentIcons.js +36 -0
  34. package/dist/agentIcons.js.map +1 -0
  35. package/dist/automation/Automation.d.ts +35 -0
  36. package/dist/automation/Automation.js +268 -0
  37. package/dist/automation/Automation.js.map +1 -0
  38. package/dist/automation/RunView.d.ts +25 -0
  39. package/dist/automation/RunView.js +187 -0
  40. package/dist/automation/RunView.js.map +1 -0
  41. package/dist/automation/cron.d.ts +41 -0
  42. package/dist/automation/cron.js +169 -0
  43. package/dist/automation/cron.js.map +1 -0
  44. package/dist/automation/marks.js +121 -0
  45. package/dist/automation/marks.js.map +1 -0
  46. package/dist/automation/messages.d.ts +113 -0
  47. package/dist/automation/messages.js +113 -0
  48. package/dist/automation/messages.js.map +1 -0
  49. package/dist/automation/record.d.ts +47 -0
  50. package/dist/automation/record.js +177 -0
  51. package/dist/automation/record.js.map +1 -0
  52. package/dist/automation/types.d.ts +143 -0
  53. package/dist/automation/when.d.ts +36 -0
  54. package/dist/automation/when.js +168 -0
  55. package/dist/automation/when.js.map +1 -0
  56. package/dist/blocks.d.ts +185 -0
  57. package/dist/browser.js +292 -0
  58. package/dist/browser.js.map +1 -0
  59. package/dist/call.d.ts +89 -0
  60. package/dist/call.js +218 -0
  61. package/dist/call.js.map +1 -0
  62. package/dist/channelParts.js +26 -0
  63. package/dist/channelParts.js.map +1 -0
  64. package/dist/classes.js +8 -3
  65. package/dist/classes.js.map +1 -0
  66. package/dist/client.d.ts +146 -54
  67. package/dist/client.js +204 -1
  68. package/dist/client.js.map +1 -0
  69. package/dist/conversationFiles.js +46 -0
  70. package/dist/conversationFiles.js.map +1 -0
  71. package/dist/credentials.d.ts +45 -0
  72. package/dist/credentials.js +98 -0
  73. package/dist/credentials.js.map +1 -0
  74. package/dist/dev.js +17 -0
  75. package/dist/dev.js.map +1 -0
  76. package/dist/es.d.ts +17 -0
  77. package/dist/es.js +382 -0
  78. package/dist/es.js.map +1 -0
  79. package/dist/files.d.ts +21 -0
  80. package/dist/files.js +214 -0
  81. package/dist/files.js.map +1 -0
  82. package/dist/frames.d.ts +115 -0
  83. package/dist/frames.js +219 -0
  84. package/dist/frames.js.map +1 -0
  85. package/dist/icons.js +29 -0
  86. package/dist/icons.js.map +1 -0
  87. package/dist/index.d.ts +63 -60
  88. package/dist/index.js +59 -45
  89. package/dist/kit/AudioPlayer.d.ts +20 -0
  90. package/dist/kit/AudioPlayer.js +116 -0
  91. package/dist/kit/AudioPlayer.js.map +1 -0
  92. package/dist/kit/Button.d.ts +29 -0
  93. package/dist/kit/Button.js +59 -0
  94. package/dist/kit/Button.js.map +1 -0
  95. package/dist/kit/Card.d.ts +16 -0
  96. package/dist/kit/Card.js +36 -0
  97. package/dist/kit/Card.js.map +1 -0
  98. package/dist/kit/Chart.js +152 -0
  99. package/dist/kit/Chart.js.map +1 -0
  100. package/dist/kit/Choices.d.ts +21 -0
  101. package/dist/kit/Choices.js +99 -0
  102. package/dist/kit/Choices.js.map +1 -0
  103. package/dist/kit/Combobox.d.ts +26 -0
  104. package/dist/kit/Combobox.js +190 -0
  105. package/dist/kit/Combobox.js.map +1 -0
  106. package/dist/kit/DateField.d.ts +17 -0
  107. package/dist/kit/DateField.js +133 -0
  108. package/dist/kit/DateField.js.map +1 -0
  109. package/dist/kit/Field.d.ts +40 -0
  110. package/dist/kit/Field.js +98 -0
  111. package/dist/kit/Field.js.map +1 -0
  112. package/dist/kit/FileField.d.ts +113 -0
  113. package/dist/kit/FileField.js +341 -0
  114. package/dist/kit/FileField.js.map +1 -0
  115. package/dist/kit/FileMark.d.ts +15 -0
  116. package/dist/kit/FileMark.js +39 -0
  117. package/dist/kit/FileMark.js.map +1 -0
  118. package/dist/kit/Inputs.d.ts +59 -0
  119. package/dist/kit/Inputs.js +205 -0
  120. package/dist/kit/Inputs.js.map +1 -0
  121. package/dist/kit/List.d.ts +47 -0
  122. package/dist/kit/List.js +85 -0
  123. package/dist/kit/List.js.map +1 -0
  124. package/dist/kit/Mark.d.ts +30 -0
  125. package/dist/kit/Mark.js +27 -0
  126. package/dist/kit/Mark.js.map +1 -0
  127. package/dist/kit/MarkStack.d.ts +15 -0
  128. package/dist/kit/MarkStack.js +26 -0
  129. package/dist/kit/MarkStack.js.map +1 -0
  130. package/dist/kit/Note.d.ts +11 -0
  131. package/dist/kit/Note.js +31 -0
  132. package/dist/kit/Note.js.map +1 -0
  133. package/dist/kit/Popover.js +90 -0
  134. package/dist/kit/Popover.js.map +1 -0
  135. package/dist/kit/RangeField.d.ts +14 -0
  136. package/dist/kit/RangeField.js +47 -0
  137. package/dist/kit/RangeField.js.map +1 -0
  138. package/dist/kit/RatingField.d.ts +14 -0
  139. package/dist/kit/RatingField.js +50 -0
  140. package/dist/kit/RatingField.js.map +1 -0
  141. package/dist/kit/Stat.d.ts +26 -0
  142. package/dist/kit/Stat.js +46 -0
  143. package/dist/kit/Stat.js.map +1 -0
  144. package/dist/kit/Switch.d.ts +14 -0
  145. package/dist/kit/Switch.js +21 -0
  146. package/dist/kit/Switch.js.map +1 -0
  147. package/dist/kit/Tag.d.ts +16 -0
  148. package/dist/kit/Tag.js +20 -0
  149. package/dist/kit/Tag.js.map +1 -0
  150. package/dist/kit/Tile.d.ts +34 -0
  151. package/dist/kit/Tile.js +52 -0
  152. package/dist/kit/Tile.js.map +1 -0
  153. package/dist/kit/TimeField.d.ts +31 -0
  154. package/dist/kit/TimeField.js +109 -0
  155. package/dist/kit/TimeField.js.map +1 -0
  156. package/dist/kit/context.d.ts +22 -0
  157. package/dist/kit/context.js +46 -0
  158. package/dist/kit/context.js.map +1 -0
  159. package/dist/kit/dates.d.ts +14 -0
  160. package/dist/kit/dates.js +129 -0
  161. package/dist/kit/dates.js.map +1 -0
  162. package/dist/kit/messages.d.ts +99 -0
  163. package/dist/kit/messages.js +66 -0
  164. package/dist/kit/messages.js.map +1 -0
  165. package/dist/kit/numbers.d.ts +12 -0
  166. package/dist/kit/numbers.js +40 -0
  167. package/dist/kit/numbers.js.map +1 -0
  168. package/dist/kit/options.d.ts +10 -0
  169. package/dist/kit/options.js +23 -0
  170. package/dist/kit/options.js.map +1 -0
  171. package/dist/kit/uploads.d.ts +42 -0
  172. package/dist/kit/uploads.js +165 -0
  173. package/dist/kit/uploads.js.map +1 -0
  174. package/dist/kit/waveform.js +76 -0
  175. package/dist/kit/waveform.js.map +1 -0
  176. package/dist/locale.d.ts +1 -0
  177. package/dist/locale.js +1 -0
  178. package/dist/markdown.js +84 -0
  179. package/dist/markdown.js.map +1 -0
  180. package/dist/marks.js +217 -0
  181. package/dist/marks.js.map +1 -0
  182. package/dist/reveal.js +48 -0
  183. package/dist/reveal.js.map +1 -0
  184. package/dist/stepGlyphs.js +62 -0
  185. package/dist/stepGlyphs.js.map +1 -0
  186. package/dist/surface/Form.js +264 -0
  187. package/dist/surface/Form.js.map +1 -0
  188. package/dist/surface/Markdown.js +42 -0
  189. package/dist/surface/Markdown.js.map +1 -0
  190. package/dist/surface/Surface.d.ts +31 -17
  191. package/dist/surface/Surface.js +480 -57
  192. package/dist/surface/Surface.js.map +1 -0
  193. package/dist/surface/scope.d.ts +32 -0
  194. package/dist/surface/scope.js +23 -0
  195. package/dist/surface/scope.js.map +1 -0
  196. package/dist/surface/values.d.ts +44 -0
  197. package/dist/surface/values.js +237 -0
  198. package/dist/surface/values.js.map +1 -0
  199. package/dist/tint.d.ts +10 -0
  200. package/dist/tint.js +19 -0
  201. package/dist/tint.js.map +1 -0
  202. package/dist/types.d.ts +534 -0
  203. package/dist/types.js +16 -0
  204. package/dist/types.js.map +1 -0
  205. package/dist/useAgentFace.d.ts +18 -0
  206. package/dist/useAgentFace.js +45 -0
  207. package/dist/useAgentFace.js.map +1 -0
  208. package/dist/useCall.d.ts +39 -0
  209. package/dist/useCall.js +273 -0
  210. package/dist/useCall.js.map +1 -0
  211. package/dist/useConversation.d.ts +77 -0
  212. package/dist/useConversation.js +523 -0
  213. package/dist/useConversation.js.map +1 -0
  214. package/dist/useDictation.d.ts +17 -0
  215. package/dist/useDictation.js +68 -0
  216. package/dist/useDictation.js.map +1 -0
  217. package/dist/useLive.d.ts +26 -0
  218. package/dist/useLive.js +88 -0
  219. package/dist/useLive.js.map +1 -0
  220. package/dist/usePolled.js +68 -0
  221. package/dist/usePolled.js.map +1 -0
  222. package/dist/voice/Orb.d.ts +10 -9
  223. package/dist/voice/Orb.js +43 -39
  224. package/dist/voice/Orb.js.map +1 -0
  225. package/dist/voice/StillOrb.js +33 -0
  226. package/dist/voice/StillOrb.js.map +1 -0
  227. package/dist/voice/WebGLOrb.js +98 -0
  228. package/dist/voice/WebGLOrb.js.map +1 -0
  229. package/dist/voice/fluidDots.js +161 -0
  230. package/dist/voice/fluidDots.js.map +1 -0
  231. package/dist/voice/motion.js +84 -0
  232. package/dist/voice/motion.js.map +1 -0
  233. package/dist/voice/orbs.d.ts +32 -0
  234. package/dist/voice/orbs.js +16 -0
  235. package/dist/voice/orbs.js.map +1 -0
  236. package/dist/voice/useReducedMotion.js +16 -16
  237. package/dist/voice/useReducedMotion.js.map +1 -0
  238. package/dist/voice/wave.js +116 -0
  239. package/dist/voice/wave.js.map +1 -0
  240. package/dist/voice/webgl.js +177 -0
  241. package/dist/voice/webgl.js.map +1 -0
  242. package/dist/widget.js +284 -8
  243. package/package.json +49 -39
  244. package/src/AgentAbout.tsx +1621 -0
  245. package/src/AgentCard.tsx +123 -0
  246. package/src/AgentChat.tsx +1795 -0
  247. package/src/AgentSpace.tsx +540 -0
  248. package/src/AgentTasks.tsx +163 -0
  249. package/src/BrowserView.tsx +442 -0
  250. package/src/FileView.tsx +620 -0
  251. package/src/Modes.tsx +112 -0
  252. package/src/VoiceCall.tsx +411 -0
  253. package/src/about.ts +355 -0
  254. package/src/agentIcons.ts +56 -0
  255. package/src/automation/Automation.tsx +308 -0
  256. package/src/automation/RunView.tsx +213 -0
  257. package/src/automation/cron.ts +196 -0
  258. package/src/automation/marks.tsx +144 -0
  259. package/src/automation/messages.ts +231 -0
  260. package/src/automation/record.ts +206 -0
  261. package/src/automation/types.ts +146 -0
  262. package/src/automation/when.ts +195 -0
  263. package/src/blocks.ts +249 -0
  264. package/src/browser.ts +334 -0
  265. package/src/call.ts +300 -0
  266. package/src/channelParts.ts +17 -0
  267. package/src/classes.ts +10 -0
  268. package/src/client.ts +397 -0
  269. package/src/conversationFiles.ts +54 -0
  270. package/src/credentials.ts +158 -0
  271. package/src/dev.ts +20 -0
  272. package/src/es.ts +423 -0
  273. package/src/files.ts +238 -0
  274. package/src/frames.ts +291 -0
  275. package/src/icons.tsx +49 -0
  276. package/src/index.ts +270 -0
  277. package/src/kit/AudioPlayer.tsx +129 -0
  278. package/src/kit/Button.tsx +97 -0
  279. package/src/kit/Card.tsx +47 -0
  280. package/src/kit/Chart.tsx +129 -0
  281. package/src/kit/Choices.tsx +131 -0
  282. package/src/kit/Combobox.tsx +247 -0
  283. package/src/kit/DateField.tsx +156 -0
  284. package/src/kit/Field.tsx +137 -0
  285. package/src/kit/FileField.tsx +492 -0
  286. package/src/kit/FileMark.tsx +56 -0
  287. package/src/kit/Inputs.tsx +250 -0
  288. package/src/kit/List.tsx +135 -0
  289. package/src/kit/Mark.tsx +64 -0
  290. package/src/kit/MarkStack.tsx +29 -0
  291. package/src/kit/Note.tsx +42 -0
  292. package/src/kit/Popover.tsx +121 -0
  293. package/src/kit/RangeField.tsx +53 -0
  294. package/src/kit/RatingField.tsx +62 -0
  295. package/src/kit/Stat.tsx +60 -0
  296. package/src/kit/Switch.tsx +28 -0
  297. package/src/kit/Tag.tsx +32 -0
  298. package/src/kit/Tile.tsx +74 -0
  299. package/src/kit/TimeField.tsx +157 -0
  300. package/src/kit/context.tsx +246 -0
  301. package/src/kit/dates.ts +150 -0
  302. package/src/kit/messages.ts +160 -0
  303. package/src/kit/numbers.ts +44 -0
  304. package/src/kit/options.ts +30 -0
  305. package/src/kit/uploads.ts +244 -0
  306. package/src/kit/waveform.ts +84 -0
  307. package/src/locale.ts +6 -0
  308. package/src/markdown.ts +69 -0
  309. package/src/marks.tsx +328 -0
  310. package/src/reveal.ts +52 -0
  311. package/src/stepGlyphs.ts +83 -0
  312. package/src/styles.css +6961 -0
  313. package/src/surface/Form.tsx +299 -0
  314. package/src/surface/Markdown.tsx +55 -0
  315. package/src/surface/Surface.tsx +605 -0
  316. package/src/surface/scope.ts +82 -0
  317. package/src/surface/values.ts +361 -0
  318. package/src/tint.ts +14 -0
  319. package/src/types.ts +596 -0
  320. package/src/useAgentFace.ts +47 -0
  321. package/src/useCall.ts +292 -0
  322. package/src/useConversation.ts +627 -0
  323. package/src/useDictation.ts +83 -0
  324. package/src/useLive.ts +115 -0
  325. package/src/usePolled.ts +60 -0
  326. package/src/voice/Orb.tsx +45 -0
  327. package/src/voice/StillOrb.tsx +29 -0
  328. package/src/voice/WebGLOrb.tsx +98 -0
  329. package/src/voice/fluidDots.tsx +171 -0
  330. package/src/voice/motion.ts +112 -0
  331. package/src/voice/orbs.ts +39 -0
  332. package/src/voice/useReducedMotion.ts +24 -0
  333. package/src/voice/wave.tsx +129 -0
  334. package/src/voice/webgl.ts +209 -0
  335. package/src/widget/css.d.ts +5 -0
  336. package/src/widget/element.tsx +535 -0
  337. package/src/widget/index.ts +10 -0
  338. package/src/widget/mount.ts +27 -0
  339. package/src/widget/widget.css +253 -0
  340. package/dist/SignalcoreProvider.d.ts +0 -113
  341. package/dist/SignalcoreProvider.js +0 -84
  342. package/dist/automations/AppMarks.d.ts +0 -11
  343. package/dist/automations/AppMarks.js +0 -18
  344. package/dist/automations/AutomationCard.d.ts +0 -27
  345. package/dist/automations/AutomationCard.js +0 -53
  346. package/dist/automations/AutomationsWindow.d.ts +0 -38
  347. package/dist/automations/AutomationsWindow.js +0 -57
  348. package/dist/automations/Card.d.ts +0 -46
  349. package/dist/automations/Card.js +0 -45
  350. package/dist/automations/ScheduleMarks.d.ts +0 -13
  351. package/dist/automations/ScheduleMarks.js +0 -24
  352. package/dist/automations/presentationIcons.d.ts +0 -7
  353. package/dist/automations/presentationIcons.js +0 -67
  354. package/dist/automations/schedule.d.ts +0 -25
  355. package/dist/automations/schedule.js +0 -59
  356. package/dist/automations/time.d.ts +0 -3
  357. package/dist/automations/time.js +0 -25
  358. package/dist/browser/BrowserView.d.ts +0 -43
  359. package/dist/browser/BrowserView.js +0 -94
  360. package/dist/browser/room.d.ts +0 -21
  361. package/dist/browser/room.js +0 -33
  362. package/dist/browser/useBrowserSession.d.ts +0 -17
  363. package/dist/browser/useBrowserSession.js +0 -67
  364. package/dist/builder/BuilderChat.d.ts +0 -32
  365. package/dist/builder/BuilderChat.js +0 -41
  366. package/dist/builder/BuilderTranscript.d.ts +0 -49
  367. package/dist/builder/BuilderTranscript.js +0 -62
  368. package/dist/builder/StatusLine.d.ts +0 -8
  369. package/dist/builder/StatusLine.js +0 -17
  370. package/dist/builder/answer.d.ts +0 -7
  371. package/dist/builder/answer.js +0 -8
  372. package/dist/builder/blocks.d.ts +0 -31
  373. package/dist/builder/blocks.js +0 -74
  374. package/dist/builder/items.d.ts +0 -15
  375. package/dist/builder/items.js +0 -21
  376. package/dist/builder/steps.d.ts +0 -20
  377. package/dist/builder/steps.js +0 -120
  378. package/dist/builder/time.d.ts +0 -4
  379. package/dist/builder/time.js +0 -24
  380. package/dist/builder/useBuildSession.d.ts +0 -36
  381. package/dist/builder/useBuildSession.js +0 -130
  382. package/dist/builder/workingOn.d.ts +0 -7
  383. package/dist/builder/workingOn.js +0 -15
  384. package/dist/chat/AgentChat.d.ts +0 -37
  385. package/dist/chat/AgentChat.js +0 -21
  386. package/dist/chat/ChatView.d.ts +0 -25
  387. package/dist/chat/ChatView.js +0 -34
  388. package/dist/chat/Composer.d.ts +0 -20
  389. package/dist/chat/Composer.js +0 -22
  390. package/dist/chat/MessageRow.d.ts +0 -22
  391. package/dist/chat/MessageRow.js +0 -32
  392. package/dist/chat/arrival.d.ts +0 -15
  393. package/dist/chat/arrival.js +0 -25
  394. package/dist/chat/history.d.ts +0 -24
  395. package/dist/chat/history.js +0 -45
  396. package/dist/chat/moments.d.ts +0 -25
  397. package/dist/chat/moments.js +0 -72
  398. package/dist/chat/types.d.ts +0 -12
  399. package/dist/chat/types.js +0 -1
  400. package/dist/chat/useComposer.d.ts +0 -46
  401. package/dist/chat/useComposer.js +0 -62
  402. package/dist/chat/useConversation.d.ts +0 -35
  403. package/dist/chat/useConversation.js +0 -71
  404. package/dist/chat/useStickToBottom.d.ts +0 -9
  405. package/dist/chat/useStickToBottom.js +0 -33
  406. package/dist/chat/useTurns.d.ts +0 -49
  407. package/dist/chat/useTurns.js +0 -111
  408. package/dist/classes.d.ts +0 -2
  409. package/dist/development.d.ts +0 -5
  410. package/dist/development.js +0 -8
  411. package/dist/flow/FlowWindow.d.ts +0 -42
  412. package/dist/flow/FlowWindow.js +0 -126
  413. package/dist/flow/index.d.ts +0 -16
  414. package/dist/flow/index.js +0 -16
  415. package/dist/flow/kinds.d.ts +0 -23
  416. package/dist/flow/kinds.js +0 -48
  417. package/dist/flow/map.d.ts +0 -44
  418. package/dist/flow/map.js +0 -225
  419. package/dist/flow/nodePaths.d.ts +0 -7
  420. package/dist/flow/nodePaths.js +0 -17
  421. package/dist/flow/relations.d.ts +0 -42
  422. package/dist/flow/relations.js +0 -174
  423. package/dist/host.d.ts +0 -124
  424. package/dist/host.js +0 -1
  425. package/dist/live/LiveView.d.ts +0 -28
  426. package/dist/live/LiveView.js +0 -55
  427. package/dist/live/capture.d.ts +0 -20
  428. package/dist/live/capture.js +0 -100
  429. package/dist/live/follow.d.ts +0 -26
  430. package/dist/live/follow.js +0 -72
  431. package/dist/live/paint.d.ts +0 -13
  432. package/dist/live/paint.js +0 -77
  433. package/dist/live/useLiveStream.d.ts +0 -16
  434. package/dist/live/useLiveStream.js +0 -57
  435. package/dist/look.d.ts +0 -33
  436. package/dist/look.js +0 -241
  437. package/dist/messages/en.d.ts +0 -3
  438. package/dist/messages/en.js +0 -251
  439. package/dist/messages/es.d.ts +0 -3
  440. package/dist/messages/es.js +0 -251
  441. package/dist/messages/index.d.ts +0 -16
  442. package/dist/messages/index.js +0 -33
  443. package/dist/messages/types.d.ts +0 -246
  444. package/dist/messages/types.js +0 -1
  445. package/dist/parts/Button.d.ts +0 -18
  446. package/dist/parts/Button.js +0 -17
  447. package/dist/parts/Icon.d.ts +0 -39
  448. package/dist/parts/Icon.js +0 -37
  449. package/dist/parts/Input.d.ts +0 -6
  450. package/dist/parts/Input.js +0 -11
  451. package/dist/parts/Spinner.d.ts +0 -2
  452. package/dist/parts/Spinner.js +0 -7
  453. package/dist/patterns.d.ts +0 -15
  454. package/dist/patterns.js +0 -5
  455. package/dist/presets.d.ts +0 -82
  456. package/dist/presets.js +0 -81
  457. package/dist/primitives.d.ts +0 -22
  458. package/dist/primitives.js +0 -58
  459. package/dist/request/RequestCard.d.ts +0 -34
  460. package/dist/request/RequestCard.js +0 -101
  461. package/dist/request/approval/ApprovalPreview.d.ts +0 -5
  462. package/dist/request/approval/ApprovalPreview.js +0 -27
  463. package/dist/request/approval/shapes.d.ts +0 -32
  464. package/dist/request/approval/shapes.js +0 -68
  465. package/dist/request/apps.d.ts +0 -11
  466. package/dist/request/apps.js +0 -27
  467. package/dist/request/cards.d.ts +0 -12
  468. package/dist/request/cards.js +0 -24
  469. package/dist/request/deliver.d.ts +0 -12
  470. package/dist/request/deliver.js +0 -21
  471. package/dist/request/handOver.d.ts +0 -18
  472. package/dist/request/handOver.js +0 -33
  473. package/dist/request/kinds/Approval.d.ts +0 -3
  474. package/dist/request/kinds/Approval.js +0 -9
  475. package/dist/request/kinds/Choice.d.ts +0 -3
  476. package/dist/request/kinds/Choice.js +0 -8
  477. package/dist/request/kinds/Connect.d.ts +0 -9
  478. package/dist/request/kinds/Connect.js +0 -119
  479. package/dist/request/kinds/FreeText.d.ts +0 -3
  480. package/dist/request/kinds/FreeText.js +0 -20
  481. package/dist/request/kinds/HandOver.d.ts +0 -13
  482. package/dist/request/kinds/HandOver.js +0 -20
  483. package/dist/request/kinds/Secret.d.ts +0 -7
  484. package/dist/request/kinds/Secret.js +0 -49
  485. package/dist/request/kinds/Upload.d.ts +0 -6
  486. package/dist/request/kinds/Upload.js +0 -47
  487. package/dist/request/types.d.ts +0 -83
  488. package/dist/request/types.js +0 -1
  489. package/dist/safeUrl.d.ts +0 -6
  490. package/dist/safeUrl.js +0 -8
  491. package/dist/stream/builder.d.ts +0 -176
  492. package/dist/stream/builder.js +0 -202
  493. package/dist/stream/reduce.d.ts +0 -51
  494. package/dist/stream/reduce.js +0 -58
  495. package/dist/stream/untilAborted.d.ts +0 -7
  496. package/dist/stream/untilAborted.js +0 -32
  497. package/dist/stream/useRunStream.d.ts +0 -26
  498. package/dist/stream/useRunStream.js +0 -137
  499. package/dist/styles.css +0 -2318
  500. package/dist/surface/HostNode.d.ts +0 -14
  501. package/dist/surface/HostNode.js +0 -30
  502. package/dist/surface/Node.d.ts +0 -7
  503. package/dist/surface/Node.js +0 -49
  504. package/dist/surface/SurfaceBoundary.d.ts +0 -26
  505. package/dist/surface/SurfaceBoundary.js +0 -20
  506. package/dist/surface/bindings.d.ts +0 -14
  507. package/dist/surface/bindings.js +0 -37
  508. package/dist/surface/check.d.ts +0 -27
  509. package/dist/surface/check.js +0 -102
  510. package/dist/surface/format.d.ts +0 -10
  511. package/dist/surface/format.js +0 -32
  512. package/dist/surface/hosts.d.ts +0 -6
  513. package/dist/surface/hosts.js +0 -11
  514. package/dist/surface/scene.d.ts +0 -21
  515. package/dist/surface/scene.js +0 -8
  516. package/dist/surface/templates/Browser.d.ts +0 -6
  517. package/dist/surface/templates/Browser.js +0 -11
  518. package/dist/surface/templates/Chart.d.ts +0 -6
  519. package/dist/surface/templates/Chart.js +0 -13
  520. package/dist/surface/templates/Choices.d.ts +0 -3
  521. package/dist/surface/templates/Choices.js +0 -12
  522. package/dist/surface/templates/Form.d.ts +0 -3
  523. package/dist/surface/templates/Form.js +0 -44
  524. package/dist/surface/templates/KeyValue.d.ts +0 -3
  525. package/dist/surface/templates/KeyValue.js +0 -7
  526. package/dist/surface/templates/Layout.d.ts +0 -5
  527. package/dist/surface/templates/Layout.js +0 -16
  528. package/dist/surface/templates/Live.d.ts +0 -6
  529. package/dist/surface/templates/Live.js +0 -9
  530. package/dist/surface/templates/Media.d.ts +0 -5
  531. package/dist/surface/templates/Media.js +0 -13
  532. package/dist/surface/templates/Slots.d.ts +0 -3
  533. package/dist/surface/templates/Slots.js +0 -19
  534. package/dist/surface/templates/Table.d.ts +0 -3
  535. package/dist/surface/templates/Table.js +0 -12
  536. package/dist/surface/templates/Text.d.ts +0 -4
  537. package/dist/surface/templates/Text.js +0 -15
  538. package/dist/surface/templates/index.d.ts +0 -7
  539. package/dist/surface/templates/index.js +0 -32
  540. package/dist/surface/types.d.ts +0 -68
  541. package/dist/surface/types.js +0 -6
  542. package/dist/theme.d.ts +0 -62
  543. package/dist/theme.js +0 -9
  544. package/dist/useTokens.d.ts +0 -7
  545. package/dist/useTokens.js +0 -41
  546. package/dist/voice/VoiceWindow.d.ts +0 -18
  547. package/dist/voice/VoiceWindow.js +0 -35
  548. package/dist/voice/index.d.ts +0 -13
  549. package/dist/voice/index.js +0 -12
  550. package/dist/voice/meter.d.ts +0 -16
  551. package/dist/voice/meter.js +0 -52
  552. package/dist/voice/orbColors.d.ts +0 -16
  553. package/dist/voice/orbColors.js +0 -75
  554. package/dist/voice/pushable.d.ts +0 -16
  555. package/dist/voice/pushable.js +0 -56
  556. package/dist/voice/useDictation.d.ts +0 -24
  557. package/dist/voice/useDictation.js +0 -60
  558. package/dist/voice/useReducedMotion.d.ts +0 -5
  559. package/dist/voice/useVoice.d.ts +0 -70
  560. package/dist/voice/useVoice.js +0 -197
  561. package/dist/voice/useVoicePrimitives.d.ts +0 -7
  562. package/dist/voice/useVoicePrimitives.js +0 -11
  563. package/dist/voice/voice.d.ts +0 -55
  564. package/dist/voice/voice.js +0 -9
  565. package/dist/voice/webSpeech.d.ts +0 -33
  566. package/dist/voice/webSpeech.js +0 -258
  567. package/dist/widget.js.map +0 -1
  568. package/dist/windows/AgentSpace.d.ts +0 -78
  569. package/dist/windows/AgentSpace.js +0 -135
  570. package/dist/windows/Captions.d.ts +0 -13
  571. package/dist/windows/Captions.js +0 -22
  572. package/dist/windows/Dock.d.ts +0 -37
  573. package/dist/windows/Dock.js +0 -22
  574. package/dist/windows/Journal.d.ts +0 -15
  575. package/dist/windows/Journal.js +0 -19
  576. package/dist/windows/KindMark.d.ts +0 -7
  577. package/dist/windows/KindMark.js +0 -31
  578. package/dist/windows/Pending.d.ts +0 -8
  579. package/dist/windows/Pending.js +0 -17
  580. package/dist/windows/Stage.d.ts +0 -18
  581. package/dist/windows/Stage.js +0 -21
  582. package/dist/windows/StatusLabel.d.ts +0 -9
  583. package/dist/windows/StatusLabel.js +0 -7
  584. package/dist/windows/Strip.d.ts +0 -17
  585. package/dist/windows/Strip.js +0 -42
  586. package/dist/windows/SurfaceWindow.d.ts +0 -20
  587. package/dist/windows/SurfaceWindow.js +0 -12
  588. package/dist/windows/WindowFrame.d.ts +0 -23
  589. package/dist/windows/WindowFrame.js +0 -22
  590. package/dist/windows/hearing.d.ts +0 -11
  591. package/dist/windows/hearing.js +0 -26
  592. package/dist/windows/kinds.d.ts +0 -47
  593. package/dist/windows/kinds.js +0 -28
  594. package/dist/windows/reduce.d.ts +0 -98
  595. package/dist/windows/reduce.js +0 -151
  596. package/dist/windows/registry.d.ts +0 -18
  597. package/dist/windows/registry.js +0 -61
  598. package/dist/windows/reveal.d.ts +0 -9
  599. package/dist/windows/reveal.js +0 -6
  600. package/dist/windows/space.d.ts +0 -21
  601. package/dist/windows/space.js +0 -6
  602. package/dist/windows/status.d.ts +0 -11
  603. package/dist/windows/status.js +0 -30
  604. package/dist/windows/types.d.ts +0 -15
  605. package/dist/windows/types.js +0 -1
  606. package/dist/windows/useLoaded.d.ts +0 -44
  607. package/dist/windows/useLoaded.js +0 -58
  608. package/dist/windows/useUnread.d.ts +0 -7
  609. package/dist/windows/useUnread.js +0 -21
  610. package/dist/windows/useWindows.d.ts +0 -70
  611. package/dist/windows/useWindows.js +0 -60
package/README.md CHANGED
@@ -1,567 +1,950 @@
1
1
  # @signalcore/ui
2
2
 
3
- SignalCore's interface as React components: what an agent shows (`Surface`), what it asks the
4
- owner (`RequestCard`), and the two chats built from them (`AgentChat` for the people an agent
5
- serves, `BuilderChat` for the owner who shapes it), drawn exactly the way the product draws them.
6
- The web app uses this package, so what you embed is what we ship. For a page that is not React
7
- there is a `<signalcore-chat>` [widget](#the-widget).
8
-
9
- ```sh
10
- npm install @signalcore/ui @signalcore/sdk
3
+ SignalCore's agents inside your product, for the people they serve: your customers talk to the
4
+ agent (`AgentChat`), call it (`VoiceCall`, with the orb), or meet it whole (`AgentSpace`: the
5
+ chat, and the call one switch away), in your React pages or, without React, as
6
+ `<signalcore-chat>`. Every piece looks like your product: your type, your colours, your CSS
7
+ ([Theming](#theming)). Every
8
+ piece is built only on the public API (the HTTP routes and the conversation's socket,
9
+ [v3/README.md](../README.md)) and its own hooks; nothing in the platform knows it exists. Our own
10
+ app imports it like any host would; if a component ever needs a private import, the package is
11
+ incomplete. Building and configuring an agent stays in our app and in your backend.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @signalcore/ui react react-dom # pnpm add, yarn add alike
11
17
  ```
12
18
 
13
- Needs React 19. The styles are one CSS file. Besides the SDK, the only dependencies are the
14
- graph library of the agent's map (`@xyflow/react`, which only `@signalcore/ui/flow` imports, see
15
- [Windows](#windows-of-an-agent)) and the orb of the voice window (`orb-ui`, MIT, which only
16
- `@signalcore/ui/voice` loads).
19
+ ESM with its types, one module per file (your bundler keeps what you use; a call's LiveKit loads
20
+ with the first call), and one stylesheet. React 19.2 or later. In Next.js's App Router, draw the
21
+ components from a client component (`"use client"`).
17
22
 
18
- ```tsx
19
- import { Signalcore } from "@signalcore/sdk";
20
- import { RequestCard, SignalcoreProvider, Surface, useRunStream } from "@signalcore/ui";
21
- import "@signalcore/ui/styles.css";
22
-
23
- const client = new Signalcore({ apiKey });
24
-
25
- function Run({ runId }: { runId: string }) {
26
- const run = useRunStream(runId);
27
- return (
28
- <>
29
- {run.surfaces.map((surface) => (
30
- <Surface key={surface.id} surface={surface} onAction={(action) => send(action)} />
31
- ))}
32
- {run.requests.map((card) => (
33
- <RequestCard key={card.request_id} card={card} />
34
- ))}
35
- </>
36
- );
37
- }
38
-
39
- <SignalcoreProvider client={client}>
40
- <Run runId="run_..." />
41
- </SignalcoreProvider>;
42
- ```
43
-
44
- ## The two chats
45
-
46
- One renderer, two chats, split by who is talking (ADR 0009). Both reach the API through the
47
- provider's `client`, and both are made of parts a host can compose.
48
-
49
- **`AgentChat`** is the chat of the people an agent **serves**: their messages and the agent's, the
50
- agent's surfaces drawn inline (with their text twin where they cannot be), and a box to write in.
51
- It speaks to the agent as the person and can never change it.
23
+ Browsers: Chrome and Edge 123, Firefox 120 and Safari 17.5 (macOS and iOS), and later; `widget.js`
24
+ is built for them. These read `light-dark()`, which every default colour is. If your bundler lowers
25
+ CSS for older ones (Vite's default `build.cssTarget` does), set it to these: lowered,
26
+ `light-dark()` becomes a polyfill that ignores a `color-scheme` set anywhere its build did not
27
+ see, and the colours come out invalid.
52
28
 
53
29
  ```tsx
54
- import { Signalcore } from "@signalcore/sdk";
55
- import { AgentChat, SignalcoreProvider } from "@signalcore/ui";
30
+ import { AgentSpace, createClient } from "@signalcore/ui";
56
31
  import "@signalcore/ui/styles.css";
57
32
 
58
- // In the browser: a token your backend minted for this person (never a secret key).
59
- const client = new Signalcore({ endUserToken });
60
-
61
- <SignalcoreProvider client={client}>
62
- <AgentChat agentId="agt_…" thread="order-7" placeholder="Ask about your order" />
63
- </SignalcoreProvider>;
64
- ```
65
-
66
- | Prop | What |
67
- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
68
- | `agentId` | The agent the person talks to. |
69
- | `thread` | The conversation to speak in; the agent remembers it. The API's default if none. |
70
- | `placeholder` | The box's hint. `autoFocus` focuses it. |
71
- | `onSurfaceAction` | Tells you what the person picked or submitted. The chat has already sent it. |
72
- | `loadHistory` | `(thread) => Promise<ChatMessage[]>`. Without it the chat reads the caller's own conversation through `endUsers.myConversation` when the client has it (an end user's token or a publishable key do), and otherwise starts empty. `messagesFromConversation` turns the API's messages into `ChatMessage`s. |
73
-
74
- - A message is sent with `agents.invoke({ input, thread })`; a pick or a form on a surface goes
75
- back as `agents.invoke({ action, thread })`, shown as what the person said ("Martes 10:00"). Only
76
- the last thing said takes a pick: once the person answers, a surface is a record.
77
- - **It never shows an approval, a connection or a secret.** When the answer has `waiting_for`, the
78
- run paused for the owner to confirm something: the person sees a quiet "waiting for a
79
- confirmation", the chat follows the run (`useRunStream`) until it is done, and then shows what
80
- arrived. The send button is not usable while a turn runs; the box stays free to write in.
81
- - Accessible: new words from the agent go to a live region, the box and the conversation are
82
- labelled, and everything works from the keyboard (Enter sends, Shift+Enter breaks the line).
83
-
84
- **`BuilderChat`** is the chat of the owner who **shapes** the agent: their messages, what the
85
- builder says and does as it works (steps open to what came back), the surfaces it shows, the cards
86
- it asks them to answer in place, and what changed.
33
+ const client = createClient({ baseUrl: "https://signalcore.ai/v1", getToken });
87
34
 
88
- ```tsx
89
- <BuilderChat agentId="agt_…" sessionId={sessionId} onSession={setSessionId} />
35
+ <AgentSpace client={client} agentId="agt_…" />;
90
36
  ```
91
37
 
92
- A turn streams as it happens and has a stop button (Esc too); answering a card tells the builder in
93
- the owner's words; reopening a conversation replays it with the cards still waiting. It needs a
94
- client with the builder's calls (`build.sessions`, `requests`), which `Signalcore` with an owner's
95
- key has. A host that draws the conversation its own way builds it from what `BuilderChat` is made
96
- of:
97
-
98
- - `useBuildSession({ agentId, sessionId, onSession, model, onEvent, onTurnEnd })` gives the
99
- transcript, `loading`, `send` and `stop`;
100
- - `reduceTranscript` is the pure fold over the builder's events behind it (live and replayed
101
- history give the same transcript);
102
- - `BuilderTranscript` draws the blocks, with `renderers` for the ones that are only content
103
- (Markdown for the builder's words, a card that activates a change): the request cards and the
104
- surfaces are always the platform's;
105
- - `StatusLine`, `Composer` (with slots for `tools` and `context`, and a held `value`), and
106
- `describeStep` complete it.
107
-
108
- ## Voice
109
-
110
- `VoiceWindow` is a conversation by voice with an agent (ADR 0011): an orb that listens, thinks and
111
- speaks, the state in words under it, and captions of what was heard and said. The person chooses
112
- it by pressing the button; the agent never starts it. It is its own entry, so a page that never
113
- speaks does not load the orb:
114
-
115
- ```tsx
116
- import { SignalcoreProvider } from "@signalcore/ui";
117
- import { VoiceWindow } from "@signalcore/ui/voice";
118
-
119
- <SignalcoreProvider client={client}>
120
- <VoiceWindow agentId="agt_…" thread="order-7" onSurfaces={(surfaces) => place(surfaces)} />
121
- </SignalcoreProvider>;
38
+ In this repo the web app depends on it as `workspace:*` and reads its source through the
39
+ exports' `@signalcore/source` condition (`v3/web`'s Vite and TypeScript set it), so nothing waits
40
+ for a build. `pnpm build` writes `dist/` (`tsdown` for the library, Vite for `widget.js`);
41
+ `pnpm widget` serves `widget.html` with the widget over the source, at
42
+ `http://localhost:5176/widget.html?base=…&agent=…&key=…`.
43
+
44
+ Every component, live, is documented at [signalcore.ai/ui](https://signalcore.ai/ui): Storybook
45
+ over the source (`stories/`), on a demo API in the page, with each component's props read from
46
+ its types and a toolbar for the language, the theme, `--sc-accent` and `--sc-radius`.
47
+ `pnpm storybook` serves it at `http://localhost:6006`; `pnpm storybook:build` writes
48
+ `storybook-static/`, which the API serves at `/ui` (`v3/api/app/webapp.py`).
49
+
50
+ ## Who the page speaks for
51
+
52
+ `createClient` takes exactly one way to sign in (v3/README.md, Credentials):
53
+
54
+ | Mode | Options | Requests send | The socket adds |
55
+ | ----------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
56
+ | Our own app | `{ baseUrl: "/api", credentials: "cookie" }` | the session cookie, on the page's origin | nothing |
57
+ | A person, token given | `{ baseUrl, token }` | `Authorization: Bearer <token>` | `?token=` |
58
+ | A person, token fetched | `{ baseUrl, getToken: () => Promise<string> }` | the same, asked of `getToken` when first needed and kept; on a 401 it is asked once more and the request sent again | `?token=`, a fresh one after a socket that couldn't open |
59
+ | An anonymous visitor | `{ baseUrl, publishableKey, visitorId? }` | `Authorization: Bearer pk_…` and `SignalCore-Visitor: <id>` | `?token=pk_…&visitor=<id>` |
60
+
61
+ `baseUrl` is the public API with its prefix (`https://signalcore.ai/v1`). Only our own app
62
+ sends the browser's cookie; every other mode sends none (`credentials: "omit"`). A visitor without
63
+ a `visitorId` is given one (32 random hex characters) kept in `localStorage` under
64
+ `signalcore.visitor` (`VISITOR_KEY`), so they find their conversation on their next visit; where
65
+ the page can keep nothing, the id lasts the page. The publishable key (`pk_…`, made in our app:
66
+ one agent, the sites it may be used from) is public; the visitor id is all that makes a
67
+ conversation theirs.
68
+
69
+ A person's token is minted by your backend with a secret key that may `talk`, for one hour, naming
70
+ who the person is in your product and the agents they may reach (`SignalCore-Org` when your key
71
+ acts for an organization you manage). Your page asks your backend for it:
72
+
73
+ ```python
74
+ # FastAPI, httpx: GET /signalcore/token for the person signed in to your product.
75
+ import httpx
76
+
77
+ SIGNALCORE = "https://signalcore.ai/v1"
78
+
79
+ @app.get("/signalcore/token")
80
+ async def signalcore_token(user = Depends(current_user)):
81
+ async with httpx.AsyncClient() as http:
82
+ answer = await http.post(
83
+ f"{SIGNALCORE}/tokens",
84
+ headers={"Authorization": f"Bearer {SECRET_KEY}"},
85
+ json={"agents": ["agt_…"], "external_id": user.id, "name": user.name, "email": user.email},
86
+ )
87
+ answer.raise_for_status()
88
+ return answer.json() # {"token": "…", "expires_at": "…"}
122
89
  ```
123
90
 
124
- - Its turns are the chat's (`agents.invoke`, in the thread) with `modality: "voice"`, so the agent
125
- keeps its answer short and puts the detail in a surface, which goes to `onSurfaces`: the window
126
- draws none. Like the chat, it never shows an approval, a connection or a secret.
127
- - `useVoice({ agentId, thread, voice, lang, continuous, onTurn, onSurfaces })` is the loop behind
128
- it (listen, send, think, speak, listen again) for a host that lays it out itself: `state`,
129
- `level`, `transcript`, `captions`, `start`, `stop`, `mute`.
130
- - A `Voice` is how to listen and speak: `listen()` (an async iterable of what is said, interim
131
- then final), `speak(text)`, their stops, and an observable `state` and `level`. `webSpeechVoice`
132
- is the browser's own (`SpeechRecognition` and `speechSynthesis`) and the default; a provider's
133
- transport comes behind the same interface. The level is the microphone's while listening and an
134
- estimate from the words while speaking, since `speechSynthesis` has no audio to measure.
135
- - `Orb` is a replaceable primitive (`components={{ Orb }}`, props `state`, `level`, `size`,
136
- `label`, `className`). The default is the `circle` theme of `orb-ui` in your tokens; under
137
- `prefers-reduced-motion` it is a still disc in a ring whose outline says the state.
138
- - `useDictation({ lang, onFinal })` (from the core entry) dictates into a message box with the
139
- same browser voice.
140
-
141
- ## The widget
142
-
143
- `<signalcore-chat>` is `AgentChat` as a web component in a shadow root, for pages that are not
144
- React. One script, with React and the SDK bundled in (`dist/widget.js`, about 108 kB gzipped):
145
-
146
- ```html
147
- <script src="https://signalcore.ai/app/widget.js"></script>
148
-
149
- <!-- An anonymous visitor: a publishable key, bound to the origins it lists. -->
150
- <signalcore-chat agent="agt_…" publishable-key="pk_live_…"></signalcore-chat>
151
-
152
- <!-- A person of your product: a token your backend minted for them. -->
153
- <signalcore-chat agent="agt_…" end-user-token="…" thread="order-7"></signalcore-chat>
91
+ ```ts
92
+ // Any TypeScript server with fetch: the same, for the person signed in.
93
+ export async function signalcoreToken(user: { id: string; name: string; email: string }) {
94
+ const answer = await fetch("https://signalcore.ai/v1/tokens", {
95
+ method: "POST",
96
+ headers: {
97
+ Authorization: `Bearer ${process.env.SIGNALCORE_SECRET_KEY}`,
98
+ "Content-Type": "application/json",
99
+ },
100
+ body: JSON.stringify({
101
+ agents: ["agt_…"],
102
+ external_id: user.id,
103
+ name: user.name,
104
+ email: user.email,
105
+ }),
106
+ });
107
+ if (!answer.ok) throw new Error(`SignalCore refused the token: ${answer.status}`);
108
+ return (await answer.json()) as { token: string; expires_at: string };
109
+ }
154
110
  ```
155
111
 
156
- | Attribute | What |
157
- | -------------------------------- | -------------------------------------------------------------------------------------------------------- |
158
- | `agent`, `thread`, `placeholder` | What `AgentChat` takes. `autofocus` focuses the box. |
159
- | `end-user-token` | The SDK's `endUserToken`. |
160
- | `publishable-key`, `visitor-id` | The SDK's `publishableKey` and `visitorId`. Without a `visitor-id` the widget keeps one in this browser. |
161
- | `base-url` | Where the API is (`https://signalcore.ai` by default). |
162
- | `locale` | `es` (the default) or `en`: the language of its words and dates. See [Words](#words). |
163
-
164
- The `client` property, set from JavaScript, wins over the attributes: give it any `UiClient` (an SDK
165
- client with other options, your own proxy). A secret key is not an option here, on purpose. The
166
- `messages` property, also from JavaScript, changes single words (see [Words](#words)).
167
-
168
- It draws in a shadow root with the package's styles inside, so the page and the widget do not
169
- touch. The page themes it from outside:
170
-
171
- - **Tokens.** `--sc-*` custom properties on the element or above it inherit in
172
- (`signalcore-chat { --sc-accent: #b4532a; height: 600px }`), and so do the page's font and text
173
- color. The element is `display: block; height: 32rem` until your CSS says otherwise.
174
- - **Dark mode.** The widget follows a `data-theme` of `light` or `dark` on itself or the nearest
175
- ancestor (usually `<html>`), and `prefers-color-scheme` without one.
176
- - **Parts.** Every slot is a part: `signalcore-chat::part(root)`, `::part(composer)`,
177
- `::part(message)` (and `::part(message--person)`, `::part(message--agent)`), and the rest of
178
- `SLOTS`.
179
- - **Events.** `surface-action` (bubbles, composed) carries what the person picked in `detail`.
180
-
181
- **`<signalcore-space>`** is [`AgentSpace`](#the-shell-agentspace) in the same script, taking the
182
- same credentials: `<signalcore-space agent="agt_…" end-user-token="…" windows="flow" mode="text">`.
183
- `windows` lists the app's windows to open (the first takes the stage), `mode` is `text` or `voice`
184
- (the element writes the person's choice back and dispatches `mode-change`), and `viewer` is
185
- `end_user` (the default: a widget serves the people of your product) or `owner`, for a page that
186
- sets an owner's `client`. The script carries the conversation, the browser, the live stream and the
187
- pages the agent places, and neither the flow map nor the voice: those windows show what the agent
188
- said they show, and the voice stays on text. It is `display: block; height: 36rem` by default.
189
-
190
- If it cannot start (no agent, credentials that make no sense) it shows a quiet line and says why in
191
- the console. `pnpm build` produces `dist/widget.js` after the library; `import "@signalcore/ui/widget"`
192
- registers the element from a bundler instead. From JavaScript,
193
- `defineSignalcoreChat({ tag, createClient })` registers it under another tag, or with your own way
194
- of making the client.
195
-
196
- ## What it draws
197
-
198
- **`Surface`** draws a surface of the `signalcore-basic.v1` catalog: cards, key-values, tables,
199
- charts, callouts, images, links, choice lists, slot pickers, forms, a live browser view and live
200
- streams. A
201
- surface is data, never code: it draws only catalog components (and the host's own, see
202
- [Components](#your-own-components)), resolves their bindings, and shows only `https://` addresses.
203
-
204
- - What the user does (a pick, a form, a button of your own component) calls
205
- `onAction({ surface_id, name, data, label })`, the shape the API's `Action` expects. Without
206
- `onAction` the surface is a record: nothing on it can be pressed.
207
- - **Every surface carries a text twin** (`fallback_text`). A surface whose payload is damaged, that
208
- names a component nobody here knows (or one of yours that this page has not registered), or whose
209
- drawing throws shows its text twin instead, and the rest of your page carries on.
210
- - The browser view frames a live page in a sandbox, only from an `https://` address.
211
-
212
- **`RequestCard`** draws what an agent needs from the owner: a question, a choice, an app to
213
- connect, a file, an approval, a secret, or the controls of its browser. These are the owner's
214
- decisions about safety, so the platform draws them and nobody else can: an agent cannot fake an
215
- "Aprobar" button because the catalog has no such component, and **a host cannot replace the card
216
- either**. It follows levels 1 to 4 of [Styling](#styling) (tokens, presets, `classNames`,
217
- `unstyled`) and takes `apps` to say how your apps look; the provider's `components` never reach it.
218
-
219
- Answering goes through the provider's `client` (`requests.resolve`; a secret goes to `vault.put`, a
220
- file to `documents.addFile`, an app is checked with `connections.refresh`). Without a client,
221
- `onResolve` is how the answer gets anywhere. Secrets and files never pass through your callbacks.
222
-
223
- **`useRunStream(runId)`** follows a run through `client.runs.stream` and gives you the events, the
224
- surfaces and the requests (as cards) it accumulates, and `status` (`connecting`, `streaming`,
225
- `paused`, `done`, `failed`). When the run pauses on a request it closes the stream and opens it
226
- again after the last event once the request is answered, wherever that happened. A host that never
227
- draws the cards passes `{ cards: false }`, and the API is not asked what they say.
228
-
229
- ## Windows of an agent
230
-
231
- The chats show an agent talking; a window shows the agent itself, as its owner reads it. The web
232
- app's agent page and map are these two, drawn by the platform from facts and never generated. They
233
- are **owner windows**: they read the business's overview and the agent's package, so they need an
234
- owner's credential (`agents:read`), and an end user's token gets a `403`.
235
-
236
112
  ```tsx
237
- import { AutomationsWindow, SignalcoreProvider } from "@signalcore/ui";
238
- import { FlowWindow } from "@signalcore/ui/flow";
239
-
240
- <SignalcoreProvider client={client}>
241
- <AutomationsWindow agentId="agt_..." onOpenFlow={showSteps} onAsk={tellTheBuilder} />
242
- <div style={{ height: 480 }}>
243
- <FlowWindow agentId="agt_..." onOpen={showPart} />
244
- </div>
245
- </SignalcoreProvider>;
113
+ // The page: a token whenever the client needs one.
114
+ const client = createClient({
115
+ baseUrl: "https://signalcore.ai/v1",
116
+ getToken: async () => (await (await fetch("/signalcore/token")).json()).token,
117
+ });
246
118
  ```
247
119
 
248
- - **`AutomationsWindow`** is what the agent does: a card for each channel it answers on and each
249
- automation, with what a model wrote about the part over what the platform knows (when it runs,
250
- its apps, its state, how the last run went). `focusedId`, `attention` and `building` follow the
251
- builder to the automation it is working on. `AutomationCard`, `ScheduleMarks` and `AppMarks` are
252
- its parts, exported for a page you lay out yourself.
253
- - **`FlowWindow`** is the agent as a map of its channels, automations, skills, actions, records and
254
- apps, with a line for each call, read from its package. A part is picked with a press (`onSelect`,
255
- `selectedId`) and opened with a second one or Enter (`onOpen`). It is in its own entry because
256
- `@xyflow/react` and its stylesheet are not small: the core entry imports nothing from it, and a
257
- test keeps it that way. `agentMap`, `relationsOf` and `kindOf` are exported from it as plain
258
- functions.
259
- - **They fetch for themselves** when given `agentId` and the client has `agents.overview` (and
260
- `agents.get` for the map's files); they say they are loading, or that they could not load. What
261
- is passed as props (`overview`, `files`) is what is drawn, and nothing is asked. `apps` says how
262
- your apps look, as in `RequestCard`.
263
- - They follow levels 1 to 4 of [Styling](#styling); their slots are `automations`, `automation`,
264
- `schedule`, `app-marks`, `flow`, `flow__node` and `status`.
265
-
266
- ## The shell: `AgentSpace`
267
-
268
- Everything a person sees of an agent is a window ([ADR 0011](../../docs/adr/0011-windows.md)), and
269
- `AgentSpace` is where they sit: a **stage** with the one that matters now, a **strip** of the open
270
- ones and a **dock** to talk from. The conversation is always one of them. The agent adds more as it
271
- works, placing a page of its own (`show` with a `window`: a generated window, a surface) or opening
272
- one of the app's (`open_window`: `automations`, `flow`, `voice`), and the person closes and reopens
273
- them.
274
-
275
- ```tsx
276
- import { AgentSpace, AutomationsWindow, SignalcoreProvider } from "@signalcore/ui";
277
- import { FlowWindow } from "@signalcore/ui/flow";
278
- import { VoiceWindow } from "@signalcore/ui/voice";
279
-
280
- // Hoisted: a new object each render would draw the shell again for nothing.
281
- const windows = { automations: AutomationsWindow, flow: FlowWindow, voice: VoiceWindow };
282
-
283
- <SignalcoreProvider client={client} viewer="owner" windows={windows}>
284
- <AgentSpace
285
- agentId="agt_…"
286
- thread="order-7"
287
- windows={["automations"]}
288
- renderVoice={(voice) => <VoiceWindow {...voice} />}
289
- />
290
- </SignalcoreProvider>;
291
- ```
120
+ ## AgentChat
292
121
 
293
- - **The registry** is the provider's `windows`: the component that draws each kind of default
294
- window, laid over the package's own (`chat`, `browser`, `live`). `automations`, `flow` and
295
- `voice` are yours to register, each from its own import, so a page that never shows the map does
296
- not load its graph library. Every component takes the same props (`DefaultWindowProps`: the
297
- agent, the thread, a label, the agent's words for the window, what the host opened it with), so
298
- the windows of this package plug in as they are. A kind with none shows what the agent said the
299
- window shows (the text twin), never an error.
300
- - **`viewer`** says who is looking, because the client does not: `"owner"` (the default) or
301
- `"end_user"`. An end user's stage never opens the owner's windows (`END_USER_WINDOW_KINDS`:
302
- `chat` and `voice`), whoever asks, the host or the agent.
303
- - **A pick on a generated window** goes back to the agent as the next turn, the way a pick in the
304
- chat does, and the transcript says where a page went ("→ Opened in Sales") instead of drawing it
305
- twice. A window the person closed stays closed until the agent puts something new in it. The
306
- stage is rebuilt from the conversation's history when it opens, with no resource of its own.
307
- - **Voice** is the dock's `renderVoice`: when the person speaks, the dock is the voice and the
308
- conversation collapses to captions. Without `renderVoice` there is no button for it.
309
- - **`useWindows`** is the state of the stage without the shell, for a host that lays windows out
310
- itself: derived from facts (`place`, `restore`, `observe` a run's journal) and from what the
311
- person does (`focus`, `close`, `reopen`). `WindowFrame`, `SurfaceWindow`, `Strip` and `Dock` are
312
- the pieces the shell is made of; `renderStrip` and `renderDock` replace two of them.
313
- - Tab goes strip, stage, dock; Esc closes the window on the stage (never the conversation); under
314
- 720px of its own width the strip becomes tabs. Its slots are `space`, `strip`, `window`, `dock`
315
- and `captions`, and `<signalcore-space>` is it as a web component (see [The widget](#the-widget)).
316
-
317
- ## Live streams
318
-
319
- What a run shows as it happens (the browser it drives, a preview its code renders) is a live
320
- stream, and **`LiveView`** watches one: the pictures it sends, drawn on a canvas as they arrive.
122
+ The conversation and its composer, for a conversation your page already has (one it started with
123
+ `client.conversations.start(agentId)`, or one it kept):
321
124
 
322
125
  ```tsx
323
- <SignalcoreProvider client={client}>
324
- <LiveView streamId="liv_..." aspect="16:9" input={false} onStatus={(status) => {}} />
325
- </SignalcoreProvider>
126
+ <AgentChat client={client} conversationId="cnv_…" name="Lucía" onTool={onTool} />
326
127
  ```
327
128
 
328
- - **Where it comes from.** The `live` template (a `Live` component in a card) draws a `LiveView`,
329
- and the `Browser` component and the hand-over card draw one when they name a `stream_id`, in
330
- preference to the hosted browser's live URL and to screenshots. `client` needs `live.connect`,
331
- which the SDK's `Signalcore` has (`client.live`); a host that reaches the API with a cookie
332
- supplies it with `new Live({ baseUrl })` from `@signalcore/sdk`.
333
- - **The picture.** The canvas takes the stream's size from its first frame (`aspect` holds its shape
334
- until then), shows the newest picture and never a queue, and has a role of `img` with a label
335
- (`label`, "Vista en directo" by default, "Live view" in English). A quiet word says how it is
336
- ("en directo", "reconectando", "terminado", "no disponible"; "live", "reconnecting", "ended",
337
- "unavailable" in English). It does not animate for people who prefer reduced motion.
338
- - **Dropped connections** are opened again, after half a second and then twice as long up to ten
339
- seconds. A stream that ended, or that the server refuses (not signed in, not allowed, no longer
340
- there), is not. A stream that never answered is given up on after three tries: `onStatus` then
341
- says `unavailable`, and the browser view goes back to its live URL or its screenshots.
342
- - **Input.** With `input`, once the stream says it takes input, the pointer (clicks, double clicks,
343
- drags), the wheel and the keys go to the stream in its own pixels, and the canvas can be reached
344
- with the keyboard: keys are sent only while it has the focus, Tab leaves it, and keys held when it
345
- loses the focus are let go. Whether it is the person's turn is for the stream's producer to say; a
346
- browser session takes input only while the agent has handed the controls over, which is when the
347
- hand-over card turns `input` on.
348
-
349
- ## Styling
350
-
351
- Five levels, from nothing to everything. Each one is optional, and each works without the next.
352
-
353
- **1. Inherit.** Dropped into any page it uses **your font, your text color and your color scheme**
354
- (`font-family: inherit`, `color: inherit`, tints of `currentColor` for lines and muted text). Dark
355
- mode follows `prefers-color-scheme`, and an ancestor with `data-theme="dark"` (or `"light"`)
356
- decides when you set it. This is the whole of the work for many pages.
357
-
358
- **2. Tokens.** Everything else is a `--sc-*` custom property with a default. Set what you want
359
- different, in CSS or through the provider, which writes them as inline custom properties on its
360
- own wrapper element:
361
-
362
- | Token | What |
363
- | ----------------------------------------------- | ----------------------------------------------------------- |
364
- | `--sc-accent`, `--sc-accent-fg` | The owner acting: primary buttons, selection, links |
365
- | `--sc-bg`, `--sc-surface`, `--sc-sunken` | What it sits on, a card's face, a recessed area |
366
- | `--sc-line`, `--sc-line-strong` | Borders |
367
- | `--sc-text`, `--sc-muted`, `--sc-subtle` | Ink (default: your text color) |
368
- | `--sc-danger`, `--sc-ok`, `--sc-signal` | State, each with a `-soft` tint |
369
- | `--sc-radius`, `--sc-density` | The corner of a card; how much room things take (1 is ours) |
370
- | `--sc-font`, `--sc-font-mono`, `--sc-font-size` | Default: your font; the reading size is 14px |
129
+ Its props, words and parts are below ([AgentChat](#agentchat-1)).
371
130
 
372
- ```tsx
373
- <SignalcoreProvider theme={{ accent: "var(--primary)", radius: "var(--radius)", density: 0.9 }} />
374
- ```
131
+ ## AgentSpace
375
132
 
376
- **3. Presets, found for you.** By default (`theme="auto"`) the provider asks your page's root for
377
- the variables of the design systems it knows and maps the tokens onto the first it finds:
378
- **shadcn** (`--primary`, `--background`, `--card`, `--border`, `--foreground`…), **Radix Themes**
379
- (`--accent-9`, `--color-background`, `--gray-6`…) and **Material 3** (`--md-sys-color-primary`,
380
- `--md-sys-color-surface`…). So in a shadcn app the surfaces already use your primary, your card and
381
- your radius, in light and in dark, with no configuration. A page that uses none keeps level 1.
133
+ An agent in your page, whole, for your customers: the conversation and, when the agent has a
134
+ voice (its face, `GET /agents/{id}/face`, says `voice.enabled`), two modes beside the
135
+ composer's paperclip, **text** and **voice**, in one small segmented control.
382
136
 
383
- ```tsx
384
- <SignalcoreProvider /> {/* auto */}
385
- <SignalcoreProvider theme="shadcn" /> {/* skip the search: Radix needs this inside its <Theme> */}
386
- <SignalcoreProvider theme={{ accent: "var(--brand)" }} /> {/* over the detected system */}
387
- <SignalcoreProvider theme={{ preset: "material", radius: "4px" }} />
388
- <SignalcoreProvider theme="none" /> {/* nothing mapped */}
389
- ```
137
+ - **Text** is the chat (`AgentChat`), as below.
138
+ - **Voice** turns the same panel, at the same size, into the call (`VoiceCall`): the orb, what
139
+ the call is doing (connecting, listening, thinking, speaking), and what is being said, as
140
+ captions under the orb (the person's last words and the agent's answer as it comes, read live
141
+ from the call's own conversation). The modes stay at its foot. `POST /agents/{id}/calls` makes
142
+ the call and a conversation of its own, the person's.
143
+ - **Back to text**: ending the call, or choosing text (which hangs up), comes back to writing:
144
+ once the agent took the call, in the call's conversation, where what was said shows (told
145
+ through `onConversation`, like one the space starts); after a call nobody took, where the
146
+ person was. A call that couldn't begin (no microphone, refused, nobody answering) stays to say
147
+ why, with a button to try again and the modes to go back.
390
148
 
391
- The search reads, and never writes: the tokens go on the provider's wrapper, not on your `:root`.
392
- It runs in the browser only (a server renders as if none were used, and the client then finds
393
- out), and again when `data-theme` or `class` on `<html>` changes. The tables of variables are in
394
- `src/presets.ts`; adding a system is adding a table.
149
+ The modes are a radio group (`role="radiogroup"`, each mode a `radio` named by its glyph and its
150
+ word): Tab reaches the mode that is on, the arrows (and Home, End) move to the other and choose
151
+ it, and after a switch the keyboard stays on the modes, drawn anew where the person now is.
395
152
 
396
- **4. Class hooks.** Every element is a named slot with a stable BEM class and attributes:
397
- `sc-card`, `sc-card__title`, `sc-button sc-button--primary`, `data-sc="button"`,
398
- `data-sc-mod="primary sm"`. `classNames` adds yours by slot (the list is `SLOTS`), or by slot and
399
- modifier:
153
+ There is no header by default: the space is the conversation, and your page says who the agent
154
+ is. `header` draws the agent's face at the top (its mark in the tint it keeps, its name and
155
+ role), or draw your own from `useAgentFace(client, agentId)`.
400
156
 
401
157
  ```tsx
402
- <SignalcoreProvider
403
- classNames={{ card: "rounded-xl border bg-card", "button--primary": "bg-primary" }}
158
+ <AgentSpace
159
+ client={client}
160
+ agentId="agt_…"
161
+ conversationId={kept} // optional: the person's conversation to go on with
162
+ onConversation={(id) => keep(id)} // hears the one it started
163
+ layout="compact" // or "full", a page's main column
164
+ header // optional: the agent's face at the top
165
+ steps="hidden" // optional: by default as the client speaks (AgentChat's `steps`)
166
+ messages={SPACE_MESSAGES_ES} // Spanish; English by default
167
+ locale={es} // from "@signalcore/ui/locale"
168
+ onTool={onTool}
169
+ onClose={close} // a close button, for a space held in a panel
404
170
  />
405
171
  ```
406
172
 
407
- **`unstyled`** leaves out the shipped styles (the elements and classes stay), for hosts that style
408
- everything through `classNames`, as a Tailwind-only app does. The wrapper is then a plain `div`;
409
- give it `classNames={{ ui: "contents" }}`. The shipped styles sit in a CSS layer (`signalcore`)
410
- and under the wrapper, so your own unlayered CSS wins without a fight over specificity. A host that
411
- has layers of its own (Tailwind v4's) says where ours go, before anything fills them, so its
412
- utilities win over our styles on what it draws around our parts:
413
- `@layer theme, base, signalcore, components, utilities;` first in its stylesheet, then
414
- `@import "tailwindcss";`. Layers declared later win, so without that line ours would.
415
-
416
- **5. Your own components.** `components` takes the primitives surfaces are built from, the
417
- catalog's components and components of your own, by name. See
418
- [Your own components](#your-own-components).
419
-
420
- **Patterns.** Where our components have two layouts, you choose; the defaults are the product's.
421
- They are classes like any other (`sc-table--compact`), and apply to our components only: a
422
- primitive you replace draws itself.
173
+ Without `conversationId`, or once the one given is gone for the person (404, 403), it starts one
174
+ and tells `onConversation`; a call the agent took is told the same way, as the conversation it
175
+ goes on in. `onClose` draws a close in the face, or without it at the top's end, over the
176
+ conversation. It takes `AgentChat`'s `classNames`, `components`, `suggestions`, `onError`,
177
+ `steps` and `orb` (the call's), and goes one agent per mount (key it on the agent's id).
178
+ `messages` (`SpaceMessages`, `SPACE_MESSAGES` in English, `SPACE_MESSAGES_ES`): `modes`
179
+ (`{label, text, voice}`: the group's name and each mode's word), `again` (try a call that
180
+ couldn't begin again), `close`, `agent` (its name when the face can't be read), `opening`,
181
+ `failed`, `retry`, and `chat` and `voice`, `AgentChat`'s and `VoiceCall`'s own. Parts:
182
+ `sc-space` (`--compact`, `--full`, `--voice`), `sc-space__face`, `sc-space__mark` (`--sky`,
183
+ `--rose`, `--sand`), `sc-space__who`, `sc-space__name`, `sc-space__role`, `sc-space__close`,
184
+ `sc-space__body`, `sc-space__voice`, `sc-space__foot`, `sc-space__modes`, `sc-space__mode`
185
+ (`--text`, `--voice`; `aria-checked` on the one on), `sc-space__caption` (`--person`, `--agent`),
186
+ `sc-space__status`.
423
187
 
424
- ```tsx
425
- <SignalcoreProvider patterns={{ table: "compact", actions: "footer", card: "raised" }} />
426
- ```
427
-
428
- | Pattern | Values | What |
429
- | --------- | ---------------------------------- | ----------------------------------------------------------- |
430
- | `table` | `comfortable` (default), `compact` | Rows with room, or close together for a lot of records |
431
- | `actions` | `inline` (default), `footer` | A form's button under its fields, or set apart on a line |
432
- | `card` | `flat` (default), `raised` | A bordered area, or one that also lifts a hair off the page |
188
+ ## The widget
433
189
 
434
- ## Words
190
+ `<signalcore-chat>` for pages without React: one script, React and the stylesheet inside it,
191
+ served by SignalCore beside its API (it follows each release; `@signalcore/ui/widget.js` from
192
+ npm pins a version instead).
435
193
 
436
- Every word the components say themselves (buttons, statuses, card headings, the builder's step
437
- verbs, the times above a conversation) comes from a dictionary. Two ship with the package, Spanish
438
- (the default, as the product speaks) and English, and the provider picks one and lets you change
439
- single words:
194
+ ```html
195
+ <script src="https://signalcore.ai/v1/widget.js"></script>
440
196
 
441
- ```tsx
442
- <SignalcoreProvider locale="en" messages={{ send: "Post", verbs: { edit: "Change" } }} />
197
+ <signalcore-chat
198
+ base-url="https://signalcore.ai/v1"
199
+ agent="agt_…"
200
+ publishable-key="pk_…"
201
+ ></signalcore-chat>
443
202
  ```
444
203
 
445
- - **`locale`** is `"es"` (default) or `"en"`. It decides the words and how dates and times are
446
- written ("Hoy 10:32" and "12 oct" in Spanish, "Today 10:32 AM" and "Oct 12" in English).
447
- - **`messages`** is any part of the dictionary, laid over the locale's: a word you leave out stays
448
- as it was. `Messages` is its type, and the key names are in `src/messages/types.ts`. Words that
449
- carry a name or a number are functions (`connectWith: (app) => "Connect " + app`), and the
450
- groups (`verbs`, what the builder is doing; `liveStatus`; `voiceStates`; and the windows'
451
- `automations` and `flow`) can change one entry at a time, at any depth
452
- (`{ flow: { verbs: { calls: "runs" } } }`). Define the object
453
- outside the render, so the components are not drawn again for nothing.
454
- - **Another language.** The package has the two. For a third, give every word as `messages` (the
455
- type makes sure none is missing) over the `locale` whose dates are closest.
456
- - **The widget** takes the language as an attribute and the words as a property:
204
+ A web channel (SignalCore, the agent's page, Channels, Web) gives the shortest form: one line
205
+ that puts the widget on the page itself, with the agent and the look the business set in its
206
+ panel (or had the builder set), read by the key as it loads, so a change reaches every site
207
+ without pasting again. `data-locale` and `data-theme` say what `locale` and `theme` would.
457
208
 
458
209
  ```html
459
- <signalcore-chat agent="agt_…" publishable-key="pk_live_…" locale="en"></signalcore-chat>
460
- <script>
461
- document.querySelector("signalcore-chat").messages = { send: "Post" };
462
- </script>
210
+ <script src="https://signalcore.ai/v1/widget.js" data-key="pk_…" async></script>
463
211
  ```
464
212
 
465
- - **Not components.** `describeStep` and `workingOn`, which a host that draws the builder's
466
- conversation itself calls, are Spanish unless they are handed the words:
467
- `describeStep(name, input, useMessages())`.
468
-
469
- ## Your own components
470
-
471
- `components` on the provider takes your own components, by name. Three kinds:
213
+ An element with a publishable key and no `agent` reads its web channel the same way; `contained`
214
+ floats it within its own box instead of at the page's corner, and the `look` property puts a
215
+ look over the channel's (SignalCore's preview).
216
+
217
+ A round button at the page's corner opens the agent's space in a panel (the whole screen on a
218
+ phone, where the space's own close is the way out); `mode="inline"` puts the space in the page's
219
+ flow at the element's size (560px tall unless your CSS says). A visitor's conversation is kept in
220
+ `localStorage` (`signalcore.conversation.<agent>`) for their next visit. In a bundled app,
221
+ `import "@signalcore/ui/widget.js"` registers it the same way.
222
+
223
+ | Attribute | Says |
224
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
225
+ | `base-url` | the public API, with `/v1` |
226
+ | `agent` | the agent's id |
227
+ | `publishable-key` | a visitor of your site (`pk_…`) |
228
+ | `token` | instead, one of your people, by the token your backend minted (set it again when it is renewed) |
229
+ | `locale` | `es` or `en`; else the page's `<html lang>`, else the browser's |
230
+ | `theme` | `light` or `dark`; else the page's `color-scheme`, and where the page names none, dark when its text is light (a page made dark by its own classes) |
231
+ | `mode` | `inline`; floating by default |
232
+ | `header` | draws the agent's face over the space (off by default) |
233
+ | `steps` | `shown` or `hidden`; hidden by default (your page speaks for a person) |
234
+ | `stylesheet` | your own CSS for the widget: the CSS itself (anything with a `{`) or its address; adopted inside the shadow root, after ours. Also the element's `stylesheet` property |
235
+ | `unstyled` | our look's defaults drop away (grounds, lines, shadows, corners, every ink but the page's text), the layout stays: for a host that styles everything |
236
+ | `shadow` | `none`: no shadow root; the widget draws in the page (our stylesheet goes first in `<head>`, so your CSS wins), for a host whose global CSS should apply directly. Read once, as the element connects |
237
+
238
+ By default the widget takes your page's type and ink: the font, the base size and the text colour
239
+ the element inherits come through the shadow root and stand in for our defaults (read as it draws,
240
+ and again whenever `<html>` or `<body>` change or the system's scheme does). Its colours are the
241
+ [tokens](#theming), with neutral defaults: the accent is your text's colour unless you give one.
242
+ Everything else is yours to style: the `--sc-*` tokens on the element or any ancestor, the
243
+ [parts](#parts) by `::part(…)`, your own `stylesheet`, `unstyled`, or `shadow="none"`.
472
244
 
473
- - **Primitives** (`Button`, `Card`, `Table`, `Input`, `Select`, with typed props): every surface is
474
- built from yours, so the catalog is drawn with your design system. They add nothing to the
475
- catalog; a surface cannot name them. The cards that ask the owner to decide never use them.
476
- `Orb`, the voice window's, is one too (see [Voice](#voice)).
477
- - **Catalog components** (`SlotPicker`): a surface that uses the name is drawn by your component,
478
- given the properties the agent filled (bindings resolved), its `children` drawn, and `onAction`
479
- to report what the user does. They are not checked: read them loosely.
480
- - **Components of your own** (`PropertyCard`): things of your product that an agent shows in your
481
- design, with props the API checks. See below.
482
-
483
- ```tsx
484
- <SignalcoreProvider components={{ Button: MyButton, SlotPicker: MySlots, PropertyCard }} />
245
+ ```html
246
+ <style>
247
+ signalcore-chat {
248
+ --sc-accent: #0f766e;
249
+ --sc-accent-fg: #fff;
250
+ --sc-radius: 6px;
251
+ --sc-orb-color: #0f766e;
252
+ }
253
+ signalcore-chat::part(message-person) {
254
+ background: #0f766e;
255
+ color: #fff;
256
+ }
257
+ </style>
258
+ <signalcore-chat base-url="…" agent="agt_…" publishable-key="pk_…" stylesheet="/widget.css">
259
+ </signalcore-chat>
485
260
  ```
486
261
 
487
- ### A component of your own
488
-
489
- An agent can show a listing, a quote or a ticket as your product draws it, if SignalCore knows the
490
- component: its name, a schema for its props and a text twin. It is registered twice, in the page
491
- and with the API.
492
-
493
- **In the page**, a React component that takes the props and, when the user can answer it, reports
494
- with `onAction`:
262
+ ## Your own tools, run in the page
263
+
264
+ A tool of yours declared `client` on the agent (`PUT /v1/agents/{id}/tools/{name}`) runs in the
265
+ person's page: the socket sends `{"type": "tool", "id", "name", "input"}`, the chat calls
266
+ `onTool`, and sends up `{"type": "tool_result", "id", "output"}` with what it resolved to
267
+ (`null` for nothing) or `{"type": "tool_result", "id", "error"}` with the message it rejected
268
+ with (an error is cut to the 2000 characters the API takes). Its step shows as working
269
+ meanwhile, like any step (`messages.steps[name]` words it), and the reply as under way until the
270
+ step is done or failed. Each call runs once; one told again while it waits (the socket opened
271
+ anew: the API tells a person's page again every call still awaited) is answered again with what
272
+ it gave, and the API's `tool` error for an answer no call waits on any more is let pass, never
273
+ shown (noted in the console in development). With no `onTool`, each is refused with
274
+ `messages.noTool` ("This page can't do that"; "Esta página no puede hacer eso" in
275
+ `CHAT_MESSAGES_ES`), so a turn never waits on a page that can't run it; only the person's page
276
+ answers it (the API refuses the business's socket, so our app reading the conversation fails
277
+ nothing).
495
278
 
496
279
  ```tsx
497
- import { AgentChat, SignalcoreProvider, type HostAction } from "@signalcore/ui";
498
-
499
- interface PropertyCardProps {
500
- address: string;
501
- price: number;
502
- onAction?: (action: HostAction) => void;
503
- }
504
-
505
- function PropertyCard({ address, price, onAction }: PropertyCardProps) {
506
- const rate = (fit: boolean) =>
507
- onAction?.({
508
- name: "rate",
509
- data: { address, fit },
510
- label: `${fit ? "Good fit" : "Not a fit"}: ${address}`,
511
- });
512
- return (
513
- <article>
514
- <h3>{address}</h3>
515
- <p>{price} €</p>
516
- <button disabled={!onAction} onClick={() => rate(true)}>
517
- Good fit
518
- </button>
519
- <button disabled={!onAction} onClick={() => rate(false)}>
520
- Not a fit
521
- </button>
522
- </article>
523
- );
524
- }
525
-
526
- <SignalcoreProvider components={{ PropertyCard }}>
527
- <AgentChat agentId="agt_…" />
528
- </SignalcoreProvider>;
280
+ const onTool = async ({ name, input }: ToolCall) => {
281
+ if (name === "open_record") {
282
+ router.push(`/records/${String(input.record)}`);
283
+ return { opened: true };
284
+ }
285
+ throw new Error(`This page has no ${name}`);
286
+ };
529
287
  ```
530
288
 
531
- **With the API**, once for your organization: `client.ui.components.put(...)` in the SDKs takes the
532
- name, a JSON Schema for the props and the text twin (see the SDK's reference for its fields). The
533
- agent then uses the component in a surface with a node of its own kind:
289
+ ## Make it yours
290
+
291
+ From least to most work:
292
+
293
+ 1. **Tokens**: the `--sc-*` custom properties (colours, type, corners, spacing, shadows, the
294
+ orb's colour) on `:root` or any ancestor, the same for the React components and the widget,
295
+ each with a neutral default that follows light and dark ([Theming](#theming)).
296
+ 2. **Words**: `messages` on every component (English by default; `CHAT_MESSAGES_ES`,
297
+ `VOICE_CALL_MESSAGES_ES`, `SPACE_MESSAGES_ES`, `KIT_MESSAGES_ES` in Spanish) and `locale` for the
298
+ calendars, days and numbers.
299
+ 3. **Parts**: in React, `classNames` adds your classes to every named part, by the part's own
300
+ class (and your stylesheet reaches those classes as it is); in the widget, `::part(…)` from
301
+ your page ([Parts](#parts)), or your own `stylesheet` inside it.
302
+ 4. **Bare**: in React, leave our stylesheet out; in the widget, `unstyled` keeps the layout and
303
+ drops our look, and `shadow="none"` lets your global CSS in.
304
+ 5. **Pieces**: `components={{ table: YourTable }}` draws a block of what the agent shows with
305
+ your component.
306
+ 6. **Headless**: the hooks under the components, to draw everything yourself:
307
+ `createClient`, `useConversation` (the conversation as the person sees it, `onTool` included),
308
+ `useLive` (the socket's frames, raw), `useCall` (a call without its drawing) and
309
+ `useAgentFace` (the agent's face). They are described below.
310
+
311
+ ## The client
312
+
313
+ `createClient(options)`: where the API is and who calls it ([Who the page speaks
314
+ for](#who-the-page-speaks-for)), and `fetch`, a host's own when the global one is not the right
315
+ one. Errors are an `ApiError { status, detail, code }`. `client.who` is who the page speaks for,
316
+ from its credentials: `"business"` (our app's cookie) or `"person"` (a token, a publishable key's
317
+ visitor); the chat draws a yes the business decides by it, and shows a person none of the
318
+ agent's steps (`steps`).
319
+
320
+ | Method | Does |
321
+ | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
322
+ | `agents.face(id)` | what a person may know of the agent (`GET /agents/{id}/face`): `AgentFace {id, name, role, icon, voice: {enabled}}` |
323
+ | `agents.about(id)` | what the agent says about itself (`about`, null until written; `about_at`, null once stale) and its `facts` |
324
+ | `agents.describe(id)` | has the agent write about itself anew (a model call, a few seconds); answers like `about` |
325
+ | `agents.files(id)` | the agent's own files (`StoredFile`: `id, path, media_type, size, searchable, created_at`), by path |
326
+ | `agents.putFile(id, path, blob, searchable)` | stores the blob's bytes under the path (replacing a file of that name), its type as the file's; `searchable` makes it knowledge `search_files` reads (text, Markdown, JSON, YAML, PDF). 50 MB at most, 5 GB per organization |
327
+ | `agents.deleteFile(id, path)` | removes one of the agent's own files |
328
+ | `agents.memories(id)` | what the agent remembered (`MemoryFact`: `id, text, created_at`), newest first |
329
+ | `agents.forget(id, memoryId)` | takes one memory from it |
330
+ | `agents.call(id)` | the owner's call with the agent from our app (`POST /agents/{id}/voice/token`, `/api` only): `CallToken {url, token, room, call_id, conversation_id}`, LiveKit's address and a token for the call's room, and the call's conversation (a test), made now |
331
+ | `files.list()` · `files.put(path, blob, searchable)` · `files.delete(path)` | the organization's files, shared with every agent: the same three as the agent's own |
332
+ | `conversations.get(id)` | one conversation with its messages, who answers it (`handled_by`: `agent`, `person` or `nobody`) and every change of it (`handovers`); on a channel, `channel`, `contact` (`muted`) and each message's `author`, `delivery` and `decision` |
333
+ | `conversations.start(agentId)` | a new conversation with an agent |
334
+ | `conversations.call(agentId)` | a call with the agent for whoever may talk to it, a person's as theirs (`POST /agents/{id}/calls`): a `CallToken`, what `VoiceCall`'s `connect` answers, with the call's own conversation, made now, where what is said lands |
335
+ | `channels.list(agentId?)` | the organization's channels, or one agent's (`Channel`: `id, agent_id, kind, provider, name, external_id, status, detail, settings, created_at`), the disconnected ones too |
336
+ | `liveUrl(conversationId, { fresh? })` | the socket's address, signed in (a promise): `ws:` beside `http:` and `wss:` beside `https:`, with `?token=` (and `&visitor=`) but in our app; `fresh` asks `getToken` for a new token first |
337
+
338
+ ## The frames
339
+
340
+ What the socket says, read into `Frame` by `frameOf` (a kind it does not know is let pass):
341
+
342
+ | Down | Means |
343
+ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
344
+ | `message {message}` | a message stored: `{id, role, text, action, steps}`; a reply's `steps` carry what it called, what each drew, and `request` while one waits for the person's leave |
345
+ | `text {delta}` | a piece of the reply as it streams |
346
+ | `ui {id, surface, awaits?}` | a surface the agent showed, under the step's id; `awaits`: the turn stops until the person acts on it |
347
+ | `step {id, action?, state, output?}` | an action running, then its state as it moves: `running`, `waiting`, `done`, `failed` |
348
+ | `request {id, action, input, decides}` | an action waits for a yes before it runs: the person's (`decides: "person"`, the turn stopped) or the business's (`"business"`, the turn went on; its people answer from the app) |
349
+ | `tool {id, name, input}` | the agent called one of the host's own tools, to run in the page (`onTool`); its step works meanwhile, and the turn waits for its `tool_result`. Told again on a socket that opens while it waits |
350
+ | `handoff {reason}` | the agent handed the conversation to a person (`escalate`), and why; it stays quiet in it until released |
351
+ | `holder {handled_by, handover}` | who answers the conversation now (`agent`, `person`, `nobody`), after any change of it, and the change: `{holder, reason, by, title, at}` |
352
+ | `decision {message_id, decision}` | what was decided of a person's message, when it is stored or settles (`{kind, reason, automation, title, run_id}`), or `null` once cleared |
353
+ | `done` | the turn is over: the reply is complete, or it stopped to wait |
354
+ | `stopped` | the person stopped the reply |
355
+ | `error {code, detail}` | `code` for the host to word (`pending`: answer the surface first; `business`: that yes is the business's), `detail` for a log |
356
+ | `browser {open, url, title, control, reason, wait, width, height}` | the conversation's browser, whole, whenever it changes (`open: false` once it closed); `control` is who holds it, `reason` and `wait` the agent's hand-over |
357
+ | `browser.frame {jpeg, width, height}` | its screen as it paints (base64 JPEG); never stored, kept out of the state (`watchScreen`) |
358
+
359
+ Up: `say {text}`, `action {surface, name, data, label}` (what the person did on a surface, `label`
360
+ the words shown for it), `stop`, `browser.control {to}` (take the browser, or give it back),
361
+ `browser.input {event}` (the person's pointer, wheel, keys and pasted text, in the page's pixels,
362
+ while they hold it) and `tool_result {id, output}` or `tool_result {id, error}` (what a host's
363
+ tool gave back, `null` for nothing, or why it failed: `toolResultOf(id, settled)` makes either
364
+ from how the call's promise settled).
365
+
366
+ ## The hooks
367
+
368
+ - `useLive(client, conversationId, { onFrame, onOpen? })` → `{ open, send(frame) }`. The socket
369
+ itself: frames down to `onFrame`, `send` up, reconnecting on its own (1s, 2s, 4s, 8s, then
370
+ 10s); `onOpen(again)` says when it is not the first time. A socket that couldn't open may have
371
+ carried a token that expired: the next try asks for a fresh one (`liveUrl`'s `fresh`), once
372
+ until one opens. The raw layer a host draws its own things over.
373
+ - `useAgentFace(client, agentId)` → `{ face, loading, error }`: what a person may know of the
374
+ agent (`AgentFace`: its name, role, `icon` for `agentGlyph`, `voice.enabled`), read once per
375
+ agent; none while another agent's is read.
376
+ - `useConversation(client, conversationId, { onError?, onHolder?, handover?, onTool? })` → the
377
+ conversation as the person sees it. `onTool(call)` runs the host's tools the agent calls in
378
+ the page and its answer goes up (each call once; one told again is answered again with what it
379
+ gave; the turn stays under way until its step settles); without it each is refused with
380
+ `NO_TOOL`. It answers `{ messages, draft, pending,
381
+ awaiting, open, loading, error, say(text), act(action), stop(), browser,
382
+ watchScreen(listener), control(to), input(event), holder, handovers }`. The history, read each
383
+ time the socket opens and kept live by its frames;
384
+ `pending` from the person's message to `done`, `stopped` or a turn error, `draft` the reply so
385
+ far. What `step`, `ui` and `request` frames say lands on the reply's steps, so a message is
386
+ the one place a step is drawn from, live or read back. `awaiting` is the step the turn waits
387
+ on: a surface to answer, or an action waiting for the person's leave (the step's `request`,
388
+ `decides: "person"`; `act` on its id with `approve` or `decline`); `say` is refused until they
389
+ act. A yes the business gives (`decides: "business"`) waits on its step and holds nothing; the
390
+ person's answer on one, refused `business`, puts it back as the business's. `onError(code)`
391
+ hears the socket's codes and `load` (the
392
+ history could not be read): the host words them, the hook shows no text; `tool` (an answer of
393
+ the page's no call waits on any more) is let pass. `holder` is who
394
+ answers it (null until the history lands) and `handovers` every change of it, read with the
395
+ history and kept live by `holder` frames (each kept once; one heard while a read was on its way
396
+ outlives it); `onHolder(holder, handover)` hears each such frame, for the host's own views of
397
+ it. `handover` is the last change the host knows of (one it made itself, as its request
398
+ answered): taken at once, kept once with the frame that tells it too (by its moment and holder),
399
+ so it shows with the socket down. A `decision` frame lands on its message (`null` clears it). One conversation per
400
+ mount: key the component on the id. On a channel (v3/CHANNELS.md) the reply runs on its own
401
+ time: what the person writes there (`author: contact`) and what a person of the business
402
+ writes (`operator`) are kept without touching the turn, which opens on the reply's first words
403
+ or step and closes on `done`; and while something of the business's is on its way (`queued`,
404
+ or `sent` less than a minute ago) the history is read again every 3 s, since where it got to
405
+ comes in no frame.
406
+
407
+ ## AgentChat
408
+
409
+ `<AgentChat client conversationId name messages? classNames? components? onError? actions? suggestions? compose? look? steps? write? handovers? onHolder? handover? onTool? />`:
410
+ the conversation, the reply streaming, and the composer (Enter sends, Shift+Enter breaks the
411
+ line; while a reply is on its way the send button is a stop button, Esc stops too, and one quiet
412
+ line over the composer says who is thinking, for how many seconds, and how to stop). The reply
413
+ streams at a reading pace (`reveal.ts`: a few characters a frame, faster the further behind, never
414
+ more than about a second behind the model), and goes on from there once it is stored. A reply
415
+ draws its words as Markdown, then the steps it took as a compact list on a thin line (the
416
+ action's glyph once done, a turning ring while it runs, a ring while it waits, a warning when it
417
+ failed; the action's name; its output), then what each step drew, through `Surface`; a surface takes input while the
418
+ turn waits on it, or while nothing runs and the agent showed it since the person last spoke. A
419
+ step waiting for a yes whoever the page speaks for gives (its `request`, live or read back)
420
+ draws a card headed by `approval`, with the sentence, the action's input and two buttons; on a
421
+ person's page (`client.who`), one the business decides (`decides: "business"`) is a quiet line
422
+ instead, `confirming` ("Waiting for the business to confirm"), never buttons. On a call, a person's message
423
+ that says what they heard of the agent's last words (`heard`, when it wasn't what was stored)
424
+ draws it: with no reply before it, the greeting the agent picked up with (never stored) as the
425
+ agent's words, where it was said; after a reply they cut short, a quiet line under it quoting the
426
+ last of what they heard (`cut(heard)`, `""` when nothing of it was). Where the agent handed the conversation to a person (an
427
+ `escalate` step done, live or read back), one quiet line across the log says so, after the last
428
+ reply of that turn. With `handovers` (a host of the business's) the log draws every change of
429
+ who answers it instead, read and live, each a quiet line where it fell (the agent's own after the
430
+ last reply of its turn), with why and when (`handover(holder, by, name)`, `by` a `HandoverBy`:
431
+ `owner`, `agent`, `platform` or an `automation` by its title; the reason follows it); and under a
432
+ person's message the agent let pass this time (its `decision` a `skip`), a quiet line says so,
433
+ and why (`skipped`). `onHolder(holder, handover)` hears each change live, for the host's own
434
+ views of who answers; `handover` (the host's last change, as its request answered) is drawn at
435
+ once, whatever the socket says. The host picks the conversation;
436
+ the chat has no list and starts none. `name` is the agent's, for the words that address it;
437
+ `messages` are those words, English by default (`placeholder(name, fresh)`: `fresh` while nothing
438
+ is said, `connecting`, `awaiting`, `thinking(name)`, `typing` (the line while it works, where steps
439
+ are hidden), `working` and `writing` (the builder's line),
440
+ `elapsed(seconds)`, `escape`, `cut(heard)`, `empty(name)`: the title of a conversation
441
+ with nothing said, `intro(name)`: a line under it (none when empty), `send`, `stop`, `log`,
442
+ `steps` by action name, `failed` (beside a failed step, in the builder's look), `approval` (the
443
+ head of the approval card), `request(name, action)`, `allow`, `decline`, `confirming` (a person's line for a yes the
444
+ business gives), `handedOff`: the handoff's line, `kit`: the words
445
+ of the surfaces' controls); `locale` is the surfaces' language; `components` is how a block type
446
+ draws (below);
447
+ `actions` is a slot at the composer's left for the host's own controls; `suggestions` are a few
448
+ things the person might say first, offered as quoted lines while nothing has been said (a click says
449
+ one). `compose` (`ChatCompose { text, send, key }`) hands the chat a request from elsewhere on the
450
+ page, such as a button beside it, taken once each time `key` changes (not the one the chat mounts
451
+ with, so a chat mounted again never says it twice; another conversation does not take it either):
452
+ with `send` it is said, once the socket is open (while a reply is on its way or the turn waits, it
453
+ goes into the composer instead); without, it goes into the composer, focused with the caret at its
454
+ end, for the person to finish.
455
+
456
+ On a channel the person's messages are the contact's, at the right as anywhere; the business's
457
+ side stays at the left: the agent's words with a quiet mark under them for where they got to
458
+ (`delivery`: a clock while queued, a check once sent, two once delivered, two in the accent once
459
+ read, the danger tone with the provider's reason once failed), and what a person of the business
460
+ wrote apart from them, in a bubble of a breath of the accent with who wrote it (`business`)
461
+ under it. The surfaces the agent showed went to the person there, so they only read here (an
462
+ approval still takes the owner's answer), and the reply can't be stopped from here. `write(text,
463
+ files)` takes what is written instead of the socket, for a person of the business answering on
464
+ the channel (`POST /conversations/{id}/operator`): the composer says `write`, takes words and
465
+ files whatever the turn is doing, and empties once the promise resolves; a rejection leaves them
466
+ for the host to say why. The words for it: `write`, `business`, `delivery` by state,
467
+ `undelivered(error)`.
468
+
469
+ `steps` (`ChatSteps`) is whether the agent's steps show. By default it follows who the page
470
+ speaks for (`client.who`): our app's cookie (`business`) sees them, as the work it is; a person
471
+ or a visitor (`person`) sees none of them (no file searches, memories, tool calls or their raw
472
+ outputs, which may be in English), only what the agent says and shows (its words, its surfaces,
473
+ files, an approval of theirs, the business's line `confirming`) and, while the turn runs, one
474
+ quiet line, `typing` ("Typing…", "Escribiendo…"), without the seconds or the key that stops it;
475
+ nor how long the reply took. `steps="shown"` or `"hidden"` says it either way.
476
+
477
+ `onTool(call)` runs the host's own tools the agent calls in the page ([Your own tools, run in
478
+ the page](#your-own-tools-run-in-the-page)); without it each is refused with `noTool`, a word the
479
+ agent reads and the person never sees.
480
+
481
+ `look` is `agent` by default. `builder` is v2's builder chat (`sc-chat--builder`): on the page's
482
+ ground (`--sc-bg`), the line over the composer says what it is doing (the step running by its
483
+ name, `writing` while its words come, `working` otherwise) under the brand's light sweeping
484
+ violet into orange, each block settles in as it comes, a step that returned something opens on it
485
+ (`sc-chat__output`) with `failed` beside one that failed, the send is the brand's gradient and
486
+ the stop the ink, the composer takes the focus, and Esc stops the turn from anywhere on the page.
487
+ `console` adds `sc-chat--console`: the same as a console beside the work, the person's words as
488
+ `❯` prompt lines and the composer as a prompt, in the mono face.
489
+
490
+ ## BrowserView
491
+
492
+ `<BrowserView live name messages? classNames? />`: the browser the agent drives, live, while the
493
+ conversation has one (`live` is `useConversation`'s; `AgentChat` docks it over its composer). Small
494
+ while the agent works (its screen, who holds it, the address read only, the page's title), with
495
+ `take` and an enlarge toggle; taken by the person, or handed over by the agent, it opens large and
496
+ the screen takes their pointer, wheel, keys and paste. A hand-over shows its reason and `done`,
497
+ which answers the turn's wait and gives the browser back; `giveBack` returns one they took. A
498
+ hand-over whose browser closed meanwhile still shows `closed` and `done`, so the person can go on.
499
+ Pictures come through `watchScreen` and are painted on a canvas, the latest only, at the page's
500
+ size (the owner's own Chrome keeps the size of their window: the browser's `where` is `mine`, and
501
+ who holds it says so). `messages` (`BROWSER_MESSAGES`, English): `agent(name)`,
502
+ `agentMine(name)`, `person`, `take`, `giveBack`, `handedOver(name)`, `done`, `closed`, `expand`,
503
+ `shrink`, `blank`, `screen(where)`.
504
+
505
+ ## VoiceCall
506
+
507
+ `<VoiceCall connect name tint? orb? orbSize? endedBy? messages? classNames? onAnswer? onEnd? after? captions? />`: a call with
508
+ an agent, the one a customer has on the phone, through LiveKit (`livekit-client`). The call starts
509
+ when it mounts and ends when it unmounts (key it on the attempt for another). It opens the
510
+ microphone first (no call is made for someone who can't be heard), then asks `connect()` where to
511
+ join (`{url, token}`: our API's `client.conversations.call(agentId)` answers both, with the
512
+ call's conversation; `client.agents.call(id)` for the owner's test), joins the room and publishes the microphone; the agent's voice plays as it comes,
513
+ and the call is on once the agent joins (its time counts from then).
514
+
515
+ What it draws, centred: the `Orb`, large (`orbSize`, 280 px; `orb`: `"wave"`, the default, or
516
+ `"fluid-dots"`), named by what the call is doing (`orbOf(look)`): it rests while the line is
517
+ found, moves with whoever speaks (the agent's voice while it speaks, the person's microphone
518
+ while it listens, each read off `meter` on every frame it draws, so the call renders for neither),
519
+ calms while the agent thinks, and fades and comes to rest once over. The shader paints its own
520
+ colours, or the host's (`--sc-orb-color`); the agent's tint (`tint`: `agentTint(id)`) is the
521
+ chrome's, worn by the still orb where there is no WebGL. Under it what the call is doing in a word (`connecting`;
522
+ `listening`, `thinking`, `speaking`: what the agent says it is doing, LiveKit's `lk.agent.state`,
523
+ or its voice for an agent that says nothing; `reconnecting`; `ended`), then who is being called,
524
+ the time, or how it ended and how long it lasted; under that, while the call is on, `captions`:
525
+ what is being said, as the host draws it (`AgentSpace` draws the call's conversation there); and
526
+ the two controls a phone has, mute (filled with the ink while the microphone is off) and hang up
527
+ (the one red thing), each with its word. Once over, `after` stands in their place: the host's
528
+ offers (call again, go back to writing).
529
+
530
+ It ends when the person hangs up; when the room closes under them (the agent's goodbye, or its
531
+ longest time, which the room doesn't tell apart: `endedBy`, the API's word as `endedByReason`
532
+ reads it, does); when the line drops (the network, or the agent leaving without closing the
533
+ room); or, before it begins, when the microphone can't be used (refused, missing, held by
534
+ another application, a browser that can't call), when `connect` fails, and when no agent joins
535
+ within 20 s. Each says why in words, and what to do. `onAnswer()` hears once that the agent took
536
+ the call; `onEnd(answered, problem)` hears once that it is over, whichever way (`answered`: the
537
+ agent took it; `problem`: why it never began, a `CallProblem`, null for an end). Where the
538
+ browser holds the agent's voice until a press, a button lets it play.
539
+
540
+ Space mutes and unmutes from anywhere that isn't a field or a control of the page's own. Escape
541
+ never hangs up: it is the key that closes things, pressed by reflex, and a call ended by accident
542
+ can't be taken back, so ending one is the red button's alone; a host that holds the call in a
543
+ panel keeps the panel open on Escape while the call is on (the root carries `sc-call--on`). Under
544
+ reduced motion the orb draws one still frame per state, and the words say the rest.
545
+
546
+ `messages` (`VOICE_CALL_MESSAGES`, English): `call(name)` (its name for screen readers), `states`
547
+ by look, `calling(name)`, `ended` by who ended it (`person`, `agent(name)`, `limit`, `dropped`),
548
+ `problems` by why it never began (`denied`, `nomic`, `inuse`, `unsupported`, `failed`,
549
+ `unanswered`), each `{title(name), help}`, `mute`, `hangUp`, `keys`, `muted`, `hear(name)`.
550
+ Parts: `sc-call` (`--connecting`, `--listening`, `--thinking`, `--speaking`, `--reconnecting`,
551
+ `--ended`, `--problem`; `--sky`, `--rose`, `--sand`; `--on` while ringing or talking; `--muted`),
552
+ `sc-call__stage`, `sc-call__orb` (on the orb; its own parts are the kit's `sc-orb…`, which
553
+ `classNames` takes too), `sc-call__state`, `sc-call__line`, `sc-call__captions`,
554
+ `sc-call__sound`, `sc-call__controls`, `sc-call__control`, `sc-call__mute`, `sc-call__hangup`,
555
+ `sc-call__hint`, `sc-call__after`.
556
+
557
+ `useCall(connect, { sink })` is the call without its drawing, for a host that draws its own:
558
+ `{ state, meter, mute(muted?), hangUp(), hear() }`, the agent's voice playing from `sink` (an
559
+ element of the host's). `state` is a `CallState` (`stage`, `answeredAt`, `agent`, `muted`,
560
+ `end: {by, seconds}`, `problem`…), `lookOf(state)` its word, `orbOf(look)` the orb's state and
561
+ whose loudness it follows (`"agent"`, `"person"` or none), `durationOf(seconds)` the time as a
562
+ phone shows it ("0:07", "12:34", "1:02:03"), and `meter.agent()` and `meter.person()` each side's
563
+ loudness now, from 0 to 1, to read every frame (an `Orb`'s `level` takes either as it is).
564
+
565
+ ## Orb
566
+
567
+ `<Orb state level size? style? label className? />`: the shape a voice conversation with an
568
+ agent takes, v2's: `state` is `idle` (at rest), `listening`, `thinking` or `speaking`; `level`
569
+ how loud the microphone (listening) or the voice (speaking) is, 0 to 1, a number or a reader
570
+ called on each frame drawn (a meter: the orb follows it with no render); `size` the diameter
571
+ (200 px); `label` its name for screen readers. Two styles (`ORB_STYLES`, `OrbStyle`): `wave`,
572
+ the default, the iOS voice waveform, and `fluid-dots`, six dots that merge and scatter. Both are
573
+ the fragment shaders of siri-wave (MIT), kept verbatim but for the signal they are given (the
574
+ level; the wave also the state's energy: 0 at rest, 0.5 thinking, 1 listening and speaking) and
575
+ the host's colour, on one WebGL canvas. The shaders paint their own spectrum unless
576
+ `--sc-orb-color` names a colour: then their light is painted in it, as bright as it was, so the
577
+ orb is the host's (read off the canvas with each state, so a theme changed shows by the next).
578
+
579
+ Each style is a module of its own, loaded when an orb of it is first drawn: a host that never
580
+ calls loads none. The orb draws only while something moves: it eases the level and the energy
581
+ toward where they go, its clock paced by the energy (slower at rest), asks for no frame while the
582
+ tab is hidden, and stops three seconds after it comes to rest. Under `prefers-reduced-motion`
583
+ there is no loop: one frame per state, at a fixed moment. The shaders paint light on a
584
+ transparent canvas (what is near black is cut out) blended by exclusion with the surface under
585
+ it: as painted on a dark surface, turned over on a light one, ink on paper (so it needs a
586
+ surface painted under it, and fading it is fading the canvas: `opacity` on an ancestor
587
+ isolates it). Where WebGL is
588
+ missing, the shader doesn't compile or the context is lost, a still disc in a ring stands in, its
589
+ outline saying the state (thin at rest, solid listening, dashed thinking, double speaking) in
590
+ `--sc-orb-color`, else the signal (the agent's tint in a call). Parts (the kit's): `sc-orb` (`--idle`,
591
+ `--listening`, `--thinking`, `--speaking`; `--still` under reduced motion and for the disc;
592
+ `--loading` while its style loads), `sc-orb__ring`, `sc-orb__core`.
593
+
594
+ ## The kit
595
+
596
+ The primitives everything the package draws is made of, each one BEM family in `styles.css`
597
+ (tokens only), so a host styles each once and every place it appears follows:
598
+
599
+ - `Card` (`sc-panel`; `sc-card` is the Ficha's), `List` and `Row` (`sc-list`, `sc-row`: a mark, a
600
+ title clamped to two lines, a quieter line, a detail at the right, a `trailing` slot for the
601
+ host's controls such as a `Switch` and a ⋯ `Button`; rows grouped in one hairline box, the
602
+ row pressable with `onPress`, `pressed` for the one picked, `quiet` for one paused), `Mark`
603
+ (`sc-mark`: what kind of thing a row is, bare, with nothing drawn behind it: a glyph in the
604
+ muted ink, a logo as it comes, an initial, or a photo, round for a person; it keeps its square
605
+ so rows line up), `MarkStack` (`sc-stack`: a few marks side by side and "+N"), `FileMark` (a
606
+ file's mark by its family, `familyOf(name, type)`: PDF, Word, Excel, PowerPoint, picture, video,
607
+ sound, text, archive; the common formats in their own tint), `Tag` (`sc-tag`: a short word on
608
+ how something stands, in its tone, no pill; a property every row has is said by the heading
609
+ the rows sit under, not by a word on each), `Stat`, `Note`, `Button`
610
+ (`primary`, `neutral`, `danger`, `ghost`; `sm`; a glyph alone; a link with `href`), `Switch`.
611
+ - The form: `Field` (label, a quiet word when optional, help, error, all wired to the control by
612
+ id) and its controls, each controlled (`value`, `onChange`) and named by `labelledBy` or
613
+ `label`: `TextInput` (text, email, phone, url, with their icon and keyboard), `TextArea`
614
+ (grows), `NumberInput` (− and +, its unit), `SecretInput` (show and hide), `Checkbox`,
615
+ `DateField` (a calendar in a popover, react-day-picker; only the `dates` offered, between
616
+ `earliest` and `latest`), `TimeField` (the `times` as chips, else every half hour to search,
617
+ or one typed; `endOfDay`, an end of a stretch, offers and takes the day's end, "24:00", too), `DateTimeField`, `Choices` (few options as cards, or one bar when short),
618
+ `Chips` (several of few), `Combobox` (the ARIA combobox, one or several, searchable),
619
+ `RangeField` (a slider with its value over the thumb), `RatingField` (stars).
620
+
621
+ `<KitProvider classNames? messages? locale?>` gives the primitives under it the host's classes
622
+ (`KitPart`), words (`KIT_MESSAGES`, English; `automation` holds an automation's and its runs',
623
+ `AUTOMATION_MESSAGES`) and language: `locale` is one of `@signalcore/ui/locale`
624
+ (`import { es } from "@signalcore/ui/locale"`), which the calendar reads and whose `code`
625
+ formats days, times, numbers and lists. Popovers render in a portal at the page's end carrying
626
+ `sc`, so the tokens set on `:root` reach them; inside a host's dialog (`role="dialog"`), in the
627
+ dialog, which a modal one needs to let the pointer and the focus reach them; inside an element
628
+ marked `data-sc-portal`, in it (a shadow root's, where the page's end is outside its styles: the
629
+ widget's).
630
+
631
+ ## Automation and RunView
632
+
633
+ An agent's automation and one of its runs, as its owner reads them (v3/AUTOMATIONS.md), built
634
+ from the kit and drawing only what they are given: the API's objects (`AutomationData`,
635
+ `AutomationView`, `Run`, `RunRecord`, `RunStep`, `Trigger`, `AutomationStep`, `Use`, `Board`),
636
+ the kit's words (`messages.automation`) and its locale's `code`. A host fetches, switches and
637
+ routes; an agent shows one in a conversation as the `automation` block.
638
+
639
+ `<Automation automation runs? onSwitch? switching? onOpenRun? openRun? apps? now? />`: one card.
640
+ The head: what starts it as a 40px mark (a clock, a webhook, the app whose event it waits for),
641
+ the title, when it runs in words with its next run ("Cada día laborable a las 9:00, próxima
642
+ mañana"), and its state in a word (`active` in the success tone, `paused` quiet, `failing` in
643
+ danger) with the host's switch when it gives `onSwitch` (held still while `switching`). Then the
644
+ owner's sentence (`summary`), set apart; its steps, numbered, each with what it uses (an app's
645
+ logo and name, "IA", "Agente", "Mensaje" for a message to a contact on a channel…); the table it
646
+ moves (`board`), a `Stat` per value, four at most and "+N" after; and, given `runs`, its runs edge
647
+ to edge, each with what started it as its mark (named by it), when, its line (a test's as what it would have done), why it
648
+ failed in danger, a check once it went well; `onOpenRun` makes them pressable, `openRun` lights
649
+ the one open. Nothing in it runs an automation.
650
+
651
+ `<RunView run automation? onOpenConversation? apps? now? />`: one run. Its head (what started
652
+ it as its mark, when; at its end how it stands and, under it, how long it took); its line; why it failed, in a `Note`; then what it
653
+ did, step by step in the code's order on a rail, each call in words ("Buscó en Gmail",
654
+ "Escribió un texto con IA", "Añadió una fila a Citas", "Abrió una conversación", "Avisó en la
655
+ bandeja", "Envió un WhatsApp a +34…" by the channel the record says, its brand as the mark when
656
+ the host draws brands; a test's writes as "Habría…"), the same call in a row once with "×N", a
657
+ failed one in danger with its error; and, with `onOpenConversation`, the conversation it was
658
+ asked in and those it opened. `automation` names its apps and its table and numbers its steps.
659
+
660
+ `apps` (`AppDirectory`, as `AgentAbout` takes it) draws apps with the host's brands; without it
661
+ an app shows the logo the API gives, or its initial. The pure helpers: `whenOf(trigger, words,
662
+ code)` and `scheduleOf(cron, words, code)` (a trigger, a cron line, as a sentence; `timed` when
663
+ it names the one time of day it runs), `lineOf(automation, now, words, code)` (the head's line),
664
+ `momentOf`, `dayRefOf`, `timeOf`, `clockOf`, `tookOf`; `verbOf(tool)` (what a tool does, from its
665
+ name), `callWords(step, words.calls, names)`, `groupsOf(steps, words.calls, names)` (a record's
666
+ lines by step) and `namingOf(automation)`; and the cron reader under them, `parseCron`, `fires`,
667
+ `occurrences` and `offsetOf`.
668
+
669
+ ## Surface
670
+
671
+ `<Surface surface onAction active? answered? components? classNames? messages? locale? />` draws
672
+ what the agent showed: a tree of blocks, one to one with the API's models in `blocks.ts`, each
673
+ mapped onto the kit (a `card` is a `Card`, a `list` a `List` of `Row`s, a `stat` a `Stat`, a
674
+ `button` a `Button`, a form's fields the kit's controls by `kind`: a select with up to 5
675
+ options as cards, more as a combobox; a multiselect with up to 8 as chips, more as a combobox).
676
+ `text` is Markdown: paragraphs, bold, italic, code, links, bullet lists. A block or a field kind
677
+ it does not know leaves the surface to its `text` twin. `onAction` receives `{name, data,
678
+ label}`: a button sends its action with `{}`, a choice `{option: id}`, a list `{item: id}`, a
679
+ form its values by field name (words, a number, true or false, "YYYY-MM-DD", "HH:MM",
680
+ "YYYY-MM-DDTHH:MM", option ids; what is left empty as null) under, when it asks one thing, that
681
+ answer in words (the day, the slot, the options picked), else its submit's label. A form checks
682
+ itself on submit (required, bounds, an email's, a phone's, an address's shape, the days and
683
+ times offered), shows what is wrong under each field in the kit's words and focuses the first.
684
+ `active` false closes the inputs; `answered` (what the person already did on it) draws its
685
+ answer: the option, row or button picked, the form's values with "Sent" where the submit was.
686
+ `components={{ table: TheirTable }}` draws a block type with the host's own component, given
687
+ the block's fields, `active` and `onAction` (a container's `children` come drawn). The pure
688
+ helpers `pressed`, `picked`, `chosen`, `filled`, `labelOf` and `problemOf` are what the blocks
689
+ send and check. An `automation` block draws `Automation` from the `view` the API filled in, read
690
+ only (no switch, its runs not pressable: the agent's page is where it is switched and opened);
691
+ one whose `view` is null says quietly that it is no longer there. A `file` block (`FileBlock`)
692
+ is a file the agent put in front of the person, compact: its kind's mark, its title over its name,
693
+ its size; pressed, it opens whole in the host's viewer (`files.open`, the conversation's
694
+ `FileHost`), where `FileView` draws it whatever its kind. A picture, a video or a sound plays where
695
+ it is instead (`part="media"`, `sc-surface__media`), in the browser's own player, streamed from the
696
+ address the host gives (`files.play`; the conversation's asks `client.conversations.play`, a link
697
+ the API signs for an hour that needs no credential and answers `Range`); a picture pressed opens
698
+ in the viewer, a player whose address ran out asks for a new one once, and a host that can't play
699
+ it keeps the row. Until the API has filled in `file` it is only named, quietly.
700
+
701
+ ## AgentAbout
702
+
703
+ `<AgentAbout client agentId name messages? classNames? controls? apps? locale? view? onViewChange? onOpenFile? onOpenConversation? />`: Qué puede, made to sit
704
+ beside the conversation (it fits a page too). From the top: who it is about and, in its own words,
705
+ a line or two of what it takes care of; **what it does on its own**, a row per automation, its
706
+ mark what starts it (a glyph for a time or a call, the app's logo for its event), what it does then
707
+ and when (`Cada día a las 8:00`), a paused one quieter; what it asks the person's leave for; then **what it works with**,
708
+ six topics (`TOPICS`): apps (with their marks), tools (what it can do while it talks), knowledge
709
+ (its files and the tables it reads or writes), learning (what it remembered), channels (where
710
+ people write to it, and its devices), keys. Each topic is a row with its glyph, its name over a
711
+ preview of what it holds and its count, quieter when it holds nothing; a row opens the topic in
712
+ the same place, with the way back above it (the host's control on the title's line), a line saying
713
+ what it is, and its rows: an app with its tools by name; every tool, its own code's and the ones
714
+ every agent has under a heading each when it has both, the ones that are off quieter, saying so;
715
+ what it knows by whose it is, its own under its name and the organization's under theirs (said
716
+ once, by the heading), each file by its kind's mark, its name on one line, its folder under it
717
+ (and that it is only kept, when `search_files` does not read it), its size at the right, then
718
+ the tables with what it may do in them; what it learned with when it was kept; keys, its own and
719
+ the organization's, with where they may go. Its channels are read whole when the topic opens (`channels.list(agentId)`, again after
720
+ each `tools.refresh()`): WhatsApp, Instagram and SMS in that order, the disconnected last and
721
+ quieter, each by its brand's mark (`apps.logo` with the kind as its provider) and name, its number
722
+ or handle, the provider's word on it (`channelDetail`), and how it stands on a dot and a word
723
+ (`channelStates`: amber while pending, the danger tone, with the reason under the row, on an
724
+ error); a server without the list shows the facts' rows.
725
+
726
+ The words are the model's (`describe`), written from the agent's folder, its automations and its
727
+ apps: the summary, one line per automation tied to it by id (`does: [{automation, icon, text,
728
+ when}]`; `automationsOf(about, facts)` pairs them, a new automation showing its title and kind
729
+ until the words catch up) and what it asks leave for. Nobody asks for them: when they are
730
+ missing or the agent changed since (`about_at` null, which a change to its folder, its grants or
731
+ its automations does), the component has the agent write them again by itself, keeping the old
732
+ ones in view with a quiet line; when they could not be read or written, a line in the warning
733
+ tone and a button to try again.
734
+
735
+ The component only reads. A host that lets its people change things draws its own controls
736
+ through `controls` (`AboutControls`), each given `tools: { refresh() }`, which reads the facts
737
+ again and has the agent rewrite its words when the change made them stale: `newAutomation(tools)`
738
+ over what it does on its own, `automation(fact, tools)` at a card's right, `topic[topic](tools)`
739
+ over an open topic, and `app`, `tool`, `file`, `table`, `memory`, `key` and `channel` (the API's
740
+ `Channel`) at a row's right
741
+ (answer `null` for a row that gets none, such as a shared file). Call `tools.refresh()` once a
742
+ change is through. `onOpenFile(file)` opens a file pressed in what it knows.
743
+
744
+ It goes one level in and back, in place, like a phone's settings: a topic or an automation from
745
+ its home, a run from its automation. Each view opened from another has a bar that stays at the
746
+ top as it scrolls: the way back, named for where it goes, and at its end the host's controls (an
747
+ automation's are `controls.automation`, the same as on its row). An automation shows as
748
+ `Automation`, flat on the panel, with its runs; a run as `RunView`; both read with `client`
749
+ (`automations.get`, `runs.get`) and again every 3 s while a run is under way (`usePolled`). The
750
+ rows of what it does on its own also say how each stands when it needs a look: a run under way,
751
+ or its last one failed (`automations.list`). A move in slides from the side it lies on, a move
752
+ back from the other, and each view comes back scrolled where it was left. Where it is lives in
753
+ `view` (`AboutView`: `{kind: "home"}`, `{kind: "topic", topic}`, `{kind: "automation", name}`,
754
+ `{kind: "run", name, run}`), the panel's own unless the host keeps it (in its address, say) and
755
+ hears each move through `onViewChange`; `aboutBack(view)` is a step back, for a host's Escape.
756
+ `onOpenConversation(id)` opens a conversation a run opened or was asked in.
757
+
758
+ `messages` are the words, English by default (`loading`, `writing(name)`, `updating`, `refresh`:
759
+ the try-again button, `asks`, `failed`, `does`, `noAutomations`, `paused`, `kinds`: what an
760
+ automation waits for, `topics`, `empty` and `hints` by topic, `theirs(name)` and `organization`:
761
+ over what is the agent's own and the organization's, `builtIn`: over the tools every agent has,
762
+ `tables`: over the tables, `kept`: under a file nothing reads at a search, `off`, `can`: what it
763
+ may do in a table, `title(name)`, `facts`: over the topics, `actions`: a shipped action's name in
764
+ the owner's words (one of its own code, and any other nobody named, by `actionWords(name)`),
765
+ `size(bytes)`, `ago(when)`, `runGone`: in place of a run no longer there, `channelNames` by kind,
766
+ `channelStates` by status, `channelDetail(detail)`: the provider's word in the host's, `kit`: what
767
+ the kit's pieces in it say, an automation and a run). The pure helpers
768
+ `aboutState(about, aboutAt, loading)`, `automationsOf(about, facts)`, `TOPICS`, `namesOf(topic, facts, label)`, `countOf(topic, facts)`,
769
+ `toolName(action)` (`Gmail.SendEmail` is "Send email"), `actionWords(name)` (`ver_hoja` is "Ver
770
+ hoja", `gmail__GMAIL_SEND_EMAIL` "Send email (Gmail)"), `familyOf(name, type)`,
771
+ `formatSize(bytes, locale?)` and `toneOf(status)` are what it draws from; `FileMark` is the mark
772
+ it gives a file. The package draws its icons with `@phosphor-icons/react`.
773
+
774
+ ## FileView
775
+
776
+ `<FileView blob name preview? messages? classNames? />`: one file to see and to save, whatever its
777
+ kind: a bar with its kind, its size (a PDF's pages) and the button that saves it under its name,
778
+ over the file as it reads. `preview` is what the API makes of it (`client.files.preview(at)`,
779
+ `FilePreview` by `kind`; `at` is `FileAt`: the organization's `{path}`, an agent's own
780
+ `{agentId, path}`, a conversation's `{conversationId, fileId}`): a spreadsheet as a grid, one
781
+ sheet at a time, its columns kept in view and its sheets as tabs at its foot (how many of its rows
782
+ are shown, when not all); a Word file as a document at a reading measure, its HTML kept again here
783
+ to the API's allow-list (`cleanDocument`); a deck's slides one under another, each a card with its
784
+ notes; Markdown drawn, plain words, a source in the mono face wrapped and never run; a PDF in the
785
+ browser's own viewer; a picture whole, a video and a sound in the browser's players; `unknown`
786
+ says it can't be shown, and saves it. Without `preview` it reads what the browser can of the bytes
787
+ alone (words, a CSV, a PDF, a picture). It fills the height it is given and scrolls inside.
788
+ `messages` (`FILE_MESSAGES`): `download`, `noPreview`, `noPreviewHint`, `truncated`, `file`,
789
+ `size(bytes)`, `pages(count)`, `sheets`, `rows(shown, total)`, `emptySheet`, `slide(number)`,
790
+ `notes`. Parts: `sc-file`, `sc-file__bar`, `sc-file__meta`, `sc-file__body`, `sc-file__text`,
791
+ `sc-file__plain`, `sc-file__code`, `sc-file__sheet`, `sc-file__table`, `sc-file__tabs`,
792
+ `sc-file__tab` (`aria-selected` on the one open), `sc-file__document`, `sc-file__slides`,
793
+ `sc-file__slide`, `sc-file__notes`, `sc-file__frame`, `sc-file__image`, `sc-file__media`
794
+ (`data-kind`), `sc-file__none`, `sc-file__note`.
795
+
796
+ ## AgentCard
797
+
798
+ `<AgentCard id name role fields renderValue? classNames? />`: the Ficha as a page about the
799
+ agent, one sheet centred at 600px. At the top the agent's mark (the glyph of its work alone,
800
+ bare, in the tint `agentTint(id)` keeps for it everywhere) beside its name, its role under it;
801
+ then its short fields as a quiet list of properties, each label beside its value; under a
802
+ hairline, each `block` field (a long text) under a small heading at a reading measure. `name`, `role` and each of `fields` is a `CardField {key, label, value, placeholder?,
803
+ block?}`; an empty value draws its placeholder, muted. The card draws text; a host that edits in
804
+ place gives `renderValue(field)`, which draws its own control in the text's place (the band's two
805
+ included) and may answer `undefined` to leave a field to the card. `agentTint(id)` is the pure
806
+ helper under the mark: `sky`, `rose` or `sand` from the id, so a rename changes nothing.
807
+
808
+ ## Theming
809
+
810
+ One vocabulary for every component, in React and in the widget: the `--sc-*` tokens, set on
811
+ `:root`, on `<signalcore-chat>` or on any ancestor. Every visual decision of ours reads one of
812
+ them; their defaults are neutral greys that follow the page's `color-scheme` (`light-dark()`),
813
+ and in the widget the page's own type and text colour stand in for ours. There are no themes or
814
+ presets: a host says what it is, once. Unset, a token keeps the default in the right column.
815
+
816
+ | Token | What it decides | Default |
817
+ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
818
+ | `--sc-font` | the face | the page's in the widget; `ui-sans-serif, system-ui` |
819
+ | `--sc-font-size` | the base size; every other size is relative to it | the page's in the widget; `14px` |
820
+ | `--sc-font-size-sm` | a notch down: a step, a small button, a kit's details | base − 1px |
821
+ | `--sc-font-size-xs` | quiet lines: the working line, a meta, a hint, the modes | base − 2px |
822
+ | `--sc-font-size-lg` | a title: the empty chat's | base + 2px |
823
+ | `--sc-font-weight` | the regular weight | the page's (inherited); 400 where a part resets it |
824
+ | `--sc-font-weight-medium` | names, buttons, the modes | `500` |
825
+ | `--sc-font-weight-bold` | titles, the call's state | `600` |
826
+ | `--sc-font-mono` | code, the console | `ui-monospace, "SF Mono", Menlo` |
827
+ | `--sc-line-height` | the lines' height | `1.5` |
828
+ | `--sc-fg` | the ink | the page's text in the widget; near black, near white |
829
+ | `--sc-muted` | quieter ink: roles, hints, placeholders | grey |
830
+ | `--sc-subtle` | quieter still: a meta, the builder's line | lighter grey |
831
+ | `--sc-bg` | the page's ground (the builder's), what sits on the main action | off-white, near black |
832
+ | `--sc-surface` | the panel, the chat, cards, the composer, the call | white, `#121212` |
833
+ | `--sc-sunken` | a notch off the surface: code, a step's output | a breath off it |
834
+ | `--sc-line` | hairlines and borders | light grey, white at 12% |
835
+ | `--sc-accent` | the launcher, a step running, a picked option, the caret, focus | the ink |
836
+ | `--sc-accent-fg` | what sits on the accent (the launcher's glyph) | the surface |
837
+ | `--sc-signal` | the agent at work: the breathing dot, the still orb | the accent |
838
+ | `--sc-primary`, `--sc-on-primary` | the main action (send, a primary button, mute on) and its glyph | the ink, `--sc-bg` |
839
+ | `--sc-bubble`, `--sc-bubble-fg` | the person's message, and its text | the ink at 5% over the surface, the ink |
840
+ | `--sc-read` | the two checks of a message the person read | WhatsApp's blue, `#53bdeb` |
841
+ | `--sc-success`, `--sc-warning`, `--sc-danger` | the tones a note, a step, a field's error take | green, amber, red |
842
+ | `--sc-danger-fg` | what sits on the danger tone filled (hanging up) | white |
843
+ | `--sc-sky`, `--sc-sand`, `--sc-rose` | the agent's tints as inks (its mark in the face, the still orb in a call); the wash under each derives from it | blue, sand, rose |
844
+ | `--sc-brand-from`, `--sc-brand-to`, `--sc-on-brand` | the brand's gradient: the builder's sweeping light and its send (our app's) | violet, orange, white |
845
+ | `--sc-focus` | a field's focus ring | the accent at 40% |
846
+ | `--sc-orb-color` | the orb: its shader's light, and the still orb's ink | the shaders' own spectrum; the still orb in the signal |
847
+ | `--sc-radius` | a box's corners (cards, the composer, the panel; inner corners derive from it) | `12px` |
848
+ | `--sc-radius-full` | what is round: pills, round buttons, the launcher, the modes | `9999px` |
849
+ | `--sc-space` | the spacing unit: every gap and padding of the chat, the space and the call is a multiple of it | `4px` |
850
+ | `--sc-shadow` | what lifts a card off the page | a hair |
851
+ | `--sc-shadow-raised` | what stands over the surface: the composer, the mode on | soft |
852
+ | `--sc-shadow-overlay` | what floats over the page: a popover, the widget's panel and launcher | deep |
853
+ | `--sc-panel-width`, `--sc-panel-height` | the widget's floating panel | `400px`, `640px` (within the screen) |
854
+ | `--sc-launcher-size`, `--sc-launcher-offset` | the widget's launcher, and how far from the page's corner it and the panel sit | `56px`, `20px` |
855
+
856
+ In React, the same tokens on any ancestor, your classes on the parts (`classNames`) and your own
857
+ drawing of a block (`components`):
534
858
 
535
- ```json
536
- {
537
- "id": "home",
538
- "type": "host",
539
- "component": "PropertyCard",
540
- "props": { "address": "Calle Mayor 3", "price": 250000 }
541
- }
859
+ ```tsx
860
+ <div
861
+ className="support"
862
+ style={{ "--sc-accent": "#0f766e", "--sc-font": "Inter" } as CSSProperties}
863
+ >
864
+ <AgentSpace
865
+ client={client}
866
+ agentId="agt_…"
867
+ classNames={{ "sc-chat__composer": "support-composer", "sc-space__mode": "support-mode" }}
868
+ components={{ table: SupportTable }}
869
+ />
870
+ </div>
542
871
  ```
543
872
 
544
- What your component is given, and what it can count on:
545
-
546
- - **`props`, exactly as the agent sent them.** The API validated them against the schema you
547
- registered before it sent the surface, so you can rely on their shape. They are values: a
548
- binding (`{ "path": … }`) is not looked up in them. There are no `children`.
549
- - **`onAction({ name, data, label })`**, to report what the user does. It goes back through the
550
- surface's action path like a pick on a list: `AgentChat` sends it to the agent as what the
551
- person said (`label`, "Good fit: Calle Mayor 3"), and `Surface` hands your `onAction` the
552
- `{ surface_id, name, data, label }` the API's `Action` expects. It is `undefined` when the surface
553
- takes no actions (it is a record of what was shown), so disable what answers. The node's props
554
- cannot replace it.
555
- - **The text twin is already in the surface's `fallback_text`.** The API appends your component's
556
- twin to the surface's, so where a surface cannot be drawn whole the user reads all of it in words,
557
- never an empty box, and the rest of your page carries on. That happens when this page has not
558
- registered the name (a development build says which in the console), when the props are not an
559
- object, and when your component throws.
560
- - **A primitive's name is not yours to use.** A node cannot name `Button`, `Card`, `Table`, `Input`
561
- or `Select`, and `RequestCard` never draws your components.
562
-
563
- ## Your own client
564
-
565
- `client` is the part of the SDK the components call (`UiClient`): `Signalcore` has all of it. If you
566
- reach the API another way (a session cookie instead of a key), supply the same calls (for streams,
567
- `live: new Live({ baseUrl: location.origin })`).
873
+ ### Parts
874
+
875
+ In the widget, the page styles what is inside its shadow root by `::part(…)`
876
+ (`signalcore-chat::part(send) { … }`). An element may carry several names; since `::part()`
877
+ takes no attribute selector, a state is a name of its own (`mode-selected`). The widget's own
878
+ `stylesheet` reaches everything inside by class as well.
879
+
880
+ | Part | What it is |
881
+ | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
882
+ | `launcher`, `panel` | the round button at the corner, and the panel it opens (or the inline one) |
883
+ | `space`, `header`, `close` | the agent's space, its face (with `header`), its close |
884
+ | `modes`, `mode`, `mode-text`, `mode-voice`, `mode-selected` | the two modes beside the paperclip (and at the call's foot), each, the one on |
885
+ | `chat`, `transcript`, `empty`, `suggestion` | the chat, its scrolling log, a conversation's start, a thing to say first |
886
+ | `message`, `message-person`, `message-agent`, `message-business` | a message, by who said it (the business's: a person of it, on a channel) |
887
+ | `text` | the agent's words (Markdown), in a reply or a surface |
888
+ | `files` | the files sent with a message, or waiting over the composer |
889
+ | `surface` | a screen the agent shows |
890
+ | `approval`, `confirming` | a yes the person gives, and the line for one the business gives |
891
+ | `status` | the line while the agent works ("Typing…") |
892
+ | `composer`, `input`, `attach`, `actions`, `send`, `stop` | where the person writes: its field, the paperclip, the modes' slot, send, stop |
893
+ | `voice`, `call`, `orb`, `call-state`, `call-line`, `captions` | the voice panel, the call in it, the orb, what the call is doing, its time, what is said |
894
+ | `call-controls`, `mute`, `hang-up`, `call-after` | the call's controls, and what is offered once it is over |
895
+ | `card`, `stack`, `divider`, `fields`, `table`, `list`, `row`, `stat`, `image`, `choice`, `option`, `form` | the blocks of what the agent shows |
896
+ | `note`, `note-info`, `note-success`, `note-warning`, `note-danger` | a note, by its tone |
897
+ | `field`, `control` | a form's field, and the box of its control |
898
+ | `button`, `button-primary`, `button-neutral`, `button-danger`, `button-ghost` | a button, by its tone (with its own name too: `close`, `mute`, `hang-up`) |
899
+
900
+ In React the same elements carry the same `part` names (harmless outside a shadow root), and
901
+ everything is reachable by its class: `classNames` adds the host's own classes to named parts,
902
+ which are also the BEM classes they carry. The chat: `sc-chat` (`--builder`, `--console`), `sc-chat__log`, `sc-chat__message`,
903
+ `sc-chat__message--person`, `sc-chat__message--agent`, `sc-chat__message--action`,
904
+ `sc-chat__business` and `sc-chat__message--business` (what a person of the business wrote on a
905
+ channel), `sc-chat__meta` (who wrote it, where it got to), `sc-chat__delivery` (`data-delivery`),
906
+ `sc-chat__steps`, `sc-chat__step` (`--running`, `--waiting`, `--done`, `--failed`),
907
+ `sc-chat__mark` (the step's glyph or state), `sc-chat__output` (what a step returned, in the
908
+ builder's look), `sc-chat__text` (the agent's words),
909
+ `sc-chat__request`, `sc-chat__confirming` (a person's line for a yes the business gives),
910
+ `sc-chat__handoff` (the line where a person took over), `sc-chat__handover`
911
+ (where who answers changed, its `time`), `sc-chat__decision` (a message let pass),
912
+ `sc-chat__message--heard` (the greeting heard on a call), `sc-chat__cut` (where a reply on a call
913
+ was cut short), `sc-chat__reply`,
914
+ `sc-chat__status`, `sc-chat__empty`,
915
+ `sc-chat__suggestion`, `sc-chat__composer`, `sc-chat__input`, `sc-chat__actions`,
916
+ `sc-chat__send`, `sc-chat__stop`. A surface: `sc-surface`, `sc-surface__card`,
917
+ `sc-surface__stack` (`--row`), `sc-surface__text`, `sc-surface__fields`, `sc-surface__table`,
918
+ `sc-surface__note` (`--info`, `--success`, `--warning`, `--danger`), `sc-surface__button`
919
+ (`--primary`, `--neutral`, `--danger`), `sc-surface__choice`, `sc-surface__option`,
920
+ `sc-surface__form`, `sc-surface__field`, `sc-surface__submit`. Qué puede: `sc-about`,
921
+ `sc-about__pane`, `sc-about__intro`, `sc-about__heading`, `sc-about__name` (the mark and the name, `data-tint`), `sc-about__lead`, `sc-about__updated`
922
+ (the words that could not be read, and the button to try again), `sc-about__updating`,
923
+ `sc-about__section`, `sc-about__label`, `sc-about__asks`, `sc-about__menu`, `sc-about__bar`
924
+ (`sc-about__bar-end`: the host's controls), `sc-about__back`, `sc-about__automation`,
925
+ `sc-about__run`, `sc-about__group`, `sc-about__title`, `sc-about__count`, `sc-about__tools` (the
926
+ host's control on a topic's title line), `sc-about__hint`, `sc-about__subtitle` (over a part of
927
+ a topic: whose, or what kind), `sc-about__file` (a file's row: its name and folder on a line
928
+ each), `sc-about__memory`, `sc-about__detail` (a channel's provider's word),
929
+ `sc-about__dot` (`data-tone`), `sc-about__state` (a channel's dot and word, `data-tone`),
930
+ `sc-about__empty`, `sc-about__writing`. The card: `sc-card` (its tint: `--sky`, `--rose`,
931
+ `--sand`), `sc-card__head`, `sc-card__mark` (the agent's mark, bare), `sc-card__who`,
932
+ `sc-card__name`, `sc-card__role`, `sc-card__facts`, `sc-card__fact`, `sc-card__body`,
933
+ `sc-card__row`, `sc-card__label`, `sc-card__value`, `sc-card__empty`. An
934
+ automation: `sc-automation` (`--gone`: the block's quiet line for one not there),
935
+ `sc-automation__head`, `sc-automation__heading`, `sc-automation__title`, `sc-automation__when`,
936
+ `sc-automation__end` (the state's word and the switch), `sc-automation__dot`,
937
+ `sc-automation__summary`, `sc-automation__section`, `sc-automation__label`,
938
+ `sc-automation__steps`, `sc-automation__step`, `sc-automation__number`, `sc-automation__name`,
939
+ `sc-automation__uses`, `sc-automation__use`, `sc-automation__board`, `sc-automation__more`,
940
+ `sc-automation__runs`, `sc-automation__run--open`, `sc-automation__status` (a run's check),
941
+ `sc-automation__empty`. A run: `sc-run`, `sc-run__head`, `sc-run__by` (what started it, its mark
942
+ named by it), `sc-run__heading`, `sc-run__title`, `sc-run__meta` (when), `sc-run__end` (how it
943
+ stands, `sc-run__took` under it), `sc-run__summary`, `sc-run__section`, `sc-run__label`, `sc-run__steps`,
944
+ `sc-run__step`, `sc-run__number`, `sc-run__name`, `sc-run__calls`, `sc-run__call` (`--would`,
945
+ `--failed`), `sc-run__count`, `sc-run__error`, `sc-run__live` (the dot of one still going),
946
+ `sc-run__links`, `sc-run__empty`. The stylesheet is plain,
947
+ unlayered CSS; a host whose utilities must win imports it into a layer under them
948
+ (`@import "@signalcore/ui/styles.css" layer(components);`).
949
+
950
+ Tests: `pnpm test` here, or `pnpm -r test` from `v3/`.