@graph8/sdk 0.308.1 → 0.357.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 (654) hide show
  1. package/README.md +81 -0
  2. package/blocks/README.md +32 -0
  3. package/blocks/_foundation/README.md +78 -0
  4. package/blocks/_foundation/block.json +79 -0
  5. package/blocks/_foundation/components/api-error.tsx +62 -0
  6. package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
  7. package/blocks/_foundation/components/empty-state.tsx +24 -0
  8. package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
  9. package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
  10. package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
  11. package/blocks/_foundation/hooks/use-paginated.ts +105 -0
  12. package/blocks/_foundation/lib/g8-client.ts +128 -0
  13. package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
  14. package/blocks/_foundation/server/proxy.ts +233 -0
  15. package/blocks/_foundation/server/route.ts +30 -0
  16. package/blocks/agents/README.md +133 -0
  17. package/blocks/agents/block.json +223 -0
  18. package/blocks/agents/components/agent-builder.tsx +199 -0
  19. package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
  20. package/blocks/agents/components/agent-runs.tsx +175 -0
  21. package/blocks/agents/components/agent-skills-panel.tsx +126 -0
  22. package/blocks/agents/components/agent-test-console.tsx +175 -0
  23. package/blocks/agents/lib/agent-types.ts +114 -0
  24. package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
  25. package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
  26. package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
  27. package/blocks/analytics/README.md +73 -0
  28. package/blocks/analytics/block.json +178 -0
  29. package/blocks/analytics/components/funnel-chart.tsx +79 -0
  30. package/blocks/analytics/components/kpi-tiles.tsx +74 -0
  31. package/blocks/analytics/components/leaderboard.tsx +86 -0
  32. package/blocks/analytics/components/report-builder.tsx +200 -0
  33. package/blocks/analytics/hooks/use-report.ts +26 -0
  34. package/blocks/analytics/lib/analytics-types.ts +108 -0
  35. package/blocks/analytics/lib/use-debounced.ts +16 -0
  36. package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
  37. package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
  38. package/blocks/analytics/recipes/03-saved-report.ts +30 -0
  39. package/blocks/appointments/README.md +80 -0
  40. package/blocks/appointments/block.json +218 -0
  41. package/blocks/appointments/components/availability-editor.tsx +192 -0
  42. package/blocks/appointments/components/booking-page.tsx +154 -0
  43. package/blocks/appointments/components/event-type-editor.tsx +214 -0
  44. package/blocks/appointments/components/meeting-detail.tsx +147 -0
  45. package/blocks/appointments/components/meeting-list.tsx +148 -0
  46. package/blocks/appointments/hooks/use-available-slots.ts +29 -0
  47. package/blocks/appointments/hooks/use-schedules.ts +18 -0
  48. package/blocks/appointments/lib/appointments-types.ts +159 -0
  49. package/blocks/appointments/lib/idempotency.ts +30 -0
  50. package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
  51. package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
  52. package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
  53. package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
  54. package/blocks/block.schema.json +130 -0
  55. package/blocks/block.types.ts +109 -0
  56. package/blocks/campaigns/README.md +118 -0
  57. package/blocks/campaigns/block.json +361 -0
  58. package/blocks/campaigns/components/campaign-assets.tsx +180 -0
  59. package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
  60. package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
  61. package/blocks/campaigns/components/campaign-list.tsx +120 -0
  62. package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
  63. package/blocks/campaigns/components/campaign-select.tsx +52 -0
  64. package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
  65. package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
  66. package/blocks/campaigns/components/form-field.tsx +29 -0
  67. package/blocks/campaigns/components/icp-editor.tsx +230 -0
  68. package/blocks/campaigns/components/persona-editor.tsx +210 -0
  69. package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
  70. package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
  71. package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
  72. package/blocks/campaigns/recipes/campaign-report.ts +22 -0
  73. package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
  74. package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
  75. package/blocks/coach/README.md +93 -0
  76. package/blocks/coach/block.json +125 -0
  77. package/blocks/coach/components/call-scorecard.tsx +126 -0
  78. package/blocks/coach/components/coaching-feed.tsx +140 -0
  79. package/blocks/coach/lib/coach-types.ts +116 -0
  80. package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
  81. package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
  82. package/blocks/copilot/README.md +99 -0
  83. package/blocks/copilot/block.json +151 -0
  84. package/blocks/copilot/components/copilot-memory.tsx +87 -0
  85. package/blocks/copilot/components/copilot-panel.tsx +189 -0
  86. package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
  87. package/blocks/copilot/lib/copilot-types.ts +111 -0
  88. package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
  89. package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
  90. package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
  91. package/blocks/crm/README.md +122 -0
  92. package/blocks/crm/block.json +378 -0
  93. package/blocks/crm/components/activity-timeline.tsx +99 -0
  94. package/blocks/crm/components/company-detail.tsx +118 -0
  95. package/blocks/crm/components/company-table.tsx +173 -0
  96. package/blocks/crm/components/contact-detail.tsx +135 -0
  97. package/blocks/crm/components/contact-table.tsx +217 -0
  98. package/blocks/crm/components/dedupe-review.tsx +164 -0
  99. package/blocks/crm/components/import-wizard.tsx +193 -0
  100. package/blocks/crm/components/notes-panel.tsx +112 -0
  101. package/blocks/crm/components/record-form.tsx +191 -0
  102. package/blocks/crm/components/task-list.tsx +107 -0
  103. package/blocks/crm/hooks/use-custom-fields.ts +18 -0
  104. package/blocks/crm/hooks/use-debounced.ts +13 -0
  105. package/blocks/crm/hooks/use-page-query.ts +52 -0
  106. package/blocks/crm/lib/crm-types.ts +136 -0
  107. package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
  108. package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
  109. package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
  110. package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
  111. package/blocks/deals/README.md +78 -0
  112. package/blocks/deals/block.json +198 -0
  113. package/blocks/deals/components/deal-detail.tsx +134 -0
  114. package/blocks/deals/components/forecast-view.tsx +112 -0
  115. package/blocks/deals/components/pipeline-board.tsx +142 -0
  116. package/blocks/deals/components/stage-editor.tsx +96 -0
  117. package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
  118. package/blocks/deals/hooks/use-pipelines.ts +19 -0
  119. package/blocks/deals/lib/deals-types.ts +128 -0
  120. package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
  121. package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
  122. package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
  123. package/blocks/dialer/README.md +114 -0
  124. package/blocks/dialer/block.json +260 -0
  125. package/blocks/dialer/components/call-log.tsx +78 -0
  126. package/blocks/dialer/components/call-queue.tsx +188 -0
  127. package/blocks/dialer/components/dialer-workspace.tsx +47 -0
  128. package/blocks/dialer/components/disposition-panel.tsx +75 -0
  129. package/blocks/dialer/components/live-calls-table.tsx +107 -0
  130. package/blocks/dialer/components/session-start-form.tsx +111 -0
  131. package/blocks/dialer/components/softphone.tsx +122 -0
  132. package/blocks/dialer/components/transcript-viewer.tsx +82 -0
  133. package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
  134. package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
  135. package/blocks/dialer/hooks/use-dispositions.ts +29 -0
  136. package/blocks/dialer/lib/dialer-types.ts +158 -0
  137. package/blocks/dialer/lib/softphone-transport.ts +67 -0
  138. package/blocks/dialer/lib/twilio-transport.ts +74 -0
  139. package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
  140. package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
  141. package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
  142. package/blocks/enrichment/README.md +105 -0
  143. package/blocks/enrichment/block.json +204 -0
  144. package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
  145. package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
  146. package/blocks/enrichment/components/enrich-button.tsx +136 -0
  147. package/blocks/enrichment/components/waterfall-config.tsx +127 -0
  148. package/blocks/enrichment/hooks/use-providers.ts +18 -0
  149. package/blocks/enrichment/lib/enrichment-types.ts +104 -0
  150. package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
  151. package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
  152. package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
  153. package/blocks/forms/README.md +112 -0
  154. package/blocks/forms/block.json +200 -0
  155. package/blocks/forms/components/form-builder.tsx +150 -0
  156. package/blocks/forms/components/form-embed.tsx +103 -0
  157. package/blocks/forms/components/form-mapping-editor.tsx +125 -0
  158. package/blocks/forms/components/site-audit-panel.tsx +73 -0
  159. package/blocks/forms/components/submissions-table.tsx +158 -0
  160. package/blocks/forms/lib/form-types.ts +127 -0
  161. package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
  162. package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
  163. package/blocks/forms/recipes/route-form-to-list.ts +44 -0
  164. package/blocks/inbox/README.md +72 -0
  165. package/blocks/inbox/block.json +194 -0
  166. package/blocks/inbox/components/inbox-list.tsx +156 -0
  167. package/blocks/inbox/components/inbox-screen.tsx +49 -0
  168. package/blocks/inbox/components/reply-composer.tsx +183 -0
  169. package/blocks/inbox/components/tag-and-assign.tsx +189 -0
  170. package/blocks/inbox/components/thread-badge.tsx +17 -0
  171. package/blocks/inbox/components/thread-view.tsx +108 -0
  172. package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
  173. package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
  174. package/blocks/inbox/lib/idempotency.ts +30 -0
  175. package/blocks/inbox/lib/inbox-types.ts +100 -0
  176. package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
  177. package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
  178. package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
  179. package/blocks/integrations/README.md +75 -0
  180. package/blocks/integrations/block.json +172 -0
  181. package/blocks/integrations/components/integration-list.tsx +165 -0
  182. package/blocks/integrations/components/sync-status.tsx +223 -0
  183. package/blocks/integrations/components/webhook-manager.tsx +300 -0
  184. package/blocks/integrations/lib/integrations-utils.ts +62 -0
  185. package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
  186. package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
  187. package/blocks/integrations/recipes/register-webhook.ts +18 -0
  188. package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
  189. package/blocks/intent/README.md +57 -0
  190. package/blocks/intent/block.json +138 -0
  191. package/blocks/intent/components/account-intent-score.tsx +91 -0
  192. package/blocks/intent/components/intent-feed.tsx +215 -0
  193. package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
  194. package/blocks/intent/hooks/use-account-intent.ts +46 -0
  195. package/blocks/intent/lib/intent-utils.ts +95 -0
  196. package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
  197. package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
  198. package/blocks/intent/recipes/who-visited-url.ts +18 -0
  199. package/blocks/lifecycle/README.md +79 -0
  200. package/blocks/lifecycle/block.json +198 -0
  201. package/blocks/lifecycle/components/churn-list.tsx +92 -0
  202. package/blocks/lifecycle/components/customer-table.tsx +119 -0
  203. package/blocks/lifecycle/components/lead-queue.tsx +127 -0
  204. package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
  205. package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
  206. package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
  207. package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
  208. package/blocks/lifecycle/lib/lifecycle.ts +99 -0
  209. package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
  210. package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
  211. package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
  212. package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
  213. package/blocks/lists/README.md +96 -0
  214. package/blocks/lists/block.json +166 -0
  215. package/blocks/lists/components/add-to-list-button.tsx +111 -0
  216. package/blocks/lists/components/list-picker.tsx +94 -0
  217. package/blocks/lists/components/list-table.tsx +160 -0
  218. package/blocks/lists/components/suppression-manager.tsx +153 -0
  219. package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
  220. package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
  221. package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
  222. package/blocks/mailboxes/README.md +71 -0
  223. package/blocks/mailboxes/block.json +187 -0
  224. package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
  225. package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
  226. package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
  227. package/blocks/mailboxes/components/warmup-health.tsx +232 -0
  228. package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
  229. package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
  230. package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
  231. package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
  232. package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
  233. package/blocks/newsletter/README.md +122 -0
  234. package/blocks/newsletter/block.json +224 -0
  235. package/blocks/newsletter/components/issue-form.tsx +124 -0
  236. package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
  237. package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
  238. package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
  239. package/blocks/newsletter/components/subscriber-table.tsx +173 -0
  240. package/blocks/newsletter/lib/newsletter-types.ts +97 -0
  241. package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
  242. package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
  243. package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
  244. package/blocks/nurture/README.md +110 -0
  245. package/blocks/nurture/block.json +191 -0
  246. package/blocks/nurture/components/nurture-builder.tsx +114 -0
  247. package/blocks/nurture/components/nurture-enroll.tsx +107 -0
  248. package/blocks/nurture/components/nurture-form.tsx +120 -0
  249. package/blocks/nurture/components/nurture-stats.tsx +81 -0
  250. package/blocks/nurture/lib/nurture-types.ts +109 -0
  251. package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
  252. package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
  253. package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
  254. package/blocks/objects/README.md +120 -0
  255. package/blocks/objects/block.json +243 -0
  256. package/blocks/objects/components/attribute-field-input.tsx +82 -0
  257. package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
  258. package/blocks/objects/components/object-not-found.tsx +34 -0
  259. package/blocks/objects/components/object-record-detail.tsx +143 -0
  260. package/blocks/objects/components/object-schema-editor.tsx +197 -0
  261. package/blocks/objects/components/object-table.tsx +130 -0
  262. package/blocks/objects/components/record-form-dialog.tsx +112 -0
  263. package/blocks/objects/components/record-roles-panel.tsx +106 -0
  264. package/blocks/objects/hooks/use-object-schema.ts +36 -0
  265. package/blocks/objects/lib/object-types.ts +214 -0
  266. package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
  267. package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
  268. package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
  269. package/blocks/pages/README.md +104 -0
  270. package/blocks/pages/block.json +223 -0
  271. package/blocks/pages/components/brand-kit-editor.tsx +152 -0
  272. package/blocks/pages/components/content-library.tsx +163 -0
  273. package/blocks/pages/components/page-editor.tsx +253 -0
  274. package/blocks/pages/components/page-list.tsx +159 -0
  275. package/blocks/pages/components/seo-report.tsx +159 -0
  276. package/blocks/pages/lib/studio.ts +77 -0
  277. package/blocks/pages/recipes/audit-site-seo.ts +28 -0
  278. package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
  279. package/blocks/pages/recipes/edit-page-section.ts +25 -0
  280. package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
  281. package/blocks/quotes/README.md +79 -0
  282. package/blocks/quotes/block.json +183 -0
  283. package/blocks/quotes/components/product-picker.tsx +80 -0
  284. package/blocks/quotes/components/quote-builder.tsx +195 -0
  285. package/blocks/quotes/components/quote-list.tsx +79 -0
  286. package/blocks/quotes/components/quote-viewer.tsx +135 -0
  287. package/blocks/quotes/components/signature-status.tsx +77 -0
  288. package/blocks/quotes/hooks/use-debounced.ts +13 -0
  289. package/blocks/quotes/lib/quotes.ts +142 -0
  290. package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
  291. package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
  292. package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
  293. package/blocks/radar/README.md +106 -0
  294. package/blocks/radar/block.json +189 -0
  295. package/blocks/radar/components/competitor-battle-card.tsx +53 -0
  296. package/blocks/radar/components/competitor-tracker.tsx +158 -0
  297. package/blocks/radar/components/radar-overview.tsx +67 -0
  298. package/blocks/radar/components/radar-report.tsx +86 -0
  299. package/blocks/radar/hooks/use-competitors.ts +23 -0
  300. package/blocks/radar/lib/radar-types.ts +124 -0
  301. package/blocks/radar/recipes/battle-card.ts +28 -0
  302. package/blocks/radar/recipes/deep-dive-report.ts +32 -0
  303. package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
  304. package/blocks/radar/recipes/track-and-scan.ts +39 -0
  305. package/blocks/search/README.md +129 -0
  306. package/blocks/search/block.json +185 -0
  307. package/blocks/search/components/filter-builder.tsx +149 -0
  308. package/blocks/search/components/prospect-results.tsx +175 -0
  309. package/blocks/search/components/save-to-list-dialog.tsx +135 -0
  310. package/blocks/search/components/saved-searches.tsx +95 -0
  311. package/blocks/search/hooks/use-filter-options.ts +41 -0
  312. package/blocks/search/lib/search-types.ts +149 -0
  313. package/blocks/search/recipes/explore-filter-values.ts +27 -0
  314. package/blocks/search/recipes/saved-searches.ts +34 -0
  315. package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
  316. package/blocks/sequencer/README.md +87 -0
  317. package/blocks/sequencer/block.json +214 -0
  318. package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
  319. package/blocks/sequencer/components/sequence-builder.tsx +306 -0
  320. package/blocks/sequencer/components/sequence-list.tsx +200 -0
  321. package/blocks/sequencer/components/sequence-stats.tsx +256 -0
  322. package/blocks/sequencer/components/step-editor.tsx +213 -0
  323. package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
  324. package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
  325. package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
  326. package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
  327. package/blocks/sequencer/recipes/sequence-report.ts +22 -0
  328. package/blocks/social-ads/README.md +128 -0
  329. package/blocks/social-ads/block.json +256 -0
  330. package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
  331. package/blocks/social-ads/components/audience-sync.tsx +150 -0
  332. package/blocks/social-ads/components/creative-gallery.tsx +121 -0
  333. package/blocks/social-ads/components/post-composer.tsx +122 -0
  334. package/blocks/social-ads/components/social-post-list.tsx +116 -0
  335. package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
  336. package/blocks/social-ads/lib/social-ads-types.ts +167 -0
  337. package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
  338. package/blocks/social-ads/recipes/schedule-post.ts +51 -0
  339. package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
  340. package/blocks/visitors/README.md +56 -0
  341. package/blocks/visitors/block.json +139 -0
  342. package/blocks/visitors/components/snippet-install.tsx +169 -0
  343. package/blocks/visitors/components/traffic-chart.tsx +156 -0
  344. package/blocks/visitors/components/visitor-company-card.tsx +92 -0
  345. package/blocks/visitors/components/visitor-feed.tsx +157 -0
  346. package/blocks/visitors/lib/visitors-utils.ts +78 -0
  347. package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
  348. package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
  349. package/blocks/visitors/recipes/traffic-summary.ts +24 -0
  350. package/blocks/webchat/README.md +87 -0
  351. package/blocks/webchat/block.json +134 -0
  352. package/blocks/webchat/components/chat-widget-config.tsx +160 -0
  353. package/blocks/webchat/components/conversation-list.tsx +74 -0
  354. package/blocks/webchat/lib/webchat-types.ts +83 -0
  355. package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
  356. package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
  357. package/blocks/workbench/README.md +135 -0
  358. package/blocks/workbench/block.json +259 -0
  359. package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
  360. package/blocks/workbench/components/run-progress.tsx +132 -0
  361. package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
  362. package/blocks/workbench/components/workbench-grid.tsx +159 -0
  363. package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
  364. package/blocks/workbench/components/workbench-table-list.tsx +105 -0
  365. package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
  366. package/blocks/workbench/lib/waterfall-presets.ts +119 -0
  367. package/blocks/workbench/lib/workbench-types.ts +191 -0
  368. package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
  369. package/blocks/workbench/recipes/import-from-search.ts +42 -0
  370. package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
  371. package/blocks/workbench/recipes/waterfall-column.ts +55 -0
  372. package/blocks/workflows/README.md +100 -0
  373. package/blocks/workflows/block.json +188 -0
  374. package/blocks/workflows/components/run-history.tsx +136 -0
  375. package/blocks/workflows/components/trigger-config.tsx +134 -0
  376. package/blocks/workflows/components/workflow-editor.tsx +142 -0
  377. package/blocks/workflows/components/workflow-list.tsx +153 -0
  378. package/blocks/workflows/hooks/use-workflow.ts +51 -0
  379. package/blocks/workflows/lib/graph.ts +187 -0
  380. package/blocks/workflows/recipes/create-workflow.ts +35 -0
  381. package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
  382. package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
  383. package/blocks/workflows/recipes/set-trigger.ts +22 -0
  384. package/dist/cli.js +5330 -29
  385. package/dist/index.browser.js +7740 -0
  386. package/dist/index.browser.js.map +1 -0
  387. package/dist/index.browser.mjs +7717 -0
  388. package/dist/index.browser.mjs.map +1 -0
  389. package/dist/index.d.mts +1440 -21
  390. package/dist/index.d.ts +1440 -21
  391. package/dist/index.js +42 -3
  392. package/dist/index.js.map +1 -1
  393. package/dist/index.mjs +42 -3
  394. package/dist/index.mjs.map +1 -1
  395. package/dist/react.browser.js +7598 -0
  396. package/dist/react.browser.js.map +1 -0
  397. package/dist/react.browser.mjs +7568 -0
  398. package/dist/react.browser.mjs.map +1 -0
  399. package/dist/react.d.mts +1340 -18
  400. package/dist/react.d.ts +1340 -18
  401. package/dist/react.js +41 -2
  402. package/dist/react.js.map +1 -1
  403. package/dist/react.mjs +41 -2
  404. package/dist/react.mjs.map +1 -1
  405. package/dist/types/agentSetup.d.ts +6 -6
  406. package/dist/types/blocks/add.d.ts +53 -0
  407. package/dist/types/blocks/create.d.ts +46 -0
  408. package/dist/types/blocks/doctor.d.ts +26 -0
  409. package/dist/types/blocks/registry.d.ts +134 -0
  410. package/dist/types/cli.d.ts +16 -0
  411. package/dist/types/core.d.ts +1 -1
  412. package/dist/types/crypto-browser.d.ts +2 -0
  413. package/dist/types/crypto-node.d.ts +6 -0
  414. package/dist/types/react.d.ts +1 -1
  415. package/dist/types/version.d.ts +2 -2
  416. package/dist/types/webhooks.d.ts +2 -2
  417. package/dist/ui/index.d.mts +54 -0
  418. package/dist/ui/index.d.ts +54 -0
  419. package/dist/ui/index.js +105 -0
  420. package/dist/ui/index.js.map +1 -0
  421. package/dist/ui/index.mjs +44 -0
  422. package/dist/ui/index.mjs.map +1 -0
  423. package/dist/ui/lib/compose-refs.d.mts +19 -0
  424. package/dist/ui/lib/compose-refs.d.ts +19 -0
  425. package/dist/ui/lib/compose-refs.js +76 -0
  426. package/dist/ui/lib/compose-refs.js.map +1 -0
  427. package/dist/ui/lib/compose-refs.mjs +41 -0
  428. package/dist/ui/lib/compose-refs.mjs.map +1 -0
  429. package/dist/ui/lib/use-mobile.d.mts +4 -0
  430. package/dist/ui/lib/use-mobile.d.ts +4 -0
  431. package/dist/ui/lib/use-mobile.js +57 -0
  432. package/dist/ui/lib/use-mobile.js.map +1 -0
  433. package/dist/ui/lib/use-mobile.mjs +22 -0
  434. package/dist/ui/lib/use-mobile.mjs.map +1 -0
  435. package/dist/ui/lib/utils.d.mts +10 -0
  436. package/dist/ui/lib/utils.d.ts +10 -0
  437. package/dist/ui/lib/utils.js +33 -0
  438. package/dist/ui/lib/utils.js.map +1 -0
  439. package/dist/ui/lib/utils.mjs +9 -0
  440. package/dist/ui/lib/utils.mjs.map +1 -0
  441. package/dist/ui/primitives/alert-dialog.d.mts +22 -0
  442. package/dist/ui/primitives/alert-dialog.d.ts +22 -0
  443. package/dist/ui/primitives/alert-dialog.js +163 -0
  444. package/dist/ui/primitives/alert-dialog.js.map +1 -0
  445. package/dist/ui/primitives/alert-dialog.mjs +119 -0
  446. package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
  447. package/dist/ui/primitives/avatar.d.mts +8 -0
  448. package/dist/ui/primitives/avatar.d.ts +8 -0
  449. package/dist/ui/primitives/avatar.js +91 -0
  450. package/dist/ui/primitives/avatar.js.map +1 -0
  451. package/dist/ui/primitives/avatar.mjs +55 -0
  452. package/dist/ui/primitives/avatar.mjs.map +1 -0
  453. package/dist/ui/primitives/breadcrumb.d.mts +13 -0
  454. package/dist/ui/primitives/breadcrumb.d.ts +13 -0
  455. package/dist/ui/primitives/breadcrumb.js +127 -0
  456. package/dist/ui/primitives/breadcrumb.js.map +1 -0
  457. package/dist/ui/primitives/breadcrumb.mjs +97 -0
  458. package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
  459. package/dist/ui/primitives/button.d.mts +15 -0
  460. package/dist/ui/primitives/button.d.ts +15 -0
  461. package/dist/ui/primitives/button.js +91 -0
  462. package/dist/ui/primitives/button.js.map +1 -0
  463. package/dist/ui/primitives/button.mjs +56 -0
  464. package/dist/ui/primitives/button.mjs.map +1 -0
  465. package/dist/ui/primitives/card.d.mts +10 -0
  466. package/dist/ui/primitives/card.d.ts +10 -0
  467. package/dist/ui/primitives/card.js +101 -0
  468. package/dist/ui/primitives/card.js.map +1 -0
  469. package/dist/ui/primitives/card.mjs +62 -0
  470. package/dist/ui/primitives/card.mjs.map +1 -0
  471. package/dist/ui/primitives/carousel.d.mts +45 -0
  472. package/dist/ui/primitives/carousel.d.ts +45 -0
  473. package/dist/ui/primitives/carousel.js +300 -0
  474. package/dist/ui/primitives/carousel.js.map +1 -0
  475. package/dist/ui/primitives/carousel.mjs +261 -0
  476. package/dist/ui/primitives/carousel.mjs.map +1 -0
  477. package/dist/ui/primitives/chart.d.mts +68 -0
  478. package/dist/ui/primitives/chart.d.ts +68 -0
  479. package/dist/ui/primitives/chart.js +280 -0
  480. package/dist/ui/primitives/chart.js.map +1 -0
  481. package/dist/ui/primitives/chart.mjs +241 -0
  482. package/dist/ui/primitives/chart.mjs.map +1 -0
  483. package/dist/ui/primitives/collapsible.d.mts +8 -0
  484. package/dist/ui/primitives/collapsible.d.ts +8 -0
  485. package/dist/ui/primitives/collapsible.js +72 -0
  486. package/dist/ui/primitives/collapsible.js.map +1 -0
  487. package/dist/ui/primitives/collapsible.mjs +36 -0
  488. package/dist/ui/primitives/collapsible.mjs.map +1 -0
  489. package/dist/ui/primitives/context-menu.d.mts +29 -0
  490. package/dist/ui/primitives/context-menu.d.ts +29 -0
  491. package/dist/ui/primitives/context-menu.js +203 -0
  492. package/dist/ui/primitives/context-menu.js.map +1 -0
  493. package/dist/ui/primitives/context-menu.mjs +155 -0
  494. package/dist/ui/primitives/context-menu.mjs.map +1 -0
  495. package/dist/ui/primitives/dialog.d.mts +26 -0
  496. package/dist/ui/primitives/dialog.d.ts +26 -0
  497. package/dist/ui/primitives/dialog.js +192 -0
  498. package/dist/ui/primitives/dialog.js.map +1 -0
  499. package/dist/ui/primitives/dialog.mjs +148 -0
  500. package/dist/ui/primitives/dialog.mjs.map +1 -0
  501. package/dist/ui/primitives/drawer.d.mts +26 -0
  502. package/dist/ui/primitives/drawer.d.ts +26 -0
  503. package/dist/ui/primitives/drawer.js +146 -0
  504. package/dist/ui/primitives/drawer.js.map +1 -0
  505. package/dist/ui/primitives/drawer.mjs +103 -0
  506. package/dist/ui/primitives/drawer.mjs.map +1 -0
  507. package/dist/ui/primitives/input.d.mts +5 -0
  508. package/dist/ui/primitives/input.d.ts +5 -0
  509. package/dist/ui/primitives/input.js +46 -0
  510. package/dist/ui/primitives/input.js.map +1 -0
  511. package/dist/ui/primitives/input.mjs +22 -0
  512. package/dist/ui/primitives/input.mjs.map +1 -0
  513. package/dist/ui/primitives/item.d.mts +27 -0
  514. package/dist/ui/primitives/item.d.ts +27 -0
  515. package/dist/ui/primitives/item.js +221 -0
  516. package/dist/ui/primitives/item.js.map +1 -0
  517. package/dist/ui/primitives/item.mjs +188 -0
  518. package/dist/ui/primitives/item.mjs.map +1 -0
  519. package/dist/ui/primitives/kbd.d.mts +15 -0
  520. package/dist/ui/primitives/kbd.d.ts +15 -0
  521. package/dist/ui/primitives/kbd.js +45 -0
  522. package/dist/ui/primitives/kbd.js.map +1 -0
  523. package/dist/ui/primitives/kbd.mjs +21 -0
  524. package/dist/ui/primitives/kbd.mjs.map +1 -0
  525. package/dist/ui/primitives/label.d.mts +6 -0
  526. package/dist/ui/primitives/label.d.ts +6 -0
  527. package/dist/ui/primitives/label.js +55 -0
  528. package/dist/ui/primitives/label.js.map +1 -0
  529. package/dist/ui/primitives/label.mjs +21 -0
  530. package/dist/ui/primitives/label.mjs.map +1 -0
  531. package/dist/ui/primitives/resizable.d.mts +10 -0
  532. package/dist/ui/primitives/resizable.d.ts +10 -0
  533. package/dist/ui/primitives/resizable.js +68 -0
  534. package/dist/ui/primitives/resizable.js.map +1 -0
  535. package/dist/ui/primitives/resizable.mjs +42 -0
  536. package/dist/ui/primitives/resizable.mjs.map +1 -0
  537. package/dist/ui/primitives/save-status.d.mts +31 -0
  538. package/dist/ui/primitives/save-status.d.ts +31 -0
  539. package/dist/ui/primitives/save-status.js +150 -0
  540. package/dist/ui/primitives/save-status.js.map +1 -0
  541. package/dist/ui/primitives/save-status.mjs +126 -0
  542. package/dist/ui/primitives/save-status.mjs.map +1 -0
  543. package/dist/ui/primitives/separator.d.mts +6 -0
  544. package/dist/ui/primitives/separator.d.ts +6 -0
  545. package/dist/ui/primitives/separator.js +62 -0
  546. package/dist/ui/primitives/separator.js.map +1 -0
  547. package/dist/ui/primitives/separator.mjs +28 -0
  548. package/dist/ui/primitives/separator.mjs.map +1 -0
  549. package/dist/ui/primitives/sheet.d.mts +16 -0
  550. package/dist/ui/primitives/sheet.d.ts +16 -0
  551. package/dist/ui/primitives/sheet.js +170 -0
  552. package/dist/ui/primitives/sheet.js.map +1 -0
  553. package/dist/ui/primitives/sheet.mjs +129 -0
  554. package/dist/ui/primitives/sheet.mjs.map +1 -0
  555. package/dist/ui/primitives/sidebar.d.mts +74 -0
  556. package/dist/ui/primitives/sidebar.d.ts +74 -0
  557. package/dist/ui/primitives/sidebar.js +697 -0
  558. package/dist/ui/primitives/sidebar.js.map +1 -0
  559. package/dist/ui/primitives/sidebar.mjs +651 -0
  560. package/dist/ui/primitives/sidebar.mjs.map +1 -0
  561. package/dist/ui/primitives/skeleton.d.mts +5 -0
  562. package/dist/ui/primitives/skeleton.d.ts +5 -0
  563. package/dist/ui/primitives/skeleton.js +40 -0
  564. package/dist/ui/primitives/skeleton.js.map +1 -0
  565. package/dist/ui/primitives/skeleton.mjs +16 -0
  566. package/dist/ui/primitives/skeleton.mjs.map +1 -0
  567. package/dist/ui/primitives/slider.d.mts +20 -0
  568. package/dist/ui/primitives/slider.d.ts +20 -0
  569. package/dist/ui/primitives/slider.js +112 -0
  570. package/dist/ui/primitives/slider.js.map +1 -0
  571. package/dist/ui/primitives/slider.mjs +78 -0
  572. package/dist/ui/primitives/slider.mjs.map +1 -0
  573. package/dist/ui/primitives/sortable.d.mts +50 -0
  574. package/dist/ui/primitives/sortable.d.ts +50 -0
  575. package/dist/ui/primitives/sortable.js +456 -0
  576. package/dist/ui/primitives/sortable.js.map +1 -0
  577. package/dist/ui/primitives/sortable.mjs +435 -0
  578. package/dist/ui/primitives/sortable.mjs.map +1 -0
  579. package/dist/ui/primitives/switch.d.mts +6 -0
  580. package/dist/ui/primitives/switch.d.ts +6 -0
  581. package/dist/ui/primitives/switch.js +67 -0
  582. package/dist/ui/primitives/switch.js.map +1 -0
  583. package/dist/ui/primitives/switch.mjs +33 -0
  584. package/dist/ui/primitives/switch.mjs.map +1 -0
  585. package/dist/ui/primitives/table.d.mts +12 -0
  586. package/dist/ui/primitives/table.d.ts +12 -0
  587. package/dist/ui/primitives/table.js +130 -0
  588. package/dist/ui/primitives/table.js.map +1 -0
  589. package/dist/ui/primitives/table.mjs +89 -0
  590. package/dist/ui/primitives/table.mjs.map +1 -0
  591. package/dist/ui/primitives/tabs.d.mts +9 -0
  592. package/dist/ui/primitives/tabs.d.ts +9 -0
  593. package/dist/ui/primitives/tabs.js +86 -0
  594. package/dist/ui/primitives/tabs.js.map +1 -0
  595. package/dist/ui/primitives/tabs.mjs +49 -0
  596. package/dist/ui/primitives/tabs.mjs.map +1 -0
  597. package/dist/ui/primitives/textarea.d.mts +5 -0
  598. package/dist/ui/primitives/textarea.d.ts +5 -0
  599. package/dist/ui/primitives/textarea.js +43 -0
  600. package/dist/ui/primitives/textarea.js.map +1 -0
  601. package/dist/ui/primitives/textarea.mjs +19 -0
  602. package/dist/ui/primitives/textarea.mjs.map +1 -0
  603. package/dist/ui/primitives/tooltip.d.mts +9 -0
  604. package/dist/ui/primitives/tooltip.d.ts +9 -0
  605. package/dist/ui/primitives/tooltip.js +94 -0
  606. package/dist/ui/primitives/tooltip.js.map +1 -0
  607. package/dist/ui/primitives/tooltip.mjs +57 -0
  608. package/dist/ui/primitives/tooltip.mjs.map +1 -0
  609. package/docs/agents.md +72 -29
  610. package/docs/api/README.md +6 -3
  611. package/docs/api/appObjects.d.ts +1 -1
  612. package/docs/api/appObjects.md +1 -1
  613. package/docs/api/by-path.md +29 -0
  614. package/docs/api/objects.d.ts +72 -5
  615. package/docs/api/objects.md +84 -7
  616. package/docs/api/processLists.d.ts +247 -0
  617. package/docs/api/processLists.md +409 -0
  618. package/docs/api/reports.d.ts +476 -0
  619. package/docs/api/reports.md +539 -0
  620. package/docs/api/scheduleBuy.d.ts +12 -8
  621. package/docs/api/scheduleBuy.md +13 -10
  622. package/docs/api/seo.d.ts +406 -3
  623. package/docs/api/seo.md +505 -1
  624. package/docs/api/workspaceBackup.d.ts +324 -0
  625. package/docs/api/workspaceBackup.md +427 -0
  626. package/package.json +174 -5
  627. package/stacks/6sense.json +46 -0
  628. package/stacks/README.md +20 -0
  629. package/stacks/apollo.json +128 -0
  630. package/stacks/clay.json +59 -0
  631. package/stacks/gong.json +72 -0
  632. package/stacks/hubspot.json +162 -0
  633. package/stacks/intercom.json +53 -0
  634. package/stacks/mailchimp.json +71 -0
  635. package/stacks/outreach.json +86 -0
  636. package/stacks/salesforce.json +94 -0
  637. package/stacks/stack.schema.json +32 -0
  638. package/stacks/starter.json +22 -0
  639. package/styles/styles.css +2 -0
  640. package/styles/theme.css +181 -0
  641. package/styles/tokens.css +284 -0
  642. package/templates/next-app/README.md.tmpl +35 -0
  643. package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
  644. package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
  645. package/templates/next-app/app/globals.css.tmpl +8 -0
  646. package/templates/next-app/app/layout.tsx.tmpl +19 -0
  647. package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
  648. package/templates/next-app/dot-env.local.example.tmpl +6 -0
  649. package/templates/next-app/dot-gitignore.tmpl +7 -0
  650. package/templates/next-app/lib/nav.ts.tmpl +2 -0
  651. package/templates/next-app/next.config.mjs.tmpl +4 -0
  652. package/templates/next-app/package.json.tmpl +25 -0
  653. package/templates/next-app/postcss.config.mjs.tmpl +4 -0
  654. package/templates/next-app/tsconfig.json.tmpl +21 -0
@@ -0,0 +1,99 @@
1
+ # copilot: the graph8 copilot as a panel
2
+
3
+ Drop an "Ask graph8" side panel into your app. It chats with the copilot (which can look things up, draft
4
+ copy and propose changes), chats in a Work channel where agents answer, and manages what the copilot
5
+ remembers. Everything is request/response or polled: the API has no streams.
6
+
7
+ Install: `npx @graph8/sdk add copilot` (installs `foundation` first).
8
+
9
+ ## What you get
10
+
11
+ | Component | Use it for | Embed props |
12
+ |---|---|---|
13
+ | `CopilotPanel` | The whole panel: Ask, Team and Memory tabs | `context`, `initialTab`, `confirmEveryMessage` |
14
+ | `CopilotTeamChat` | A Work channel with polled messages and "ask an agent" | `channelId`, `pollMs` |
15
+ | `CopilotMemory` | What the copilot remembers, plus the tool catalog | `scope` |
16
+
17
+ ## The flow, with the ids that pass between steps
18
+
19
+ 1. **Mention** (optional): typing `@name` calls `POST /copilot/mention-search { query, mode }`; the chosen items
20
+ are sent back unchanged as `mentions` on the chat call.
21
+ 2. **Ask**: `POST /copilot/chat { message, conversation_history, mentions, web_search, thinking, research,
22
+ session_id, canvas_context }` answers `{ response, events }`. `events` has `text`, `tool_start`,
23
+ `tool_end`, `approval_required`, `error` and a final `done` with `sources`. The panel keeps the
24
+ conversation and sends it back as `conversation_history` each time.
25
+ 3. **Approvals**: a tool that needs a person (publishing a campaign, exporting a list) comes back as an
26
+ `approval_required` event and is NOT run. The panel shows "needs your approval in graph8"; the API has no
27
+ operation to approve it, so the person does that in the graph8 app.
28
+ 4. **Team**: `GET /work/channels` gives channel `id`s; `GET /work/conversations/{id}/messages?limit=50` is
29
+ polled (newest first); `POST /work/messages { channel, message, mentionAgentIds }` posts. Mentioning an
30
+ agent (ids from `GET /work/agents`) starts an agent turn and its answer arrives as a new message.
31
+ 5. **Memory**: `GET /copilot/memory?scope=user`, `POST /copilot/memory { content, scope }`,
32
+ `DELETE /copilot/memory/{memory_id}`. `GET /copilot/tools` lists the tools and which need approval.
33
+
34
+ ```tsx
35
+ "use client";
36
+ import { CopilotPanel } from "@/components/g8/copilot/components/copilot-panel";
37
+
38
+ export function AskGraph8({ campaignId }: { campaignId: string }) {
39
+ return <CopilotPanel context={{ mode: "campaign", campaignId }} className="w-[28rem]" />;
40
+ }
41
+ ```
42
+
43
+ (`add` installs under `components/g8/copilot/`, or `src/components/g8/copilot/`; adjust the alias.)
44
+
45
+ ## Tiers and confirmations
46
+
47
+ - Chat is `billable`: `CopilotPanel` opens `ConfirmSpendDialog` before the first message of a session, with a
48
+ "Don't ask again this session" choice. Set `confirmEveryMessage` to ask every time. There is no price or
49
+ balance read, so the dialog states that replies use credits (research the most) instead of a number.
50
+ - Posting in a channel is a write; with an agent mentioned it starts a billable agent turn, so
51
+ `CopilotTeamChat` confirms that case as `billable`.
52
+ - Forgetting a memory is `destructive` and confirmed.
53
+ - Mention search, listing memories and tools, reading channels are free reads.
54
+
55
+ ## Prerequisites
56
+
57
+ - `foundation-installed` (app): proxy `allowedPrefixes` include `copilot` and `work`.
58
+ - `api-key-scopes` (api): `knowledge:run|read|write|delete`, `work:read|write`.
59
+ - `credits` (api): a chat answers 402 when the workspace is out of credit; the panel shows the error.
60
+
61
+ ## Defaults the agent can use without asking
62
+
63
+ | Decision | Default |
64
+ |---|---|
65
+ | Confirmation for chat | once per session |
66
+ | Web search / thinking / deep research | off |
67
+ | Memory scope | `user` |
68
+
69
+ Ask the person: which Work channels and agents the panel may use (narrow it in the proxy's `authorize`).
70
+
71
+ ## Known limits
72
+
73
+ - No streaming: a reply appears when it is complete; the panel shows "Thinking..." meanwhile.
74
+ - A key that is not a member of a channel gets 403 on read and post; an organisation key sits in the built-in
75
+ channels only.
76
+ - The copilot's memory and personal wiki are per person; with an organisation key they are the key's.
77
+
78
+ ## Recipes
79
+
80
+ | File | Does |
81
+ |---|---|
82
+ | `recipes/ask-the-copilot.ts` | Ask, read `response` and tool events, handle 402, list the tool catalog |
83
+ | `recipes/remember-and-recall.ts` | Add a memory, list, forget by id |
84
+ | `recipes/post-in-a-work-channel.ts` | Post with an agent mention and poll for the reply |
85
+
86
+ ## Operations
87
+
88
+ | Operation | Method and path | Tier | Used by |
89
+ |---|---|---|---|
90
+ | chatWithCopilot | POST /copilot/chat | billable | CopilotPanel |
91
+ | listCopilotMemories | GET /copilot/memory | read | CopilotMemory |
92
+ | createCopilotMemory | POST /copilot/memory | write | CopilotMemory |
93
+ | deleteCopilotMemory | DELETE /copilot/memory/{memory_id} | destructive | CopilotMemory |
94
+ | searchCopilotMentions | POST /copilot/mention-search | read | CopilotPanel |
95
+ | listCopilotTools | GET /copilot/tools | read | CopilotMemory |
96
+ | listWorkAgents | GET /work/agents | read | CopilotTeamChat |
97
+ | listWorkChannels | GET /work/channels | read | CopilotTeamChat |
98
+ | listWorkConversationMessages | GET /work/conversations/{conversation_id}/messages | read | CopilotTeamChat |
99
+ | createWorkMessage | POST /work/messages | write | CopilotTeamChat |
@@ -0,0 +1,151 @@
1
+ {
2
+ "$schema": "../block.schema.json",
3
+ "id": "copilot",
4
+ "title": "Copilot",
5
+ "summary": "A copilot side panel: ask the graph8 copilot (request/response, billable), chat in a Work channel with agents (polled), and manage what it remembers.",
6
+ "features": [
7
+ "chat with @mentions, web search, thinking and deep research options",
8
+ "tool events and approval-needed proposals shown inline",
9
+ "Work channel chat with agent mentions, polled",
10
+ "memory list with add and forget",
11
+ "tool catalog showing which tools need approval"
12
+ ],
13
+ "dependsOn": [
14
+ "foundation"
15
+ ],
16
+ "operations": [
17
+ "chat_with_copilot_copilot_chat_post",
18
+ "create_copilot_memory_copilot_memory_post",
19
+ "create_work_message_work_messages_post",
20
+ "delete_copilot_memory_copilot_memory__memory_id__delete",
21
+ "list_copilot_memories_copilot_memory_get",
22
+ "list_copilot_tools_copilot_tools_get",
23
+ "list_work_agents_work_agents_get",
24
+ "list_work_channels_work_channels_get",
25
+ "list_work_conversation_messages_work_conversations__conversation_id__messages_get",
26
+ "search_copilot_mentions_copilot_mention_search_post"
27
+ ],
28
+ "components": [
29
+ {
30
+ "name": "CopilotPanel",
31
+ "file": "components/copilot-panel.tsx",
32
+ "purpose": "The copilot as a panel: Ask (billable chat with mentions and options), Team and Memory tabs.",
33
+ "operations": [
34
+ "search_copilot_mentions_copilot_mention_search_post",
35
+ "chat_with_copilot_copilot_chat_post"
36
+ ],
37
+ "tiers": [
38
+ "read",
39
+ "billable"
40
+ ]
41
+ },
42
+ {
43
+ "name": "CopilotTeamChat",
44
+ "file": "components/copilot-team-chat.tsx",
45
+ "purpose": "A Work channel: read the polled conversation, post, and ask an agent by mention (billable agent turn confirmed).",
46
+ "operations": [
47
+ "list_work_channels_work_channels_get",
48
+ "list_work_agents_work_agents_get",
49
+ "list_work_conversation_messages_work_conversations__conversation_id__messages_get",
50
+ "create_work_message_work_messages_post"
51
+ ],
52
+ "tiers": [
53
+ "read",
54
+ "write"
55
+ ]
56
+ },
57
+ {
58
+ "name": "CopilotMemory",
59
+ "file": "components/copilot-memory.tsx",
60
+ "purpose": "What the copilot remembers: list, add, forget (confirmed), plus the tool catalog.",
61
+ "operations": [
62
+ "list_copilot_memories_copilot_memory_get",
63
+ "create_copilot_memory_copilot_memory_post",
64
+ "delete_copilot_memory_copilot_memory__memory_id__delete",
65
+ "list_copilot_tools_copilot_tools_get"
66
+ ],
67
+ "tiers": [
68
+ "read",
69
+ "write",
70
+ "destructive"
71
+ ]
72
+ }
73
+ ],
74
+ "hooks": [],
75
+ "lib": [
76
+ {
77
+ "file": "lib/copilot-types.ts",
78
+ "purpose": "Chat turn and event shapes, and summariseReply(), which folds the copilot's events into one bubble."
79
+ }
80
+ ],
81
+ "recipes": [
82
+ {
83
+ "file": "recipes/ask-the-copilot.ts",
84
+ "title": "Ask the copilot and read its answer and tool events",
85
+ "when": "Server-side copilot calls; billable, answered in one piece."
86
+ },
87
+ {
88
+ "file": "recipes/remember-and-recall.ts",
89
+ "title": "Teach the copilot a standing fact, forget it",
90
+ "when": "A rule should shape every answer."
91
+ },
92
+ {
93
+ "file": "recipes/post-in-a-work-channel.ts",
94
+ "title": "Post in a Work channel, ask an agent, poll for the reply",
95
+ "when": "Asking a teammate or agent in a shared channel."
96
+ }
97
+ ],
98
+ "prerequisites": [
99
+ {
100
+ "id": "foundation-installed",
101
+ "description": "The foundation block is installed and the /api/g8 proxy allows the copilot and work prefixes.",
102
+ "where": "app",
103
+ "verify": "allowedPrefixes includes \"copilot\" and \"work\"."
104
+ },
105
+ {
106
+ "id": "api-key-scopes",
107
+ "description": "The API key carries the block's scopes (knowledge and work).",
108
+ "where": "api",
109
+ "verify": "describeCurrentKey lists knowledge:run, knowledge:read, knowledge:write, knowledge:delete, work:read, work:write."
110
+ },
111
+ {
112
+ "id": "credits",
113
+ "description": "The workspace has credits: chat replies are billable and answer 402 when it is out.",
114
+ "where": "api",
115
+ "verify": "A test message to the copilot answers 200, not 402."
116
+ }
117
+ ],
118
+ "scopes": [
119
+ "knowledge:delete",
120
+ "knowledge:read",
121
+ "knowledge:run",
122
+ "knowledge:write",
123
+ "work:read",
124
+ "work:write"
125
+ ],
126
+ "defaults": [
127
+ {
128
+ "decision": "Confirmation for chat",
129
+ "default": "once per session, with a 'don't ask again' choice (confirmEveryMessage turns it off)",
130
+ "askHuman": false
131
+ },
132
+ {
133
+ "decision": "Web search, thinking, deep research",
134
+ "default": "all off",
135
+ "askHuman": false
136
+ },
137
+ {
138
+ "decision": "Memory scope",
139
+ "default": "user",
140
+ "askHuman": false
141
+ },
142
+ {
143
+ "decision": "Which Work channels and agents the panel may use",
144
+ "default": "whatever the key can read; narrow it in the proxy's authorize hook",
145
+ "askHuman": true
146
+ }
147
+ ],
148
+ "peerDependencies": {
149
+ "react": ">=18"
150
+ }
151
+ }
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CopilotMemory: what the copilot remembers about you and your work, and what it can do.
5
+ *
6
+ * SDK operations, in order:
7
+ * 1. listCopilotMemories GET /copilot/memory?scope&limit (read)
8
+ * 2. createCopilotMemory POST /copilot/memory { content, scope } (write)
9
+ * 3. deleteCopilotMemory DELETE /copilot/memory/{memory_id} (destructive, ConfirmSpendDialog)
10
+ * 4. listCopilotTools GET /copilot/tools (read) which tools exist and which need approval
11
+ *
12
+ * A memory is a short fact in plain words ("We never discount below 15%"). The copilot reads memories on
13
+ * every chat, so a wrong one steers every answer: forgetting one is confirmed.
14
+ */
15
+
16
+ import { useState } from "react";
17
+ import { Button, Input, Skeleton } from "@graph8/sdk/ui";
18
+ import { ApiError } from "@/g8/components/api-error";
19
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
20
+ import { EmptyState } from "@/g8/components/empty-state";
21
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
22
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
23
+ import { g8Delete, g8Get, g8Post } from "@/g8/lib/g8-client";
24
+ import { listOf } from "../lib/copilot-types";
25
+ import type { MemoryRow, ToolRow } from "../lib/copilot-types";
26
+
27
+ export interface CopilotMemoryProps {
28
+ /** Memory scope to read and write. Default "user". */
29
+ scope?: string;
30
+ className?: string;
31
+ }
32
+
33
+ const idOf = (m: MemoryRow): string => m.memory_id ?? m.id ?? "";
34
+
35
+ export function CopilotMemory({ scope = "user", className }: CopilotMemoryProps) {
36
+ const [note, setNote] = useState("");
37
+ const [forgetting, setForgetting] = useState<MemoryRow | null>(null);
38
+ const [showTools, setShowTools] = useState(false);
39
+
40
+ const memories = useG8Query((signal) => g8Get("/copilot/memory", { scope, limit: 100 }, { signal }), [scope]);
41
+ const tools = useG8Query((signal) => g8Get("/copilot/tools", undefined, { signal }), [], { enabled: showTools });
42
+ const add = useG8Mutation(() => g8Post("/copilot/memory", { content: note.trim(), scope }), { onSuccess: () => { setNote(""); void memories.refetch(); } });
43
+ const forget = useG8Mutation((id: string) => g8Delete(`/copilot/memory/${id}`), { onSuccess: () => { setForgetting(null); void memories.refetch(); } });
44
+
45
+ const rows = listOf<MemoryRow>(memories.data, "items");
46
+ const toolRows = listOf<ToolRow>(tools.data, "items");
47
+
48
+ return (
49
+ <div className={`flex flex-col gap-4 ${className ?? ""}`}>
50
+ <form className="flex gap-2" onSubmit={(e) => { e.preventDefault(); if (note.trim()) void add.mutate(undefined); }}>
51
+ <Input aria-label="New memory" placeholder="Something the copilot should always remember" value={note} onChange={(e) => setNote(e.target.value)} />
52
+ <Button type="submit" loading={add.loading} disabled={!note.trim()}>Remember</Button>
53
+ </form>
54
+ {add.error ? <ApiError error={add.error} title="Could not save the memory" /> : null}
55
+ {memories.error ? <ApiError error={memories.error} title="Could not load memories" onRetry={() => void memories.refetch()} /> : null}
56
+ {memories.loading && rows.length === 0 ? <Skeleton className="h-10 w-full" /> : null}
57
+ <ul className="divide-y rounded-md border">
58
+ {rows.map((m) => (
59
+ <li key={idOf(m)} data-testid={`memory-${idOf(m)}`} className="flex items-start justify-between gap-3 p-2 text-sm">
60
+ <span className="whitespace-pre-wrap">{m.content}</span>
61
+ <Button size="sm" variant="ghost" onClick={() => setForgetting(m)}>Forget</Button>
62
+ </li>
63
+ ))}
64
+ </ul>
65
+ {!memories.loading && !memories.error && rows.length === 0 ? <EmptyState title="Nothing remembered yet" description="Add the rules and facts you want in every answer." /> : null}
66
+ {forget.error ? <ApiError error={forget.error} title="Could not forget it" /> : null}
67
+
68
+ <div>
69
+ <Button size="sm" variant="outline" onClick={() => setShowTools((v) => !v)}>{showTools ? "Hide tools" : "What can it do?"}</Button>
70
+ {tools.error ? <ApiError error={tools.error} title="Could not load tools" onRetry={() => void tools.refetch()} /> : null}
71
+ {showTools ? (
72
+ <ul className="mt-2 divide-y rounded-md border" data-testid="tools">
73
+ {toolRows.map((t) => (
74
+ <li key={t.name} className="flex items-start justify-between gap-3 p-2 text-sm">
75
+ <span><span className="font-medium">{t.name}</span>{t.description ? <span className="block text-xs text-muted-foreground">{t.description}</span> : null}</span>
76
+ {t.requires_approval || t.needs_approval ? <span className="text-xs text-amber-700">needs approval</span> : null}
77
+ </li>
78
+ ))}
79
+ </ul>
80
+ ) : null}
81
+ </div>
82
+
83
+ <ConfirmSpendDialog open={forgetting != null} tier="destructive" title="Forget this memory" description={forgetting?.content} confirmLabel="Forget"
84
+ affected={1} confirming={forget.loading} onConfirm={() => forgetting && void forget.mutate(idOf(forgetting))} onCancel={() => setForgetting(null)} />
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,189 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CopilotPanel: the graph8 copilot as a side panel. Four tabs: Ask (chat with the copilot), Team (post in a
5
+ * Work channel and read the answers), Memory (what the copilot remembers, add / forget) and Tools (what it
6
+ * can do, and which tools need a person's approval).
7
+ *
8
+ * Ask, SDK operations in order:
9
+ * 1. searchCopilotMentions POST /copilot/mention-search { query, mode } (read) as you type "@name"
10
+ * 2. chatWithCopilot POST /copilot/chat { message, conversation_history, mentions, web_search, thinking, research, session_id }
11
+ * (billable, ConfirmSpendDialog) answers { response, events } in one piece: no streaming
12
+ *
13
+ * The first message of a session asks for confirmation (every reply spends credits, `research` the most);
14
+ * "Don't ask again this session" skips it for the rest. Tools that need approval are PROPOSED in `events`
15
+ * and shown as "needs approval"; the API cannot approve them, the person does that in the graph8 app.
16
+ * Team and Memory are CopilotTeamChat and CopilotMemory; mount them alone if you only need one.
17
+ */
18
+
19
+ import { useEffect, useRef, useState } from "react";
20
+ import { Button, Switch, Textarea } from "@graph8/sdk/ui";
21
+ import { ApiError } from "@/g8/components/api-error";
22
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
23
+ import { EmptyState } from "@/g8/components/empty-state";
24
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
25
+ import { g8Post } from "@/g8/lib/g8-client";
26
+ import { listOf, mentionLabel, summariseReply } from "../lib/copilot-types";
27
+ import type { ChatTurn, CopilotReply, MentionHit } from "../lib/copilot-types";
28
+ import { CopilotMemory } from "./copilot-memory";
29
+ import { CopilotTeamChat } from "./copilot-team-chat";
30
+
31
+ export interface CopilotPanelProps {
32
+ /** Where the copilot is being asked from; passed as `canvas_context`. */
33
+ context?: { mode?: "global" | "campaign" | "account"; campaignId?: string | null; accountId?: string | null };
34
+ /** First tab. Default "ask". */
35
+ initialTab?: "ask" | "team" | "memory";
36
+ /** Ask for confirmation before every message instead of once per session. */
37
+ confirmEveryMessage?: boolean;
38
+ className?: string;
39
+ }
40
+
41
+ type Tab = "ask" | "team" | "memory";
42
+ const TABS: Array<{ id: Tab; label: string }> = [{ id: "ask", label: "Ask" }, { id: "team", label: "Team" }, { id: "memory", label: "Memory" }];
43
+ /** Only mentions whose @label is still in the text go to the API (the person may have deleted it). */
44
+ const mentionsIn = (message: string, list: MentionHit[]): MentionHit[] | undefined => {
45
+ const kept = list.filter((m) => message.includes(`@${mentionLabel(m)}`));
46
+ return kept.length ? kept : undefined;
47
+ };
48
+ const MENTION_AT_END = /(^|\s)@([\w.-]{1,40})$/;
49
+
50
+ export function CopilotPanel({ context, initialTab = "ask", confirmEveryMessage = false, className }: CopilotPanelProps) {
51
+ const [tab, setTab] = useState<Tab>(initialTab);
52
+ const [turns, setTurns] = useState<ChatTurn[]>([]);
53
+ const [draft, setDraft] = useState("");
54
+ const [mentions, setMentions] = useState<MentionHit[]>([]);
55
+ const [options, setOptions] = useState({ web_search: false, thinking: false, research: false });
56
+ const [sessionId, setSessionId] = useState<string | undefined>();
57
+ const [pending, setPending] = useState<string | null>(null);
58
+ const [skipConfirm, setSkipConfirm] = useState(false);
59
+ const [suggestions, setSuggestions] = useState<MentionHit[]>([]);
60
+ const nextId = useRef(1);
61
+ const lastMentions = useRef<MentionHit[]>([]);
62
+ const [dontAsk, setDontAsk] = useState(false);
63
+ const bottom = useRef<HTMLDivElement>(null);
64
+
65
+ const searchSeq = useRef(0);
66
+ // Latest request wins: a slow answer for "@ac" must not overwrite the one for "@acme".
67
+ const search = useG8Mutation(async (q: string) => {
68
+ const mine = ++searchSeq.current;
69
+ const res = await g8Post("/copilot/mention-search", { query: q, mode: context?.mode });
70
+ if (mine === searchSeq.current) setSuggestions(listOf<MentionHit>(res, "items"));
71
+ return res;
72
+ });
73
+ const chat = useG8Mutation(
74
+ (v: { message: string; history: ChatTurn[]; mentions: MentionHit[] }) =>
75
+ g8Post<CopilotReply>("/copilot/chat", {
76
+ message: v.message, conversation_history: v.history.filter((t) => !t.error).map((t) => ({ role: t.role, content: t.text })),
77
+ mentions: mentionsIn(v.message, v.mentions), session_id: sessionId,
78
+ canvas_context: context ? { mode: context.mode ?? "global", campaignId: context.campaignId ?? null, accountId: context.accountId ?? null } : undefined,
79
+ web_search: options.web_search, thinking: options.thinking, research: options.research,
80
+ }),
81
+ {
82
+ onSuccess: (res) => {
83
+ const reply = ((res as { data?: CopilotReply }).data ?? res) as CopilotReply;
84
+ if (reply.session_id) setSessionId(reply.session_id);
85
+ setTurns((t) => [...t, summariseReply(reply, nextId.current++)]);
86
+ },
87
+ },
88
+ );
89
+
90
+ useEffect(() => { bottom.current?.scrollIntoView?.({ block: "end" }); }, [turns.length, chat.loading]);
91
+ useEffect(() => {
92
+ const m = MENTION_AT_END.exec(draft);
93
+ if (!m) { searchSeq.current++; setSuggestions([]); return; }
94
+ const t = setTimeout(() => void search.mutate(m[2]), 200);
95
+ return () => clearTimeout(t);
96
+ }, [draft]); // eslint-disable-line react-hooks/exhaustive-deps
97
+
98
+ const send = (text: string, history: ChatTurn[] = turns, sent: MentionHit[] = mentions) => {
99
+ setPending(null);
100
+ lastMentions.current = sent;
101
+ setTurns((t) => [...t, { id: nextId.current++, role: "user", text }]);
102
+ setDraft("");
103
+ void chat.mutate({ message: text, history, mentions: sent });
104
+ setMentions([]);
105
+ };
106
+ // Retry: the failed user turn is not part of the history it is resent with, mentions are kept, and the billable
107
+ // confirmation applies exactly as for a new message.
108
+ const retry = () => {
109
+ const last = turns[turns.length - 1];
110
+ if (last?.role !== "user") return;
111
+ const base = turns.slice(0, -1);
112
+ setTurns(base);
113
+ chat.reset();
114
+ if (skipConfirm && !confirmEveryMessage) send(last.text, base, lastMentions.current);
115
+ else { setMentions(lastMentions.current); setPending(last.text); }
116
+ };
117
+ const submit = () => {
118
+ const text = draft.trim();
119
+ if (!text || chat.loading) return;
120
+ if (skipConfirm && !confirmEveryMessage) send(text);
121
+ else setPending(text);
122
+ };
123
+ const pick = (m: MentionHit) => {
124
+ setMentions((list) => [...list, m]);
125
+ setDraft((d) => d.replace(MENTION_AT_END, (_all, lead: string) => `${lead}@${mentionLabel(m)} `));
126
+ setSuggestions([]);
127
+ };
128
+ const setOpt = (k: keyof typeof options) => (v: boolean) => setOptions((o) => ({ ...o, [k]: v }));
129
+
130
+ return (
131
+ <div className={`flex h-full min-h-[24rem] flex-col gap-3 ${className ?? ""}`}>
132
+ <div role="tablist" className="flex gap-1 border-b">
133
+ {TABS.map((t) => (
134
+ <button key={t.id} role="tab" type="button" aria-selected={tab === t.id} onClick={() => setTab(t.id)}
135
+ className={`px-3 py-2 text-sm ${tab === t.id ? "border-b-2 border-primary font-medium" : "text-muted-foreground"}`}>{t.label}</button>
136
+ ))}
137
+ </div>
138
+
139
+ {tab === "ask" ? (
140
+ <>
141
+ <div className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto" aria-live="polite" data-testid="transcript">
142
+ {turns.length === 0 ? <EmptyState title="Ask the copilot" description="It can look things up, draft copy and propose changes. Type @ to mention a campaign, document or teammate." /> : null}
143
+ {turns.map((t) => (
144
+ <div key={t.id} data-testid={`turn-${t.role}`} className={`max-w-[85%] rounded-lg px-3 py-2 text-sm ${t.role === "user" ? "self-end bg-primary text-primary-foreground" : t.error ? "self-start border border-destructive" : "self-start bg-muted"}`}>
145
+ <p className="whitespace-pre-wrap">{t.text}</p>
146
+ {t.tools?.length ? <p className="mt-1 text-xs opacity-70">Used: {t.tools.join(", ")}</p> : null}
147
+ {t.needsApproval?.length ? <p className="mt-1 text-xs text-amber-700">Needs your approval in graph8 before it runs: {t.needsApproval.join(", ")}</p> : null}
148
+ {t.sources ? <p className="mt-1 text-xs opacity-70">{t.sources} sources</p> : null}
149
+ </div>
150
+ ))}
151
+ {chat.loading ? <div className="self-start rounded-lg bg-muted px-3 py-2 text-sm text-muted-foreground">Thinking...</div> : null}
152
+ {chat.error ? <ApiError error={chat.error} title="The copilot could not answer" onRetry={retry} /> : null}
153
+ <div ref={bottom} />
154
+ </div>
155
+
156
+ <div className="flex flex-wrap items-center gap-4 text-xs">
157
+ {([["web_search", "Web search"], ["thinking", "Think longer"], ["research", "Deep research"]] as const).map(([k, label]) => (
158
+ <label key={k} className="flex items-center gap-1"><Switch aria-label={label} checked={options[k]} onCheckedChange={setOpt(k)} />{label}</label>
159
+ ))}
160
+ {mentions.length ? <span className="text-muted-foreground">Mentioning: {mentions.map(mentionLabel).join(", ")}</span> : null}
161
+ </div>
162
+ {suggestions.length ? (
163
+ <ul role="listbox" aria-label="Mentions" className="rounded-md border bg-background text-sm">
164
+ {suggestions.map((m, i) => (
165
+ <li key={`${m.id ?? i}`}><button type="button" role="option" aria-selected={false} className="w-full px-3 py-1 text-left hover:bg-accent" onClick={() => pick(m)}>{mentionLabel(m)}{m.type ? <span className="ml-2 text-xs text-muted-foreground">{m.type}</span> : null}</button></li>
166
+ ))}
167
+ </ul>
168
+ ) : null}
169
+ <div className="flex gap-2">
170
+ <Textarea aria-label="Message" rows={2} placeholder="Ask anything" value={draft} onChange={(e) => setDraft(e.target.value)}
171
+ onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); } }} />
172
+ <Button className="self-end" disabled={!draft.trim() || chat.loading} onClick={submit}>Send</Button>
173
+ </div>
174
+ </>
175
+ ) : null}
176
+ {tab === "team" ? <CopilotTeamChat /> : null}
177
+ {tab === "memory" ? <CopilotMemory /> : null}
178
+
179
+ <ConfirmSpendDialog open={pending != null} tier="billable" title="Send to the copilot"
180
+ description={
181
+ <div className="flex flex-col gap-2">
182
+ <span>Each reply uses credits. Web search and thinking use more; deep research uses the most.</span>
183
+ <label className="flex items-center gap-2 text-sm"><input type="checkbox" checked={dontAsk} onChange={(e) => setDontAsk(e.target.checked)} />Don&apos;t ask again this session</label>
184
+ </div>
185
+ }
186
+ confirmLabel="Send" onConfirm={() => { if (pending) { setSkipConfirm(dontAsk); send(pending); } }} onCancel={() => { setPending(null); setDontAsk(false); }} />
187
+ </div>
188
+ );
189
+ }
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ /**
4
+ * CopilotTeamChat: a Work channel inside the panel. Read the conversation, post a message, and @-ask an
5
+ * agent: mentioning an agent starts an agent turn in the background, so the answer arrives as a new
6
+ * message a moment later. There are no streams, so the conversation is polled.
7
+ *
8
+ * SDK operations, in order:
9
+ * 1. listWorkChannels GET /work/channels (read)
10
+ * 2. listWorkAgents GET /work/agents (read) who can be asked
11
+ * 3. listWorkConversationMessages GET /work/conversations/{conversation_id}/messages?limit (read) polled; newest first
12
+ * 4. createWorkMessage POST /work/messages { channel, message, mentionAgentIds } (write; with an agent
13
+ * mentioned it starts a billable agent turn, so it goes through ConfirmSpendDialog)
14
+ *
15
+ * A key may only post where it holds a seat (the built-in channels for an organisation key): a 403 is shown
16
+ * as-is. The conversation id is the channel's `id`.
17
+ */
18
+
19
+ import { useEffect, useRef, useState } from "react";
20
+ import { Button, Input } from "@graph8/sdk/ui";
21
+ import { ApiError } from "@/g8/components/api-error";
22
+ import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
23
+ import { EmptyState } from "@/g8/components/empty-state";
24
+ import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
25
+ import { useG8Query } from "@/g8/hooks/use-g8-query";
26
+ import { useJobPoll } from "@/g8/hooks/use-job-poll";
27
+ import { g8Get, g8Post } from "@/g8/lib/g8-client";
28
+ import { bodyOf, listOf } from "../lib/copilot-types";
29
+ import type { WorkChannel, WorkMessageRow } from "../lib/copilot-types";
30
+
31
+ export interface CopilotTeamChatProps {
32
+ /** Start in this channel (id). Without it the first channel is opened. */
33
+ channelId?: string;
34
+ /** Poll interval in ms. Default 4000. */
35
+ pollMs?: number;
36
+ className?: string;
37
+ }
38
+
39
+ interface AgentRow { agent_id?: string | null; name?: string | null }
40
+ interface Page { conversation_id?: string; messages?: WorkMessageRow[] }
41
+
42
+ const newKey = (): string => `team-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
43
+
44
+ export function CopilotTeamChat({ channelId, pollMs = 4000, className }: CopilotTeamChatProps) {
45
+ const [channel, setChannel] = useState<string | undefined>(channelId);
46
+ const [text, setText] = useState("");
47
+ const [agentId, setAgentId] = useState("");
48
+ const [confirming, setConfirming] = useState(false);
49
+ const draftKey = useRef(newKey());
50
+
51
+ const channels = useG8Query((signal) => g8Get("/work/channels", undefined, { signal }), []);
52
+ const agents = useG8Query((signal) => g8Get("/work/agents", { limit: 50 }, { signal }), []);
53
+ const rows = listOf<WorkChannel>(channels.data, "channels");
54
+ const roster = listOf<AgentRow>(agents.data, "items");
55
+ const active = channel ?? rows[0]?.id ?? undefined;
56
+
57
+ const feed = useJobPoll<Page>((signal) => g8Get(`/work/conversations/${active}/messages`, { limit: 50 }, { signal }).then((r) => bodyOf<Page>(r)), {
58
+ enabled: Boolean(active), isTerminal: () => false, intervalMs: pollMs, backoff: 1, maxAttempts: 100000,
59
+ });
60
+ useEffect(() => { if (active) feed.start(); }, [active]); // eslint-disable-line react-hooks/exhaustive-deps
61
+
62
+ const post = useG8Mutation(
63
+ // One key per draft: a double submit or a retry of the same draft is the same post, not a second one.
64
+ () => g8Post("/work/messages", { channel: active, message: text.trim(), mentionAgentIds: agentId ? [agentId] : undefined }, { idempotencyKey: draftKey.current }),
65
+ { onSuccess: () => { setText(""); setConfirming(false); draftKey.current = newKey(); feed.start(); }, onError: () => setConfirming(false) },
66
+ );
67
+ const submit = () => post.loading ? undefined : (agentId ? setConfirming(true) : void post.mutate(undefined));
68
+ // After a channel switch the previous channel's page is still in state until the first poll lands: do not show it.
69
+ const stale = Boolean(feed.status?.conversation_id && feed.status.conversation_id !== active);
70
+ const messages = stale ? [] : [...(feed.status?.messages ?? [])].reverse();
71
+ const agentName = roster.find((a) => a.agent_id === agentId)?.name ?? "the agent";
72
+
73
+ return (
74
+ <div className={`flex min-h-0 flex-1 flex-col gap-3 ${className ?? ""}`}>
75
+ {channels.error ? <ApiError error={channels.error} title="Could not load channels" onRetry={() => void channels.refetch()} /> : null}
76
+ <select aria-label="Channel" className="h-9 rounded-md border bg-background px-2 text-sm" value={active ?? ""} onChange={(e) => setChannel(e.target.value)}>
77
+ {rows.map((c) => <option key={c.id} value={c.id}>{c.displayName ?? c.name ?? c.id}</option>)}
78
+ </select>
79
+ {!channels.loading && !channels.error && rows.length === 0 ? <EmptyState title="No channels" description="Work channels this key may read show up here." /> : null}
80
+
81
+ {active ? (
82
+ <>
83
+ {feed.error ? <ApiError error={feed.error} title="Could not load the conversation" onRetry={() => feed.start()} /> : null}
84
+ <ul className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto" data-testid="messages">
85
+ {messages.map((m, i) => (
86
+ <li key={m.id ?? i} className="rounded-md bg-muted px-3 py-2 text-sm">
87
+ <span className="mr-2 text-xs font-medium">{m.author?.displayName ?? m.author?.name ?? "Someone"}</span>
88
+ <span className="whitespace-pre-wrap">{m.text}</span>
89
+ </li>
90
+ ))}
91
+ </ul>
92
+ {!feed.status && !feed.error ? <p className="text-sm text-muted-foreground">Loading...</p> : null}
93
+ {feed.status && !stale && messages.length === 0 ? <p className="text-sm text-muted-foreground">No messages yet.</p> : null}
94
+ {post.error ? <ApiError error={post.error} title="Could not post" /> : null}
95
+ <div className="flex gap-2">
96
+ <select aria-label="Ask an agent" className="h-9 w-40 rounded-md border bg-background px-2 text-sm" value={agentId} onChange={(e) => setAgentId(e.target.value)}>
97
+ <option value="">No agent</option>
98
+ {roster.map((a) => a.agent_id ? <option key={a.agent_id} value={a.agent_id}>{a.name ?? a.agent_id}</option> : null)}
99
+ </select>
100
+ <Input aria-label="Team message" placeholder="Write a message" value={text} onChange={(e) => setText(e.target.value)}
101
+ onKeyDown={(e) => { if (e.key === "Enter" && text.trim() && !post.loading) submit(); }} />
102
+ <Button loading={post.loading} disabled={!text.trim()} onClick={submit}>Post</Button>
103
+ </div>
104
+ </>
105
+ ) : null}
106
+
107
+ <ConfirmSpendDialog open={confirming} tier="billable" title={`Ask ${agentName}`} description="Mentioning an agent starts an agent turn that uses credits. Your message is posted for the whole channel to see."
108
+ confirmLabel="Post and ask" confirming={post.loading} onConfirm={() => void post.mutate(undefined)} onCancel={() => setConfirming(false)} />
109
+ </div>
110
+ );
111
+ }