agent-relay-server 0.80.0 → 0.81.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 (59) hide show
  1. package/docs/openapi.json +1 -1
  2. package/package.json +2 -2
  3. package/public/assets/{MessageBubble-C-3IcqgU.js → MessageBubble-DcSxvVGQ.js} +2 -2
  4. package/public/assets/{MessageBubble-C-3IcqgU.js.map → MessageBubble-DcSxvVGQ.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-o3u36Del.js → PlanGraphPanel-C_Qm7s4q.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-o3u36Del.js.map → PlanGraphPanel-C_Qm7s4q.js.map} +1 -1
  7. package/public/assets/{activity-L3POfhGa.js → activity-HoO4RRm1.js} +2 -2
  8. package/public/assets/{activity-L3POfhGa.js.map → activity-HoO4RRm1.js.map} +1 -1
  9. package/public/assets/{agents-DAdDtQb6.js → agents-CSYMFVpW.js} +2 -2
  10. package/public/assets/{agents-DAdDtQb6.js.map → agents-CSYMFVpW.js.map} +1 -1
  11. package/public/assets/{automation-kakk3xZ9.js → automation-Bj2AYEw0.js} +2 -2
  12. package/public/assets/{automation-kakk3xZ9.js.map → automation-Bj2AYEw0.js.map} +1 -1
  13. package/public/assets/{chat-DDlRjwGC.js → chat-C410UoF1.js} +2 -2
  14. package/public/assets/{chat-DDlRjwGC.js.map → chat-C410UoF1.js.map} +1 -1
  15. package/public/assets/{formatted-body-impl-CEqSWNRL.js → formatted-body-impl-CzKd4psz.js} +2 -2
  16. package/public/assets/{formatted-body-impl-CEqSWNRL.js.map → formatted-body-impl-CzKd4psz.js.map} +1 -1
  17. package/public/assets/{index-CtGaz-Yg.js → index-DW4YIA7L.js} +5 -5
  18. package/public/assets/{index-CtGaz-Yg.js.map → index-DW4YIA7L.js.map} +1 -1
  19. package/public/assets/{insights-mf5sd483.js → insights-DgGTwAwZ.js} +2 -2
  20. package/public/assets/{insights-mf5sd483.js.map → insights-DgGTwAwZ.js.map} +1 -1
  21. package/public/assets/{maintenance-CruMfiA7.js → maintenance-CmgIgjo4.js} +2 -2
  22. package/public/assets/{maintenance-CruMfiA7.js.map → maintenance-CmgIgjo4.js.map} +1 -1
  23. package/public/assets/{managed-agents-fOhHGzNI.js → managed-agents-DRxAGFUB.js} +2 -2
  24. package/public/assets/{managed-agents-fOhHGzNI.js.map → managed-agents-DRxAGFUB.js.map} +1 -1
  25. package/public/assets/{markdown-preview-impl-g5-PTM1A.js → markdown-preview-impl-DSquGj2d.js} +2 -2
  26. package/public/assets/{markdown-preview-impl-g5-PTM1A.js.map → markdown-preview-impl-DSquGj2d.js.map} +1 -1
  27. package/public/assets/{memory-CRyq2WNC.js → memory-BYq4eEF_.js} +2 -2
  28. package/public/assets/{memory-CRyq2WNC.js.map → memory-BYq4eEF_.js.map} +1 -1
  29. package/public/assets/{messages-BqLp0X3y.js → messages-BZt9N6sy.js} +2 -2
  30. package/public/assets/{messages-BqLp0X3y.js.map → messages-BZt9N6sy.js.map} +1 -1
  31. package/public/assets/{orchestrators-BD4nZcG3.js → orchestrators-DoqpuP2u.js} +2 -2
  32. package/public/assets/{orchestrators-BD4nZcG3.js.map → orchestrators-DoqpuP2u.js.map} +1 -1
  33. package/public/assets/{overview-De-2GN06.js → overview-CtpqcW4U.js} +2 -2
  34. package/public/assets/{overview-De-2GN06.js.map → overview-CtpqcW4U.js.map} +1 -1
  35. package/public/assets/{pairs-DOqjBd0R.js → pairs-XoWsTxhe.js} +2 -2
  36. package/public/assets/{pairs-DOqjBd0R.js.map → pairs-XoWsTxhe.js.map} +1 -1
  37. package/public/assets/{security-DtoRSpIi.js → security-B_C5yJi5.js} +2 -2
  38. package/public/assets/{security-DtoRSpIi.js.map → security-B_C5yJi5.js.map} +1 -1
  39. package/public/assets/{select-esP750zQ.js → select-BBZ8pgCn.js} +2 -2
  40. package/public/assets/{select-esP750zQ.js.map → select-BBZ8pgCn.js.map} +1 -1
  41. package/public/assets/{settings-U8788lYt.js → settings-DNYEfnw6.js} +2 -2
  42. package/public/assets/{settings-U8788lYt.js.map → settings-DNYEfnw6.js.map} +1 -1
  43. package/public/assets/{tasks-CtiwQy9I.js → tasks-Bww7kqYc.js} +2 -2
  44. package/public/assets/{tasks-CtiwQy9I.js.map → tasks-Bww7kqYc.js.map} +1 -1
  45. package/public/assets/teams-C6B038l0.js +2 -0
  46. package/public/assets/teams-C6B038l0.js.map +1 -0
  47. package/public/assets/{terminal-viewer-impl-h9hvvB9S.js → terminal-viewer-impl-CvoK2Byo.js} +2 -2
  48. package/public/assets/{terminal-viewer-impl-h9hvvB9S.js.map → terminal-viewer-impl-CvoK2Byo.js.map} +1 -1
  49. package/public/assets/{work-queue-C-bS6YmI.js → work-queue-w_eT-XFN.js} +2 -2
  50. package/public/assets/{work-queue-C-bS6YmI.js.map → work-queue-w_eT-XFN.js.map} +1 -1
  51. package/public/index.html +1 -1
  52. package/src/db/teams.ts +13 -4
  53. package/src/maintenance/teams.ts +1 -7
  54. package/src/routes/teams.ts +11 -4
  55. package/src/services/team.ts +17 -4
  56. package/src/services/transient-agent-reaper.ts +36 -0
  57. package/src/team-idle-tracker.ts +13 -0
  58. package/public/assets/teams-Cx29VUif.js +0 -2
  59. package/public/assets/teams-Cx29VUif.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{Hn as e,i as t,lt as n,sn as r,vn as i}from"./lucide-react-CNGB8fo1.js";import{l as a,t as o}from"./store-Cjcd0egy.js";import{U as s,d as c,ut as l}from"./display-DHgdn4vN.js";import{t as u}from"./badge-DGB2nVM0.js";import{t as d}from"./button-D_Y8CEP4.js";import{B as f,K as p,s as m}from"./index-CtGaz-Yg.js";import{r as h,t as g}from"./card-DTLNgk0R.js";var _=e(),v={open:`bg-blue-500/10 text-blue-400 border-blue-500/20`,blocked:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,claimed:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,in_progress:`bg-purple-500/10 text-purple-400 border-purple-500/20`};function y(){let e=a(),t=o(e=>e.agentsById),r=o(e=>e.compose),i=o(e=>e.set),s=o(e=>e.doClaimTask),l=o(e=>e.doClaim),d=o(e=>e.openTaskEvents),h=o(e=>e.showError),g=o(e=>e.openConfirm),v=o(e=>e.doDeleteMessage),y=o(e=>e.doUpdateTaskStatus),x=f(),S=p(),C=S.filter(e=>e.claimable).length;function w(e){if(!r.from){h(`Validation`,`Select a "Claim as" agent first.`);return}e.sourceType===`task`&&e.task?s(e.task.id):e.sourceType===`message`&&e.message&&l(e.message.id)}function T(e){g(`Cancel Item`,`Cancel "${e.title||(e.sourceType===`task`?`Task #${e.id}`:`Message #${e.id}`)}"? This cannot be undone.`,()=>{e.sourceType===`task`&&e.task?y(e.task,`canceled`):e.sourceType===`message`&&e.message&&v(e.message.id)})}function E(e){e.task&&d(e.task)}return(0,_.jsxs)(`div`,{className:`space-y-4`,children:[(0,_.jsxs)(`div`,{className:`flex items-center justify-between flex-wrap gap-2`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(n,{className:`w-5 h-5 text-muted-foreground`}),(0,_.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Work Queue`}),C>0&&(0,_.jsxs)(u,{className:`bg-orange-500/20 text-orange-400 border-orange-500/30 border`,children:[C,` claimable`]})]}),(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(`label`,{className:`text-xs text-muted-foreground shrink-0`,children:`Claim as`}),(0,_.jsxs)(`select`,{className:`rounded-md border border-input bg-background px-3 py-1.5 text-sm`,value:r.from,onChange:e=>i({compose:{...r,from:e.target.value}}),children:[(0,_.jsx)(`option`,{value:``,children:`Select agent...`}),x.map(e=>(0,_.jsx)(`option`,{value:e.id,children:c(e)},e.id))]})]})]}),(0,_.jsx)(m,{className:`h-[calc(100dvh-11rem)]`,children:(0,_.jsxs)(`div`,{className:`space-y-2 pr-2`,children:[S.length===0&&(0,_.jsx)(`div`,{className:`text-center text-muted-foreground py-16 text-sm`,children:`Work queue is empty`}),S.map(n=>(0,_.jsx)(b,{item:n,now:e,agentName:e=>t[e]?c(t[e]):e.slice(-10),onClaim:()=>w(n),onCancel:()=>T(n),onEvents:n.task?()=>E(n):void 0},n.id))]})})]})}function b({item:e,now:n,agentName:a,onClaim:o,onCancel:c,onEvents:f}){let p=l[e.severity]||l.info,m=v[e.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:e.claimable?`ring-1 ring-orange-500/30`:``,children:(0,_.jsx)(h,{className:`p-3`,children:(0,_.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,_.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap mb-1`,children:[(0,_.jsx)(`span`,{className:`font-medium text-sm truncate`,children:e.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:e.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:e.status}),e.sourceType===`message`&&(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400`,children:`msg`})]}),e.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:e.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[e.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(r,{className:`w-3 h-3 inline mr-1`}),a(e.owner)]}),e.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:e.source}),(0,_.jsxs)(`span`,{title:String(e.updatedAt),children:[(0,_.jsx)(i,{className:`w-3 h-3 inline mr-1`}),s(n,e.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[e.claimable&&(0,_.jsxs)(d,{size:`sm`,className:`h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white`,onClick:o,children:[(0,_.jsx)(r,{className:`w-3.5 h-3.5 mr-1`}),` Claim`]}),f&&(0,_.jsx)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs`,onClick:f,children:`Events`}),(0,_.jsxs)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20`,onClick:c,children:[(0,_.jsx)(t,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
2
- //# sourceMappingURL=work-queue-C-bS6YmI.js.map
1
+ import{Hn as e,i as t,lt as n,sn as r,vn as i}from"./lucide-react-CNGB8fo1.js";import{l as a,t as o}from"./store-Cjcd0egy.js";import{U as s,d as c,ut as l}from"./display-DHgdn4vN.js";import{t as u}from"./badge-DGB2nVM0.js";import{t as d}from"./button-D_Y8CEP4.js";import{B as f,K as p,s as m}from"./index-DW4YIA7L.js";import{r as h,t as g}from"./card-DTLNgk0R.js";var _=e(),v={open:`bg-blue-500/10 text-blue-400 border-blue-500/20`,blocked:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,claimed:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,in_progress:`bg-purple-500/10 text-purple-400 border-purple-500/20`};function y(){let e=a(),t=o(e=>e.agentsById),r=o(e=>e.compose),i=o(e=>e.set),s=o(e=>e.doClaimTask),l=o(e=>e.doClaim),d=o(e=>e.openTaskEvents),h=o(e=>e.showError),g=o(e=>e.openConfirm),v=o(e=>e.doDeleteMessage),y=o(e=>e.doUpdateTaskStatus),x=f(),S=p(),C=S.filter(e=>e.claimable).length;function w(e){if(!r.from){h(`Validation`,`Select a "Claim as" agent first.`);return}e.sourceType===`task`&&e.task?s(e.task.id):e.sourceType===`message`&&e.message&&l(e.message.id)}function T(e){g(`Cancel Item`,`Cancel "${e.title||(e.sourceType===`task`?`Task #${e.id}`:`Message #${e.id}`)}"? This cannot be undone.`,()=>{e.sourceType===`task`&&e.task?y(e.task,`canceled`):e.sourceType===`message`&&e.message&&v(e.message.id)})}function E(e){e.task&&d(e.task)}return(0,_.jsxs)(`div`,{className:`space-y-4`,children:[(0,_.jsxs)(`div`,{className:`flex items-center justify-between flex-wrap gap-2`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(n,{className:`w-5 h-5 text-muted-foreground`}),(0,_.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Work Queue`}),C>0&&(0,_.jsxs)(u,{className:`bg-orange-500/20 text-orange-400 border-orange-500/30 border`,children:[C,` claimable`]})]}),(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(`label`,{className:`text-xs text-muted-foreground shrink-0`,children:`Claim as`}),(0,_.jsxs)(`select`,{className:`rounded-md border border-input bg-background px-3 py-1.5 text-sm`,value:r.from,onChange:e=>i({compose:{...r,from:e.target.value}}),children:[(0,_.jsx)(`option`,{value:``,children:`Select agent...`}),x.map(e=>(0,_.jsx)(`option`,{value:e.id,children:c(e)},e.id))]})]})]}),(0,_.jsx)(m,{className:`h-[calc(100dvh-11rem)]`,children:(0,_.jsxs)(`div`,{className:`space-y-2 pr-2`,children:[S.length===0&&(0,_.jsx)(`div`,{className:`text-center text-muted-foreground py-16 text-sm`,children:`Work queue is empty`}),S.map(n=>(0,_.jsx)(b,{item:n,now:e,agentName:e=>t[e]?c(t[e]):e.slice(-10),onClaim:()=>w(n),onCancel:()=>T(n),onEvents:n.task?()=>E(n):void 0},n.id))]})})]})}function b({item:e,now:n,agentName:a,onClaim:o,onCancel:c,onEvents:f}){let p=l[e.severity]||l.info,m=v[e.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:e.claimable?`ring-1 ring-orange-500/30`:``,children:(0,_.jsx)(h,{className:`p-3`,children:(0,_.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,_.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap mb-1`,children:[(0,_.jsx)(`span`,{className:`font-medium text-sm truncate`,children:e.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:e.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:e.status}),e.sourceType===`message`&&(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400`,children:`msg`})]}),e.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:e.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[e.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(r,{className:`w-3 h-3 inline mr-1`}),a(e.owner)]}),e.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:e.source}),(0,_.jsxs)(`span`,{title:String(e.updatedAt),children:[(0,_.jsx)(i,{className:`w-3 h-3 inline mr-1`}),s(n,e.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[e.claimable&&(0,_.jsxs)(d,{size:`sm`,className:`h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white`,onClick:o,children:[(0,_.jsx)(r,{className:`w-3.5 h-3.5 mr-1`}),` Claim`]}),f&&(0,_.jsx)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs`,onClick:f,children:`Events`}),(0,_.jsxs)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20`,onClick:c,children:[(0,_.jsx)(t,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
2
+ //# sourceMappingURL=work-queue-w_eT-XFN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"work-queue-C-bS6YmI.js","names":[],"sources":["../../dashboard/src/components/views/work-queue.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { useWorkQueueItems, useComposeAgents } from '@/hooks/use-selectors'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { ListChecks, CheckCircle2, Calendar, X } from 'lucide-react'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { SEVERITY_COLORS } from '@/lib/constants'\nimport type { WorkQueueItem } from '@/types'\n\nconst STATUS_COLORS: Record<string, string> = {\n open: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n blocked: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n claimed: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n in_progress: 'bg-purple-500/10 text-purple-400 border-purple-500/20',\n}\n\nexport function WorkQueueView() {\n const now = useNow()\n const agentsById = useRelayStore((s) => s.agentsById)\n const compose = useRelayStore((s) => s.compose)\n const set = useRelayStore((s) => s.set)\n const doClaimTask = useRelayStore((s) => s.doClaimTask)\n const doClaim = useRelayStore((s) => s.doClaim)\n const openTaskEvents = useRelayStore((s) => s.openTaskEvents)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const doDeleteMessage = useRelayStore((s) => s.doDeleteMessage)\n const doUpdateTaskStatus = useRelayStore((s) => s.doUpdateTaskStatus)\n\n const composeAgents = useComposeAgents()\n const items = useWorkQueueItems()\n\n const claimableCount = items.filter((i) => i.claimable).length\n\n function handleClaim(item: WorkQueueItem) {\n if (!compose.from) { showError('Validation', 'Select a \"Claim as\" agent first.'); return }\n if (item.sourceType === 'task' && item.task) doClaimTask(item.task.id)\n else if (item.sourceType === 'message' && item.message) doClaim(item.message.id)\n }\n\n function handleCancel(item: WorkQueueItem) {\n const label = item.title || (item.sourceType === 'task' ? `Task #${item.id}` : `Message #${item.id}`)\n openConfirm('Cancel Item', `Cancel \"${label}\"? This cannot be undone.`, () => {\n if (item.sourceType === 'task' && item.task) doUpdateTaskStatus(item.task, 'canceled')\n else if (item.sourceType === 'message' && item.message) doDeleteMessage(item.message.id)\n })\n }\n\n function handleEvents(item: WorkQueueItem) {\n if (item.task) openTaskEvents(item.task)\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <ListChecks className=\"w-5 h-5 text-muted-foreground\" />\n <h2 className=\"text-lg font-semibold\">Work Queue</h2>\n {claimableCount > 0 && (\n <Badge className=\"bg-orange-500/20 text-orange-400 border-orange-500/30 border\">\n {claimableCount} claimable\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-muted-foreground shrink-0\">Claim as</label>\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={compose.from}\n onChange={(e) => set({ compose: { ...compose, from: e.target.value } })}\n >\n <option value=\"\">Select agent...</option>\n {composeAgents.map((a) => <option key={a.id} value={a.id}>{displayName(a)}</option>)}\n </select>\n </div>\n </div>\n\n <ScrollArea className=\"h-[calc(100dvh-11rem)]\">\n <div className=\"space-y-2 pr-2\">\n {items.length === 0 && (\n <div className=\"text-center text-muted-foreground py-16 text-sm\">Work queue is empty</div>\n )}\n {items.map((item) => (\n <WorkQueueCard\n key={item.id}\n item={item}\n now={now}\n agentName={(id) => agentsById[id] ? displayName(agentsById[id]) : id.slice(-10)}\n onClaim={() => handleClaim(item)}\n onCancel={() => handleCancel(item)}\n onEvents={item.task ? () => handleEvents(item) : undefined}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n )\n}\n\nfunction WorkQueueCard({\n item, now, agentName, onClaim, onCancel, onEvents,\n}: {\n item: WorkQueueItem\n now: number\n agentName: (id: string) => string\n onClaim: () => void\n onCancel: () => void\n onEvents?: () => void\n}) {\n const severityClass = SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info\n const statusClass = STATUS_COLORS[item.status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n\n return (\n <Card className={item.claimable ? 'ring-1 ring-orange-500/30' : ''}>\n <CardContent className=\"p-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap mb-1\">\n <span className=\"font-medium text-sm truncate\">{item.title}</span>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${severityClass}`}>{item.severity}</Badge>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${statusClass}`}>{item.status}</Badge>\n {item.sourceType === 'message' && (\n <Badge variant=\"outline\" className=\"text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400\">msg</Badge>\n )}\n </div>\n {item.body && (\n <p className=\"text-xs text-muted-foreground line-clamp-2 mb-2\">{item.body}</p>\n )}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground flex-wrap\">\n {item.owner && (\n <span title=\"Owner\">\n <CheckCircle2 className=\"w-3 h-3 inline mr-1\" />{agentName(item.owner)}\n </span>\n )}\n {item.source && (\n <span className=\"font-mono\">{item.source}</span>\n )}\n <span title={String(item.updatedAt)}>\n <Calendar className=\"w-3 h-3 inline mr-1\" />{timeAgo(now, item.updatedAt)}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col gap-1 shrink-0\">\n {item.claimable && (\n <Button size=\"sm\" className=\"h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white\" onClick={onClaim}>\n <CheckCircle2 className=\"w-3.5 h-3.5 mr-1\" /> Claim\n </Button>\n )}\n {onEvents && (\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs\" onClick={onEvents}>\n Events\n </Button>\n )}\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20\" onClick={onCancel}>\n <X className=\"w-3.5 h-3.5 mr-1\" /> Cancel\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"work-queue-w_eT-XFN.js","names":[],"sources":["../../dashboard/src/components/views/work-queue.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { useWorkQueueItems, useComposeAgents } from '@/hooks/use-selectors'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { ListChecks, CheckCircle2, Calendar, X } from 'lucide-react'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { SEVERITY_COLORS } from '@/lib/constants'\nimport type { WorkQueueItem } from '@/types'\n\nconst STATUS_COLORS: Record<string, string> = {\n open: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n blocked: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n claimed: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n in_progress: 'bg-purple-500/10 text-purple-400 border-purple-500/20',\n}\n\nexport function WorkQueueView() {\n const now = useNow()\n const agentsById = useRelayStore((s) => s.agentsById)\n const compose = useRelayStore((s) => s.compose)\n const set = useRelayStore((s) => s.set)\n const doClaimTask = useRelayStore((s) => s.doClaimTask)\n const doClaim = useRelayStore((s) => s.doClaim)\n const openTaskEvents = useRelayStore((s) => s.openTaskEvents)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const doDeleteMessage = useRelayStore((s) => s.doDeleteMessage)\n const doUpdateTaskStatus = useRelayStore((s) => s.doUpdateTaskStatus)\n\n const composeAgents = useComposeAgents()\n const items = useWorkQueueItems()\n\n const claimableCount = items.filter((i) => i.claimable).length\n\n function handleClaim(item: WorkQueueItem) {\n if (!compose.from) { showError('Validation', 'Select a \"Claim as\" agent first.'); return }\n if (item.sourceType === 'task' && item.task) doClaimTask(item.task.id)\n else if (item.sourceType === 'message' && item.message) doClaim(item.message.id)\n }\n\n function handleCancel(item: WorkQueueItem) {\n const label = item.title || (item.sourceType === 'task' ? `Task #${item.id}` : `Message #${item.id}`)\n openConfirm('Cancel Item', `Cancel \"${label}\"? This cannot be undone.`, () => {\n if (item.sourceType === 'task' && item.task) doUpdateTaskStatus(item.task, 'canceled')\n else if (item.sourceType === 'message' && item.message) doDeleteMessage(item.message.id)\n })\n }\n\n function handleEvents(item: WorkQueueItem) {\n if (item.task) openTaskEvents(item.task)\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <ListChecks className=\"w-5 h-5 text-muted-foreground\" />\n <h2 className=\"text-lg font-semibold\">Work Queue</h2>\n {claimableCount > 0 && (\n <Badge className=\"bg-orange-500/20 text-orange-400 border-orange-500/30 border\">\n {claimableCount} claimable\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-muted-foreground shrink-0\">Claim as</label>\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={compose.from}\n onChange={(e) => set({ compose: { ...compose, from: e.target.value } })}\n >\n <option value=\"\">Select agent...</option>\n {composeAgents.map((a) => <option key={a.id} value={a.id}>{displayName(a)}</option>)}\n </select>\n </div>\n </div>\n\n <ScrollArea className=\"h-[calc(100dvh-11rem)]\">\n <div className=\"space-y-2 pr-2\">\n {items.length === 0 && (\n <div className=\"text-center text-muted-foreground py-16 text-sm\">Work queue is empty</div>\n )}\n {items.map((item) => (\n <WorkQueueCard\n key={item.id}\n item={item}\n now={now}\n agentName={(id) => agentsById[id] ? displayName(agentsById[id]) : id.slice(-10)}\n onClaim={() => handleClaim(item)}\n onCancel={() => handleCancel(item)}\n onEvents={item.task ? () => handleEvents(item) : undefined}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n )\n}\n\nfunction WorkQueueCard({\n item, now, agentName, onClaim, onCancel, onEvents,\n}: {\n item: WorkQueueItem\n now: number\n agentName: (id: string) => string\n onClaim: () => void\n onCancel: () => void\n onEvents?: () => void\n}) {\n const severityClass = SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info\n const statusClass = STATUS_COLORS[item.status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n\n return (\n <Card className={item.claimable ? 'ring-1 ring-orange-500/30' : ''}>\n <CardContent className=\"p-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap mb-1\">\n <span className=\"font-medium text-sm truncate\">{item.title}</span>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${severityClass}`}>{item.severity}</Badge>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${statusClass}`}>{item.status}</Badge>\n {item.sourceType === 'message' && (\n <Badge variant=\"outline\" className=\"text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400\">msg</Badge>\n )}\n </div>\n {item.body && (\n <p className=\"text-xs text-muted-foreground line-clamp-2 mb-2\">{item.body}</p>\n )}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground flex-wrap\">\n {item.owner && (\n <span title=\"Owner\">\n <CheckCircle2 className=\"w-3 h-3 inline mr-1\" />{agentName(item.owner)}\n </span>\n )}\n {item.source && (\n <span className=\"font-mono\">{item.source}</span>\n )}\n <span title={String(item.updatedAt)}>\n <Calendar className=\"w-3 h-3 inline mr-1\" />{timeAgo(now, item.updatedAt)}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col gap-1 shrink-0\">\n {item.claimable && (\n <Button size=\"sm\" className=\"h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white\" onClick={onClaim}>\n <CheckCircle2 className=\"w-3.5 h-3.5 mr-1\" /> Claim\n </Button>\n )}\n {onEvents && (\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs\" onClick={onEvents}>\n Events\n </Button>\n )}\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20\" onClick={onCancel}>\n <X className=\"w-3.5 h-3.5 mr-1\" /> Cancel\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n"],"mappings":"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"}
package/public/index.html CHANGED
@@ -12,7 +12,7 @@
12
12
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%2309090b'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='%2358a6ff'/%3E%3Ccircle cx='6' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='6' cy='24' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='24' r='2.5' fill='%233fb950'/%3E%3Cline x1='8' y1='9.5' x2='13' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='9.5' x2='19' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='8' y1='22.5' x2='13' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='22.5' x2='19' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3C/svg%3E">
13
13
  <link rel="manifest" href="manifest.webmanifest">
14
14
  <link rel="apple-touch-icon" href="icons/agent-relay-192.png">
15
- <script type="module" crossorigin src="./assets/index-CtGaz-Yg.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-DW4YIA7L.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/chunk-CilyBKbf.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-CP_edrrL.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/lucide-react-CNGB8fo1.js">
package/src/db/teams.ts CHANGED
@@ -131,11 +131,20 @@ export function getTeamRoster(teamId: string): AgentCard[] {
131
131
  return rows.map(rowToAgent);
132
132
  }
133
133
 
134
- export function countLiveTeamMembers(teamId: string, cutoff: number): number {
134
+ export interface TeamMemberStats {
135
+ total: number;
136
+ live: number;
137
+ }
138
+
139
+ export function getTeamMemberStats(teamId: string, cutoff: number): TeamMemberStats {
135
140
  const row = getDb().query(
136
- "SELECT count(*) AS n FROM agents WHERE team_id = ? AND status NOT IN ('offline') AND last_seen > ?",
137
- ).get(teamId, cutoff) as { n: number };
138
- return row.n;
141
+ "SELECT count(*) AS total, count(CASE WHEN status != 'offline' AND last_seen > ? THEN 1 END) AS live FROM agents WHERE team_id = ?",
142
+ ).get(cutoff, teamId) as { total: number; live: number };
143
+ return { total: row.total, live: row.live };
144
+ }
145
+
146
+ export function countLiveTeamMembers(teamId: string, cutoff: number): number {
147
+ return getTeamMemberStats(teamId, cutoff).live;
139
148
  }
140
149
 
141
150
  export function getTeamBusyState(teamId: string, cutoff: number): TeamBusyState {
@@ -13,13 +13,7 @@ import { dissolveTeamById } from "../services/team";
13
13
  import { getTeamsConfig } from "../teams-config-store";
14
14
  import { teamReapGraceMs } from "./constants";
15
15
  import { notifyTarget } from "./events";
16
-
17
- interface TeamIdleTrackerEntry {
18
- firstNoBusyAt: number;
19
- pinged: boolean;
20
- }
21
-
22
- const teamIdleTracker = new Map<string, TeamIdleTrackerEntry>();
16
+ import { teamIdleTracker } from "../team-idle-tracker";
23
17
 
24
18
  export function resetTeamIdleWatchdogForTests(): void {
25
19
  teamIdleTracker.clear();
@@ -1,9 +1,10 @@
1
1
  import { McpAuthError, McpNotFoundError } from "../mcp-errors";
2
2
  import { authContextFromRequest } from "../services/auth-context";
3
- import { getTeam, getTeamOutcomeAggregates, listAllTeams, ValidationError } from "../db";
4
- import { dissolveCallerTeam, dissolveTeamById, getCallerTeam, setCallerTeamBrief, TeamAuthError, TeamNotFoundError, type TeamDissolveServiceInput } from "../services/team";
3
+ import { getTeam, getTeamOutcomeAggregates, getPlanByTeam, listAllTeams, ValidationError } from "../db";
4
+ import { dissolveCallerTeam, dissolveTeamById, deriveTeamRuntimeState, getCallerTeam, setCallerTeamBrief, TeamAuthError, TeamNotFoundError, type TeamDissolveServiceInput } from "../services/team";
5
5
  import { cleanString, optionalEnum } from "../validation";
6
6
  import { authorizeRoute, error, json, parseBody, parseQueryInt, type Handler } from "./_shared";
7
+ import { STALE_TTL_MS } from "../config";
7
8
  import type { Team } from "../types";
8
9
 
9
10
  const OUTCOME_STATUSES = ["success", "partial", "failed"] as const;
@@ -43,8 +44,14 @@ export const getTeamsRoute: Handler = (req) => {
43
44
  if (denied) return denied;
44
45
  try {
45
46
  const status = optionalEnum(new URL(req.url).searchParams.get("status") ?? undefined, "status", TEAM_STATUSES) as Team["status"] | undefined;
46
- const teams = listAllTeams();
47
- return json({ teams: status ? teams.filter((team) => team.status === status) : teams });
47
+ const cutoff = Date.now() - STALE_TTL_MS;
48
+ const all = listAllTeams();
49
+ const filtered = status ? all.filter((team) => team.status === status) : all;
50
+ const teams = filtered.map((team) => {
51
+ const plan = team.status === "active" ? getPlanByTeam(team.id) ?? null : null;
52
+ return { ...team, runtimeState: deriveTeamRuntimeState(team, plan, cutoff) };
53
+ });
54
+ return json({ teams });
48
55
  } catch (err) {
49
56
  return routeError(err);
50
57
  }
@@ -1,10 +1,11 @@
1
1
  import { hasScope } from "agent-relay-sdk/types";
2
2
  import { STALE_TTL_MS } from "../config";
3
- import { claimTeam, countLiveTeamMembers, dissolveTeam, ensureTeam, getAgent, getPlanByTeam, getTeam, getTeamRoster, setTeamBrief } from "../db";
3
+ import { claimTeam, countLiveTeamMembers, dissolveTeam, ensureTeam, getAgent, getPlanByTeam, getTeam, getTeamBusyState, getTeamMemberStats, getTeamRoster, setTeamBrief } from "../db";
4
4
  import { emitRelayEvent } from "../events";
5
5
  import { listIssueRefsForEntity } from "./issue-associations";
6
+ import { getTeamIdleEntry } from "../team-idle-tracker";
6
7
  import type { AuthContext } from "./auth-context";
7
- import type { Team } from "../types";
8
+ import type { Plan, Team, TeamRuntimeState } from "../types";
8
9
 
9
10
  export class TeamAuthError extends Error {}
10
11
  export class TeamNotFoundError extends Error {}
@@ -30,6 +31,16 @@ export interface TeamDissolveServiceInput {
30
31
  landsClean?: boolean;
31
32
  }
32
33
 
34
+ export function deriveTeamRuntimeState(team: Team, plan: Plan | null, cutoff: number): TeamRuntimeState {
35
+ if (team.status === "dissolved") return "dissolved";
36
+ const busy = getTeamBusyState(team.id, cutoff);
37
+ if (busy.busyMemberIds.length > 0) return "busy";
38
+ if (getTeamIdleEntry(team.id)?.pinged) return "stalled";
39
+ const stats = getTeamMemberStats(team.id, cutoff);
40
+ if (stats.total > stats.live && plan && plan.nodes.some((n) => n.status !== "landed")) return "degraded";
41
+ return "idle";
42
+ }
43
+
33
44
  function requireCallerAgentId(ctx: AuthContext): string {
34
45
  if (!ctx.callerAgentId) throw new TeamAuthError("team operations require a single caller agent identity");
35
46
  return ctx.callerAgentId;
@@ -39,12 +50,14 @@ function payloadForAgent(agentId: string, channel?: string): TeamPayload {
39
50
  const agent = getAgent(agentId);
40
51
  const team = agent?.teamId ? getTeam(agent.teamId) : null;
41
52
  if (!team) throw new TeamNotFoundError("caller has no team");
53
+ const plan = getPlanByTeam(team.id, channel) ?? null;
54
+ const runtimeState = deriveTeamRuntimeState(team, plan, Date.now() - STALE_TTL_MS);
42
55
  return {
43
- team,
56
+ team: { ...team, runtimeState },
44
57
  roster: getTeamRoster(team.id),
45
58
  brief: team.brief ?? null,
46
59
  definitionOfDone: team.definitionOfDone ?? null,
47
- plan: getPlanByTeam(team.id, channel) ?? null,
60
+ plan,
48
61
  issues: listIssueRefsForEntity("team", team.id),
49
62
  };
50
63
  }
@@ -4,6 +4,7 @@ import { createActivityEvent, getAgent, listAgents, listWorkspaces } from "../db
4
4
  import { routeNotification } from "../notification-router";
5
5
  import { isProviderBlocked } from "../provider-state";
6
6
  import { TERMINAL_WORKSPACE_STATUSES } from "../workspace-phase";
7
+ import type { WorkspaceRecord } from "../types";
7
8
 
8
9
  const envMsOrDefault = (name: string, fallback: number): number => {
9
10
  const v = Number(process.env[name]);
@@ -17,6 +18,34 @@ export function resetTransientTrackerForTests(): void {
17
18
  transientTracker.clear();
18
19
  }
19
20
 
21
+ // How many commits the agent's branch has ahead of its base. Returns null when
22
+ // the check can't run (missing fields, git failure) — callers treat null as
23
+ // "unknown" and preserve the safe #614 hold-pending bias: only 0 is fast-reaped.
24
+ function defaultCommitsAhead(ws: WorkspaceRecord): number | null {
25
+ const base = ws.baseSha ?? ws.baseRef;
26
+ if (!base || !ws.branch || !ws.worktreePath) return null;
27
+ try {
28
+ const proc = Bun.spawnSync(
29
+ ["git", "-C", ws.worktreePath, "rev-list", "--count", `${base}..${ws.branch}`],
30
+ { stdin: "ignore", stdout: "pipe", stderr: "ignore" },
31
+ );
32
+ if (proc.exitCode !== 0) return null;
33
+ const n = parseInt(Buffer.from(proc.stdout).toString("utf8").trim(), 10);
34
+ return Number.isFinite(n) ? n : null;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ let _commitsAheadFn: (ws: WorkspaceRecord) => number | null = defaultCommitsAhead;
41
+
42
+ export function overrideCommitsAheadForTests(fn: (ws: WorkspaceRecord) => number | null): void {
43
+ _commitsAheadFn = fn;
44
+ }
45
+ export function resetCommitsAheadForTests(): void {
46
+ _commitsAheadFn = defaultCommitsAhead;
47
+ }
48
+
20
49
  function transientLandComplete(agentId: string): { complete: boolean; workspaceId?: string; status?: string; reason?: string } {
21
50
  const isolated = listWorkspaces({ ownerAgentId: agentId }).filter((ws) => ws.mode === "isolated");
22
51
  if (!isolated.length) return { complete: true, reason: "no-isolated-workspace" };
@@ -27,6 +56,13 @@ function transientLandComplete(agentId: string): { complete: boolean; workspaceI
27
56
  if (live.status === "active" && mergedAt !== undefined && mergedAt > (landReportedAt ?? 0)) {
28
57
  return { complete: true, workspaceId: live.id, status: live.status, reason: "recycled-after-land" };
29
58
  }
59
+ // Fast-path (#629): a zero-commit branch has nothing to preserve — reap immediately
60
+ // instead of holding it pending a land that will never happen. Only fires when the
61
+ // branch is provably empty (count === 0); null (unknown/git error) falls through to
62
+ // the safe #614 hold-pending bias below.
63
+ if (live.status === "active" && _commitsAheadFn(live) === 0) {
64
+ return { complete: true, workspaceId: live.id, status: live.status, reason: "empty-branch" };
65
+ }
30
66
  return { complete: false, workspaceId: live.id, status: live.status, reason: "land-pending" };
31
67
  }
32
68
 
@@ -0,0 +1,13 @@
1
+ // Singleton in-memory tracker shared by maintenance/teams.ts (writer) and
2
+ // services/team.ts (reader). Lives in its own module to avoid a circular dep:
3
+ // maintenance/teams.ts → services/team (dissolveTeamById) → team-idle-tracker ← maintenance/teams.ts
4
+ export interface TeamIdleEntry {
5
+ firstNoBusyAt: number;
6
+ pinged: boolean;
7
+ }
8
+
9
+ export const teamIdleTracker = new Map<string, TeamIdleEntry>();
10
+
11
+ export function getTeamIdleEntry(teamId: string): TeamIdleEntry | undefined {
12
+ return teamIdleTracker.get(teamId);
13
+ }
@@ -1,2 +0,0 @@
1
- import{r as e}from"./chunk-CilyBKbf.js";import{Bn as t,Hn as n,N as r,Un as i,Z as a,er as o,gn as s,l as c,m as ee}from"./lucide-react-CNGB8fo1.js";import{i as te,n as ne,t as l}from"./store-Cjcd0egy.js";import{H as re,U as u,X as d,d as f,h as p}from"./display-DHgdn4vN.js";import{a as ie,i as ae,n as oe,r as se,t as ce}from"./select-esP750zQ.js";import{t as m}from"./badge-DGB2nVM0.js";import{t as h}from"./button-D_Y8CEP4.js";import{t as le}from"./input-C5FrEDmc.js";import{f as ue,s as g}from"./index-CtGaz-Yg.js";import{a as _,o as v,r as y,t as b}from"./card-DTLNgk0R.js";import{t as de}from"./PlanGraphPanel-o3u36Del.js";var x=e(o(),1),S=n();function C(e){if(e==null)return`-`;let t=Math.round(e/1e3);if(t<60)return`${t}s`;let n=Math.round(t/60);if(n<60)return`${n}m`;let r=Math.round(n/60);return r<48?`${r}h`:`${Math.round(r/24)}d`}function w(e){return`${Math.round(e*100)}%`}function T(e){return e===`success`?`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`:e===`partial`?`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`:e===`failed`?`bg-red-500/10 text-red-400 border-red-500/20`:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`}function fe({data:e,loading:t,error:n}){let r=e?.totals.byOutcomeStatus,i=e?.compositionShapes.slice(0,6)??[],a=e?.recent.slice(0,8)??[];return(0,S.jsxs)(b,{children:[(0,S.jsx)(_,{children:(0,S.jsxs)(v,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,S.jsx)(s,{className:`size-4 text-primary`}),` Outcomes`]}),(0,S.jsxs)(m,{variant:`secondary`,children:[e?.totals.total??0,` dissolved`]})]})}),(0,S.jsxs)(y,{className:`space-y-5`,children:[n?(0,S.jsx)(`div`,{className:`rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:n}):null,t&&!e?(0,S.jsx)(`div`,{className:`py-8 text-center text-sm text-muted-foreground`,children:`Loading outcomes…`}):!e||e.totals.total===0?(0,S.jsx)(`div`,{className:`py-8 text-center text-sm text-muted-foreground`,children:`No dissolved teams yet`}):(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`div`,{className:`flex flex-wrap gap-2 text-xs`,children:[`success`,`partial`,`failed`,`unknown`].map(e=>{let t=r?.[e]??0;return(0,S.jsxs)(m,{variant:`outline`,className:t===0?`text-muted-foreground`:T(e===`unknown`?null:e),children:[t,` `,e]},e)})}),(0,S.jsxs)(`section`,{className:`space-y-2`,children:[(0,S.jsx)(`div`,{className:`text-sm font-medium`,children:`Composition win-rate`}),(0,S.jsx)(`div`,{className:`divide-y divide-border rounded-md border border-border`,children:i.map(e=>(0,S.jsxs)(`div`,{className:`grid gap-2 px-3 py-2 text-sm md:grid-cols-[1fr_auto_auto] md:items-center`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`div`,{className:`break-words font-medium`,title:e.shape,children:e.shape}),(0,S.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[e.count,` team`,e.count===1?``:`s`,` · avg `,C(e.avgWallClockMs)]})]}),(0,S.jsxs)(`span`,{className:`text-muted-foreground`,children:[e.successCount,`/`,e.count]}),(0,S.jsx)(m,{variant:`outline`,className:T(e.successRate>=.5?`success`:e.successRate>0?`partial`:`failed`),children:w(e.successRate)})]},e.shape))})]}),(0,S.jsxs)(`section`,{className:`space-y-2`,children:[(0,S.jsx)(`div`,{className:`text-sm font-medium`,children:`Recent outcomes`}),(0,S.jsx)(`div`,{className:`overflow-hidden rounded-md border border-border`,children:(0,S.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,S.jsx)(`thead`,{className:`bg-muted/40 text-xs text-muted-foreground`,children:(0,S.jsxs)(`tr`,{children:[(0,S.jsx)(`th`,{className:`px-3 py-2 text-left font-medium`,children:`Team`}),(0,S.jsx)(`th`,{className:`px-3 py-2 text-left font-medium`,children:`Outcome`}),(0,S.jsx)(`th`,{className:`px-3 py-2 text-right font-medium`,children:`Members`}),(0,S.jsx)(`th`,{className:`px-3 py-2 text-right font-medium`,children:`Duration`})]})}),(0,S.jsx)(`tbody`,{className:`divide-y divide-border`,children:a.map(e=>(0,S.jsxs)(`tr`,{children:[(0,S.jsxs)(`td`,{className:`max-w-[220px] px-3 py-2`,children:[(0,S.jsx)(`div`,{className:`truncate font-medium`,children:e.title||e.id.slice(0,12)}),(0,S.jsx)(`div`,{className:`truncate text-xs text-muted-foreground`,children:e.id})]}),(0,S.jsx)(`td`,{className:`px-3 py-2`,children:(0,S.jsx)(m,{variant:`outline`,className:T(e.outcome),children:e.outcome??`unknown`})}),(0,S.jsx)(`td`,{className:`px-3 py-2 text-right`,children:e.memberCount}),(0,S.jsx)(`td`,{className:`px-3 py-2 text-right text-muted-foreground`,children:C(e.durationMs)})]},e.id))})]})})]})]})]})]})}var pe=[],me=[{value:`all`,label:`All kinds`},{value:`decision`,label:`Decisions`},{value:`fact`,label:`Facts`},{value:`ruled_out`,label:`Ruled out`}],he={decision:`decision`,fact:`fact`,ruled_out:`ruled out`};function ge(e){return e.status===`dissolved`?`dissolved`:e.zeroMembersSince==null?`active`:`orphaned`}var _e={active:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,orphaned:`bg-amber-500/10 text-amber-400 border-amber-500/20`,dissolved:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`},ve={success:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,partial:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,failed:`bg-red-500/10 text-red-400 border-red-500/20`},ye={active:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,superseded:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,retracted:`bg-red-500/10 text-red-400 border-red-500/20`};function E(e){return e?e.slice(0,12):`-`}function be(e){if(e===void 0)return`-`;let t=Math.round(e/1e3);if(t<60)return`${t}s`;let n=Math.round(t/60);if(n<60)return`${n}m`;let r=Math.round(n/60);return r<48?`${r}h`:`${Math.round(r/24)}d`}function xe(e){if(!e.composition)return null;try{let t=JSON.parse(e.composition);return Array.isArray(t)?t.length:null}catch{return null}}function Se(e,t){let n=t.trim().toLowerCase();return n?[e.title,e.id,e.coordinatorId,e.rootCoordinatorId,e.outcomeStatus,e.outcomeSummary].some(e=>String(e??``).toLowerCase().includes(n)):!0}function Ce(e){return e.status===`offline`?`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`:e.status===`busy`?`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`:e.status===`stale`?`bg-red-500/10 text-red-400 border-red-500/20`:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`}function D(){let[e,n]=(0,x.useState)(null),[o,s]=(0,x.useState)([]),[C,w]=(0,x.useState)(null),[T,D]=(0,x.useState)(`all`),[O,we]=(0,x.useState)(``),[k,A]=(0,x.useState)(!1),[Te,j]=(0,x.useState)(!1),[M,N]=(0,x.useState)(null),[P,Ee]=(0,x.useState)(()=>Date.now()),F=l(e=>e.teams),I=e??F[0]?.id??null,L=I?ne(`team`,I):``,R=l(e=>e.teamsLoading),z=l(e=>e.teamsError),De=l(e=>e.fetchTeams),B=l(e=>(I?e.issuesByEntity[L]:void 0)??pe),V=l(e=>e.issueRefsById),Oe=l(e=>L?!!e.issueLoading[L]:!1),H=l(e=>L?e.issueErrors[L]??null:null),U=l(e=>e.fetchIssuesForEntity),ke=l(e=>e.teamOutcomeAggregates),Ae=l(e=>e.teamOutcomeAggregatesLoading),je=l(e=>e.teamOutcomeAggregatesError),Me=l(e=>e.fetchTeamOutcomeAggregates),W=l(e=>e.fetchPlan),Ne=l(e=>e.dissolveTeam),Pe=l(e=>e.openConfirm),G=l(e=>e.agentsById),Fe=l(e=>e.switchView),Ie=l(e=>e.openInboxThread),Le=l(e=>I?e.plansByTeam[te(I)]??null:null),K=e=>{let t=G[e];return t?f(t):e.slice(-8)};async function q(e,t=T){j(!0),N(null);try{let n=new URLSearchParams({team:e});t!==`all`&&n.set(`kind`,t),s((await i(`GET`,`/blackboard?${n.toString()}`)).entries),w(e)}catch(t){N(d(t)),s([]),w(e)}finally{j(!1)}}async function J(){A(!0),N(null);try{let[t]=await Promise.all([De(),Me()]);Ee(Date.now());let n=e??t[0]?.id;n?(W({teamId:n}),await Promise.all([q(n,T),U(`team`,n)])):(s([]),w(null))}catch(e){N(d(e))}finally{A(!1)}}(0,x.useEffect)(()=>{J()},[]),(0,x.useEffect)(()=>{C&&q(C,T)},[T]);let Y=(0,x.useMemo)(()=>F.filter(e=>Se(e,O)),[F,O]),X=F.find(e=>e.id===I)??null,Z=C===I?o:[],Q=(0,x.useMemo)(()=>B.map(e=>({...e,issueRef:V[e.issueRef.id]??e.issueRef})),[V,B]),$=(0,x.useMemo)(()=>I?Object.values(G).filter(e=>e.teamId===I).sort((e,t)=>f(e).localeCompare(f(t))||e.id.localeCompare(t.id)):[],[G,I]),Re=(0,x.useMemo)(()=>{let e={decision:[],fact:[],ruled_out:[]};for(let t of Z)e[t.kind].push(t);return e},[Z]),ze=T===`all`?[`decision`,`fact`,`ruled_out`]:[T];return(0,S.jsxs)(`div`,{className:`space-y-4 p-4`,children:[(0,S.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,S.jsx)(c,{className:`size-5 text-primary`}),(0,S.jsx)(`h1`,{className:`text-lg font-semibold`,children:`Teams`}),(0,S.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`active and dissolved team working sets`})]}),(0,S.jsxs)(h,{variant:`outline`,size:`sm`,onClick:()=>void J(),disabled:k||R,children:[(0,S.jsx)(r,{className:t(`size-4`,(k||R)&&`animate-spin`)}),` Refresh`]})]}),(M||z)&&(0,S.jsx)(`div`,{className:`rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:M||z}),(0,S.jsxs)(`div`,{className:`grid gap-4 xl:grid-cols-[minmax(320px,420px)_1fr]`,children:[(0,S.jsxs)(`div`,{className:`min-h-[520px] min-w-0 space-y-3`,children:[(0,S.jsxs)(`div`,{className:`space-y-3`,children:[(0,S.jsxs)(`div`,{className:`flex items-center justify-between gap-3`,children:[(0,S.jsx)(`span`,{children:`Team list`}),(0,S.jsx)(m,{variant:`secondary`,children:F.length})]}),(0,S.jsx)(le,{value:O,onChange:e=>we(e.target.value),placeholder:`Filter teams`,className:`h-8`})]}),(0,S.jsx)(`div`,{children:(k||R)&&F.length===0?(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`Loading teams…`}):Y.length===0?(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:F.length===0?`No teams`:`No teams match`}):(0,S.jsx)(g,{className:`h-[610px] pr-3`,children:(0,S.jsx)(`div`,{className:`space-y-2`,children:Y.map(e=>{let r=e.id===I,i=xe(e),o=ge(e),s=e.coordinatorId,c=()=>{n(e.id),W({teamId:e.id}),U(`team`,e.id),q(e.id,T)};return(0,S.jsx)(`div`,{role:`button`,tabIndex:0,onClick:c,onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),c())},className:`block w-full cursor-pointer text-left`,children:(0,S.jsx)(b,{className:t(`transition-colors hover:border-primary/50`,r&&`border-primary/60 bg-primary/5`),children:(0,S.jsxs)(y,{className:`space-y-3 p-3`,children:[(0,S.jsxs)(`div`,{className:`flex items-start justify-between gap-2`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`div`,{className:`truncate text-sm font-medium`,children:e.title||E(e.id)}),(0,S.jsx)(`div`,{className:`truncate text-xs text-muted-foreground`,children:e.id})]}),(0,S.jsx)(m,{variant:`outline`,className:_e[o],children:o})]}),(0,S.jsxs)(`div`,{className:`grid grid-cols-2 gap-x-3 gap-y-1 text-xs text-muted-foreground`,children:[(0,S.jsx)(`span`,{children:`Coordinator`}),s?(0,S.jsxs)(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),Ie(s),Fe(`chat`)},className:`flex min-w-0 items-center justify-end gap-1 text-right text-primary hover:underline`,title:`Chat with ${s}`,children:[(0,S.jsx)(a,{className:`size-3 shrink-0`}),(0,S.jsx)(`span`,{className:`truncate`,children:K(s)})]}):(0,S.jsx)(`span`,{className:`text-right text-muted-foreground`,children:`—`}),(0,S.jsx)(`span`,{children:`Members`}),(0,S.jsx)(`span`,{className:`text-right text-foreground`,children:i??`-`}),(0,S.jsx)(`span`,{children:`Created`}),(0,S.jsx)(`span`,{className:`text-right text-foreground`,title:p(e.createdAt),children:u(P,e.createdAt)}),e.dissolvedAt?(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`span`,{children:`Dissolved`}),(0,S.jsx)(`span`,{className:`text-right text-foreground`,title:p(e.dissolvedAt),children:u(P,e.dissolvedAt)})]}):null]}),o===`orphaned`&&e.zeroMembersSince!=null?(0,S.jsx)(`div`,{className:`flex items-center gap-1.5 border-t border-border pt-2 text-xs text-amber-400`,title:p(e.zeroMembersSince),children:(0,S.jsxs)(`span`,{children:[`No live members since `,u(P,e.zeroMembersSince),` — auto-dissolving`]})}):null,e.status===`dissolved`?(0,S.jsxs)(`div`,{className:`space-y-1 border-t border-border pt-2`,children:[(0,S.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[e.outcomeStatus?(0,S.jsx)(m,{variant:`outline`,className:ve[e.outcomeStatus],children:e.outcomeStatus}):(0,S.jsx)(m,{variant:`outline`,children:`no outcome`}),(0,S.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:be(e.wallClockMs)})]}),e.outcomeSummary?(0,S.jsx)(`p`,{className:`line-clamp-2 text-xs text-muted-foreground`,children:e.outcomeSummary}):null]}):null]})})},e.id)})})})})]}),(0,S.jsxs)(`div`,{className:`min-h-[520px] min-w-0 space-y-4`,children:[(0,S.jsx)(fe,{data:ke,loading:Ae,error:je}),X?(0,S.jsxs)(b,{children:[(0,S.jsx)(_,{children:(0,S.jsxs)(v,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`span`,{children:`Issues`}),(0,S.jsxs)(`span`,{className:`ml-2 text-xs font-normal text-muted-foreground`,children:[Q.length,` associated`]})]}),Oe?(0,S.jsx)(m,{variant:`secondary`,children:`loading`}):null]})}),(0,S.jsx)(y,{children:H?(0,S.jsx)(`div`,{className:`text-xs text-destructive`,children:H}):Q.length===0?(0,S.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`No associated issues`}):(0,S.jsx)(`div`,{className:`flex flex-wrap gap-2`,children:Q.map(({association:e,issueRef:t})=>(0,S.jsx)(ue,{issueRef:t,now:P,showSync:!0},`${e.issueRefId}:${e.createdAt}`))})})]}):null,X&&Le?(0,S.jsx)(b,{className:`p-0`,children:(0,S.jsx)(de,{teamId:X.id})}):null,(0,S.jsxs)(b,{children:[(0,S.jsx)(_,{children:(0,S.jsxs)(v,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`span`,{children:`Roster`}),(0,S.jsxs)(`span`,{className:`ml-2 text-xs font-normal text-muted-foreground`,children:[$.length,` member`,$.length===1?``:`s`]})]}),X&&X.status!==`dissolved`?(0,S.jsxs)(h,{variant:`destructive`,size:`sm`,onClick:()=>Pe(`Dissolve Team`,`Dissolve "${X.title||X.id}"? This closes the team working set but keeps its records visible.`,()=>void Ne(X.id)),children:[(0,S.jsx)(ee,{className:`size-4`}),` Dissolve`]}):null]})}),(0,S.jsx)(y,{children:X?$.length===0?(0,S.jsx)(`div`,{className:`py-8 text-center text-sm text-muted-foreground`,children:`No members`}):(0,S.jsx)(`div`,{className:`grid gap-2 md:grid-cols-2`,children:$.map(e=>(0,S.jsxs)(`div`,{className:`flex min-w-0 items-center justify-between gap-3 rounded-md border border-border bg-background px-3 py-2`,children:[(0,S.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[(0,S.jsx)(`span`,{className:t(`h-2 w-2 shrink-0 rounded-full`,re(e,P))}),(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`div`,{className:`truncate text-sm font-medium`,children:f(e)}),(0,S.jsx)(`div`,{className:`truncate text-xs text-muted-foreground`,children:e.id})]})]}),(0,S.jsx)(m,{variant:`outline`,className:Ce(e),children:e.status})]},e.id))}):(0,S.jsx)(`div`,{className:`py-8 text-center text-sm text-muted-foreground`,children:`No team selected`})})]}),(0,S.jsxs)(b,{children:[(0,S.jsx)(_,{children:(0,S.jsxs)(v,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`span`,{children:`Blackboard`}),X?(0,S.jsx)(`span`,{className:`ml-2 text-xs font-normal text-muted-foreground`,children:X.title||X.id}):null]}),(0,S.jsxs)(ce,{value:T,onValueChange:e=>D(e),children:[(0,S.jsx)(ae,{size:`sm`,className:`w-36`,children:(0,S.jsx)(ie,{})}),(0,S.jsx)(oe,{children:me.map(e=>(0,S.jsx)(se,{value:e.value,children:e.label},e.value))})]})]})}),(0,S.jsx)(y,{children:X?Te&&Z.length===0?(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`Loading blackboard…`}):Z.length===0?(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`No blackboard entries`}):(0,S.jsx)(g,{className:`h-[640px] pr-3`,children:(0,S.jsx)(`div`,{className:`space-y-5`,children:ze.map(e=>{let t=Re[e];return t.length===0?null:(0,S.jsxs)(`section`,{className:`space-y-2`,children:[(0,S.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,S.jsx)(m,{variant:`secondary`,children:he[e]}),(0,S.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:t.length})]}),(0,S.jsx)(`div`,{className:`space-y-2`,children:t.map(e=>(0,S.jsxs)(`div`,{className:`space-y-3 rounded-md border border-border bg-background p-3`,children:[(0,S.jsxs)(`div`,{className:`flex flex-wrap items-start justify-between gap-2`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`div`,{className:`text-sm font-medium`,children:e.title}),(0,S.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[`by `,E(e.authorId),` · `,u(P,e.updatedAt||e.createdAt)]})]}),(0,S.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-1`,children:[(0,S.jsx)(m,{variant:`outline`,className:ye[e.status],children:e.status}),e.supersedes?(0,S.jsxs)(m,{variant:`outline`,children:[`supersedes `,E(e.supersedes)]}):null]})]}),e.body?(0,S.jsx)(`p`,{className:`whitespace-pre-wrap text-sm text-muted-foreground`,children:e.body}):null]},e.id))})]},e)})})}):(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`No team selected`})})]})]})]})]})}export{D as TeamsView};
2
- //# sourceMappingURL=teams-Cx29VUif.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"teams-Cx29VUif.js","names":[],"sources":["../../dashboard/src/components/teams/TeamOutcomesCard.tsx","../../dashboard/src/components/views/teams.tsx"],"sourcesContent":["import { BarChart3 } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport type { TeamOutcomeAggregates } from '@/store/types'\n\nfunction duration(ms: number | null | undefined): string {\n if (ms == null) return '-'\n const seconds = Math.round(ms / 1000)\n if (seconds < 60) return `${seconds}s`\n const minutes = Math.round(seconds / 60)\n if (minutes < 60) return `${minutes}m`\n const hours = Math.round(minutes / 60)\n if (hours < 48) return `${hours}h`\n return `${Math.round(hours / 24)}d`\n}\n\nfunction percent(value: number): string {\n return `${Math.round(value * 100)}%`\n}\n\nfunction outcomeTone(outcome: string | null | undefined): string {\n if (outcome === 'success') return 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20'\n if (outcome === 'partial') return 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20'\n if (outcome === 'failed') return 'bg-red-500/10 text-red-400 border-red-500/20'\n return 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n}\n\nexport function TeamOutcomesCard({\n data,\n loading,\n error,\n}: {\n data: TeamOutcomeAggregates | null\n loading: boolean\n error: string | null\n}) {\n const totals = data?.totals.byOutcomeStatus\n const shapes = data?.compositionShapes.slice(0, 6) ?? []\n const recent = data?.recent.slice(0, 8) ?? []\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex flex-wrap items-center justify-between gap-3\">\n <span className=\"flex items-center gap-2\"><BarChart3 className=\"size-4 text-primary\" /> Outcomes</span>\n <Badge variant=\"secondary\">{data?.totals.total ?? 0} dissolved</Badge>\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {error ? <div className=\"rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive\">{error}</div> : null}\n {loading && !data ? (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">Loading outcomes…</div>\n ) : !data || data.totals.total === 0 ? (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">No dissolved teams yet</div>\n ) : (\n <>\n <div className=\"flex flex-wrap gap-2 text-xs\">\n {(['success', 'partial', 'failed', 'unknown'] as const).map((outcome) => {\n const count = totals?.[outcome] ?? 0\n // Number-first to echo the \"N dissolved\" header badge; zero counts stay\n // neutral so empty buckets (e.g. \"0 failed\") don't shout in alarm colors.\n return (\n <Badge key={outcome} variant=\"outline\" className={count === 0 ? 'text-muted-foreground' : outcomeTone(outcome === 'unknown' ? null : outcome)}>\n {count} {outcome}\n </Badge>\n )\n })}\n </div>\n\n <section className=\"space-y-2\">\n <div className=\"text-sm font-medium\">Composition win-rate</div>\n <div className=\"divide-y divide-border rounded-md border border-border\">\n {shapes.map((shape) => (\n <div key={shape.shape} className=\"grid gap-2 px-3 py-2 text-sm md:grid-cols-[1fr_auto_auto] md:items-center\">\n <div className=\"min-w-0\">\n {/* The composition is this table's primary key — wrap it rather than\n truncate so it stays readable at narrow widths instead of hiding\n which roster actually won behind a tooltip. */}\n <div className=\"break-words font-medium\" title={shape.shape}>{shape.shape}</div>\n <div className=\"text-xs text-muted-foreground\">{shape.count} team{shape.count === 1 ? '' : 's'} · avg {duration(shape.avgWallClockMs)}</div>\n </div>\n <span className=\"text-muted-foreground\">{shape.successCount}/{shape.count}</span>\n <Badge variant=\"outline\" className={outcomeTone(shape.successRate >= 0.5 ? 'success' : shape.successRate > 0 ? 'partial' : 'failed')}>\n {percent(shape.successRate)}\n </Badge>\n </div>\n ))}\n </div>\n </section>\n\n <section className=\"space-y-2\">\n <div className=\"text-sm font-medium\">Recent outcomes</div>\n <div className=\"overflow-hidden rounded-md border border-border\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted/40 text-xs text-muted-foreground\">\n <tr>\n <th className=\"px-3 py-2 text-left font-medium\">Team</th>\n <th className=\"px-3 py-2 text-left font-medium\">Outcome</th>\n <th className=\"px-3 py-2 text-right font-medium\">Members</th>\n <th className=\"px-3 py-2 text-right font-medium\">Duration</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {recent.map((team) => (\n <tr key={team.id}>\n <td className=\"max-w-[220px] px-3 py-2\">\n <div className=\"truncate font-medium\">{team.title || team.id.slice(0, 12)}</div>\n <div className=\"truncate text-xs text-muted-foreground\">{team.id}</div>\n </td>\n <td className=\"px-3 py-2\"><Badge variant=\"outline\" className={outcomeTone(team.outcome)}>{team.outcome ?? 'unknown'}</Badge></td>\n <td className=\"px-3 py-2 text-right\">{team.memberCount}</td>\n <td className=\"px-3 py-2 text-right text-muted-foreground\">{duration(team.durationMs)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </section>\n </>\n )}\n </CardContent>\n </Card>\n )\n}\n","import { useEffect, useMemo, useState } from 'react'\nimport { errMessage } from 'agent-relay-sdk/types'\nimport { MessageSquare, RefreshCw, Trash2, Users } from 'lucide-react'\nimport { api } from '@/lib/api'\nimport { useRelayStore } from '@/store'\nimport { cn } from '@/lib/utils'\nimport { displayName, fmtTime, statusDotColor, timeAgo } from '@/lib/display'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { PlanGraphPanel } from '@/components/chat/PlanGraphPanel'\nimport { IssueRefChip } from '@/components/shared/issue-ref-chip'\nimport { TeamOutcomesCard } from '@/components/teams/TeamOutcomesCard'\nimport { issueEntityKey } from '@/store/slices/issues'\nimport { planTeamLookupKey } from '@/store/slices/plans'\nimport type { Agent, BlackboardEntry, BlackboardEntryKind, BlackboardEntryStatus, Team } from '@/types'\nimport type { IssueAssociationIssue } from '@/lib/issue-types'\n\ninterface BlackboardResponse {\n entries: BlackboardEntry[]\n}\n\n// Stable reference for the empty case: a selector returning a fresh `[]` each\n// render breaks Zustand v5's useSyncExternalStore snapshot equality → infinite\n// re-render loop. Mirrors EMPTY_AGENT_CHAT_WINDOW in store/slices/chat.ts.\nconst EMPTY_TEAM_ISSUES: IssueAssociationIssue[] = []\n\ntype KindFilter = 'all' | BlackboardEntryKind\n\nconst KIND_OPTIONS: Array<{ value: KindFilter; label: string }> = [\n { value: 'all', label: 'All kinds' },\n { value: 'decision', label: 'Decisions' },\n { value: 'fact', label: 'Facts' },\n { value: 'ruled_out', label: 'Ruled out' },\n]\n\nconst KIND_LABEL: Record<BlackboardEntryKind, string> = {\n decision: 'decision',\n fact: 'fact',\n ruled_out: 'ruled out',\n}\n\n// (status badge colors are derived via DISPLAY_STATE_CLASS below, not a raw status map.)\n// Display state is derived, not stored: a team row stays status='active' while orphaned —\n// zeroMembersSince being set means the #398 reaper has flagged it (zero live members) and is\n// counting down to auto-dissolve. Surface that as a distinct state so \"active\" means truly live.\ntype TeamDisplayState = 'active' | 'orphaned' | 'dissolved'\n\nfunction teamDisplayState(team: Team): TeamDisplayState {\n if (team.status === 'dissolved') return 'dissolved'\n if (team.zeroMembersSince != null) return 'orphaned'\n return 'active'\n}\n\nconst DISPLAY_STATE_CLASS: Record<TeamDisplayState, string> = {\n active: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n orphaned: 'bg-amber-500/10 text-amber-400 border-amber-500/20',\n dissolved: 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20',\n}\n\nconst OUTCOME_STATUS_CLASS: Record<NonNullable<Team['outcomeStatus']>, string> = {\n success: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n partial: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n failed: 'bg-red-500/10 text-red-400 border-red-500/20',\n}\n\nconst ENTRY_STATUS_CLASS: Record<BlackboardEntryStatus, string> = {\n active: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n superseded: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n retracted: 'bg-red-500/10 text-red-400 border-red-500/20',\n}\n\nfunction shortId(id: string | undefined): string {\n return id ? id.slice(0, 12) : '-'\n}\n\nfunction duration(ms: number | undefined): string {\n if (ms === undefined) return '-'\n const seconds = Math.round(ms / 1000)\n if (seconds < 60) return `${seconds}s`\n const minutes = Math.round(seconds / 60)\n if (minutes < 60) return `${minutes}m`\n const hours = Math.round(minutes / 60)\n if (hours < 48) return `${hours}h`\n return `${Math.round(hours / 24)}d`\n}\n\nfunction compositionCount(team: Team): number | null {\n if (!team.composition) return null\n try {\n const parsed = JSON.parse(team.composition) as unknown\n return Array.isArray(parsed) ? parsed.length : null\n } catch {\n return null\n }\n}\n\nfunction matchesTeamFilter(team: Team, filter: string): boolean {\n const q = filter.trim().toLowerCase()\n if (!q) return true\n return [\n team.title,\n team.id,\n team.coordinatorId,\n team.rootCoordinatorId,\n team.outcomeStatus,\n team.outcomeSummary,\n ].some((value) => String(value ?? '').toLowerCase().includes(q))\n}\n\nfunction agentStatusClass(agent: Agent): string {\n if (agent.status === 'offline') return 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n if (agent.status === 'busy') return 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20'\n if (agent.status === 'stale') return 'bg-red-500/10 text-red-400 border-red-500/20'\n return 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20'\n}\n\nexport function TeamsView() {\n const [selectedTeamId, setSelectedTeamId] = useState<string | null>(null)\n const [entries, setEntries] = useState<BlackboardEntry[]>([])\n const [entriesTeamId, setEntriesTeamId] = useState<string | null>(null)\n const [kindFilter, setKindFilter] = useState<KindFilter>('all')\n const [teamFilter, setTeamFilter] = useState('')\n const [refreshing, setRefreshing] = useState(false)\n const [entriesLoading, setEntriesLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [now, setNow] = useState(() => Date.now())\n const teams = useRelayStore((s) => s.teams)\n const displayedTeamId = selectedTeamId ?? teams[0]?.id ?? null\n const teamIssueKey = displayedTeamId ? issueEntityKey('team', displayedTeamId) : ''\n const teamsLoading = useRelayStore((s) => s.teamsLoading)\n const teamsError = useRelayStore((s) => s.teamsError)\n const fetchTeams = useRelayStore((s) => s.fetchTeams)\n const teamIssues = useRelayStore((s) => (displayedTeamId ? s.issuesByEntity[teamIssueKey] : undefined) ?? EMPTY_TEAM_ISSUES)\n const issueRefsById = useRelayStore((s) => s.issueRefsById)\n const teamIssuesLoading = useRelayStore((s) => teamIssueKey ? Boolean(s.issueLoading[teamIssueKey]) : false)\n const teamIssuesError = useRelayStore((s) => teamIssueKey ? (s.issueErrors[teamIssueKey] ?? null) : null)\n const fetchIssuesForEntity = useRelayStore((s) => s.fetchIssuesForEntity)\n const teamOutcomeAggregates = useRelayStore((s) => s.teamOutcomeAggregates)\n const teamOutcomeAggregatesLoading = useRelayStore((s) => s.teamOutcomeAggregatesLoading)\n const teamOutcomeAggregatesError = useRelayStore((s) => s.teamOutcomeAggregatesError)\n const fetchTeamOutcomeAggregates = useRelayStore((s) => s.fetchTeamOutcomeAggregates)\n const fetchPlan = useRelayStore((s) => s.fetchPlan)\n const dissolveTeam = useRelayStore((s) => s.dissolveTeam)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const agentsById = useRelayStore((s) => s.agentsById)\n const switchView = useRelayStore((s) => s.switchView)\n const openInboxThread = useRelayStore((s) => s.openInboxThread)\n // Read the selected team's plan from the store so the drill-in can gate the\n // plan card on its existence (no empty card for plan-less teams). teams.tsx\n // owns the fetch below; PlanGraphPanel then renders it from the store.\n const teamPlan = useRelayStore((s) => {\n return displayedTeamId ? s.plansByTeam[planTeamLookupKey(displayedTeamId)] ?? null : null\n })\n const coordinatorLabel = (id: string): string => {\n const agent = agentsById[id]\n return agent ? displayName(agent) : id.slice(-8)\n }\n\n async function loadEntries(teamId: string, nextKind: KindFilter = kindFilter) {\n setEntriesLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({ team: teamId })\n if (nextKind !== 'all') params.set('kind', nextKind)\n const payload = await api<BlackboardResponse>('GET', `/blackboard?${params.toString()}`)\n setEntries(payload.entries)\n setEntriesTeamId(teamId)\n } catch (e) {\n setError(errMessage(e))\n setEntries([])\n setEntriesTeamId(teamId)\n } finally {\n setEntriesLoading(false)\n }\n }\n\n async function refresh() {\n setRefreshing(true)\n setError(null)\n try {\n const [nextTeams] = await Promise.all([fetchTeams(), fetchTeamOutcomeAggregates()])\n setNow(Date.now())\n const displayedTeamId = selectedTeamId ?? nextTeams[0]?.id\n if (displayedTeamId) {\n void fetchPlan({ teamId: displayedTeamId })\n await Promise.all([\n loadEntries(displayedTeamId, kindFilter),\n fetchIssuesForEntity('team', displayedTeamId),\n ])\n } else {\n setEntries([])\n setEntriesTeamId(null)\n }\n } catch (e) {\n setError(errMessage(e))\n } finally {\n setRefreshing(false)\n }\n }\n\n // Mount: synchronize this local view with Relay's persisted team list.\n useEffect(() => {\n void refresh()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n // Filter change: re-query the currently loaded blackboard working set.\n useEffect(() => {\n if (entriesTeamId) void loadEntries(entriesTeamId, kindFilter)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [kindFilter])\n\n const visibleTeams = useMemo(() => teams.filter((team) => matchesTeamFilter(team, teamFilter)), [teams, teamFilter])\n const displayedTeam = teams.find((team) => team.id === displayedTeamId) ?? null\n const displayedEntries = entriesTeamId === displayedTeamId ? entries : []\n const displayedTeamIssues = useMemo(() => teamIssues.map((item) => ({\n ...item,\n issueRef: issueRefsById[item.issueRef.id] ?? item.issueRef,\n })), [issueRefsById, teamIssues])\n const teamRoster = useMemo(() => {\n if (!displayedTeamId) return []\n return Object.values(agentsById)\n .filter((agent) => agent.teamId === displayedTeamId)\n .sort((a, b) => displayName(a).localeCompare(displayName(b)) || a.id.localeCompare(b.id))\n }, [agentsById, displayedTeamId])\n\n const groupedEntries = useMemo(() => {\n const groups: Record<BlackboardEntryKind, BlackboardEntry[]> = { decision: [], fact: [], ruled_out: [] }\n for (const entry of displayedEntries) groups[entry.kind].push(entry)\n return groups\n }, [displayedEntries])\n\n const entryKinds = kindFilter === 'all'\n ? (['decision', 'fact', 'ruled_out'] as const)\n : ([kindFilter] as const)\n\n return (\n <div className=\"space-y-4 p-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <Users className=\"size-5 text-primary\" />\n <h1 className=\"text-lg font-semibold\">Teams</h1>\n <span className=\"text-xs text-muted-foreground\">active and dissolved team working sets</span>\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void refresh()} disabled={refreshing || teamsLoading}>\n <RefreshCw className={cn('size-4', (refreshing || teamsLoading) && 'animate-spin')} /> Refresh\n </Button>\n </div>\n\n {(error || teamsError) && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive\">{error || teamsError}</div>\n )}\n\n <div className=\"grid gap-4 xl:grid-cols-[minmax(320px,420px)_1fr]\">\n <div className=\"min-h-[520px] min-w-0 space-y-3\">\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-3\">\n <span>Team list</span>\n <Badge variant=\"secondary\">{teams.length}</Badge>\n </div>\n <Input\n value={teamFilter}\n onChange={(e) => setTeamFilter(e.target.value)}\n placeholder=\"Filter teams\"\n className=\"h-8\"\n />\n </div>\n <div>\n {(refreshing || teamsLoading) && teams.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-muted-foreground\">Loading teams…</div>\n ) : visibleTeams.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-muted-foreground\">{teams.length === 0 ? 'No teams' : 'No teams match'}</div>\n ) : (\n <ScrollArea className=\"h-[610px] pr-3\">\n <div className=\"space-y-2\">\n {visibleTeams.map((team) => {\n const selected = team.id === displayedTeamId\n const members = compositionCount(team)\n const state = teamDisplayState(team)\n const coordId = team.coordinatorId\n const selectTeam = () => {\n setSelectedTeamId(team.id)\n void fetchPlan({ teamId: team.id })\n void fetchIssuesForEntity('team', team.id)\n void loadEntries(team.id, kindFilter)\n }\n return (\n // Clickable card, not a <button>: it nests an interactive \"chat with\n // coordinator\" link, and button-in-button is invalid HTML.\n <div\n key={team.id}\n role=\"button\"\n tabIndex={0}\n onClick={selectTeam}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); selectTeam() } }}\n className=\"block w-full cursor-pointer text-left\"\n >\n <Card className={cn('transition-colors hover:border-primary/50', selected && 'border-primary/60 bg-primary/5')}>\n <CardContent className=\"space-y-3 p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium\">{team.title || shortId(team.id)}</div>\n <div className=\"truncate text-xs text-muted-foreground\">{team.id}</div>\n </div>\n <Badge variant=\"outline\" className={DISPLAY_STATE_CLASS[state]}>{state}</Badge>\n </div>\n <div className=\"grid grid-cols-2 gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n <span>Coordinator</span>\n {coordId ? (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); openInboxThread(coordId); switchView('chat') }}\n className=\"flex min-w-0 items-center justify-end gap-1 text-right text-primary hover:underline\"\n title={`Chat with ${coordId}`}\n >\n <MessageSquare className=\"size-3 shrink-0\" />\n <span className=\"truncate\">{coordinatorLabel(coordId)}</span>\n </button>\n ) : (\n <span className=\"text-right text-muted-foreground\">—</span>\n )}\n <span>Members</span>\n <span className=\"text-right text-foreground\">{members ?? '-'}</span>\n <span>Created</span>\n <span className=\"text-right text-foreground\" title={fmtTime(team.createdAt)}>{timeAgo(now, team.createdAt)}</span>\n {team.dissolvedAt ? (\n <>\n <span>Dissolved</span>\n <span className=\"text-right text-foreground\" title={fmtTime(team.dissolvedAt)}>{timeAgo(now, team.dissolvedAt)}</span>\n </>\n ) : null}\n </div>\n {state === 'orphaned' && team.zeroMembersSince != null ? (\n <div className=\"flex items-center gap-1.5 border-t border-border pt-2 text-xs text-amber-400\" title={fmtTime(team.zeroMembersSince)}>\n <span>No live members since {timeAgo(now, team.zeroMembersSince)} — auto-dissolving</span>\n </div>\n ) : null}\n {team.status === 'dissolved' ? (\n <div className=\"space-y-1 border-t border-border pt-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {team.outcomeStatus ? (\n <Badge variant=\"outline\" className={OUTCOME_STATUS_CLASS[team.outcomeStatus]}>{team.outcomeStatus}</Badge>\n ) : (\n <Badge variant=\"outline\">no outcome</Badge>\n )}\n <span className=\"text-xs text-muted-foreground\">{duration(team.wallClockMs)}</span>\n </div>\n {team.outcomeSummary ? <p className=\"line-clamp-2 text-xs text-muted-foreground\">{team.outcomeSummary}</p> : null}\n </div>\n ) : null}\n </CardContent>\n </Card>\n </div>\n )\n })}\n </div>\n </ScrollArea>\n )}\n </div>\n </div>\n\n <div className=\"min-h-[520px] min-w-0 space-y-4\">\n <TeamOutcomesCard\n data={teamOutcomeAggregates}\n loading={teamOutcomeAggregatesLoading}\n error={teamOutcomeAggregatesError}\n />\n\n {displayedTeam ? (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <span>Issues</span>\n <span className=\"ml-2 text-xs font-normal text-muted-foreground\">{displayedTeamIssues.length} associated</span>\n </div>\n {teamIssuesLoading ? <Badge variant=\"secondary\">loading</Badge> : null}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {teamIssuesError ? (\n <div className=\"text-xs text-destructive\">{teamIssuesError}</div>\n ) : displayedTeamIssues.length === 0 ? (\n <div className=\"text-xs text-muted-foreground\">No associated issues</div>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {displayedTeamIssues.map(({ association, issueRef }) => (\n <IssueRefChip key={`${association.issueRefId}:${association.createdAt}`} issueRef={issueRef} now={now} showSync />\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n ) : null}\n\n {displayedTeam && teamPlan ? (\n // Wrap the chat-style plan overlay in a Card so it reads as a peer of\n // the Roster/Blackboard cards (rounded + solid bg + ring) instead of a\n // flat translucent strip. Gated on teamPlan so plan-less teams show no\n // empty card. p-0 lets the panel's own px-3 py-2 govern its padding.\n <Card className=\"p-0\">\n <PlanGraphPanel teamId={displayedTeam.id} />\n </Card>\n ) : null}\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <span>Roster</span>\n <span className=\"ml-2 text-xs font-normal text-muted-foreground\">{teamRoster.length} member{teamRoster.length === 1 ? '' : 's'}</span>\n </div>\n {displayedTeam && displayedTeam.status !== 'dissolved' ? (\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => openConfirm(\n 'Dissolve Team',\n `Dissolve \"${displayedTeam.title || displayedTeam.id}\"? This closes the team working set but keeps its records visible.`,\n () => void dissolveTeam(displayedTeam.id),\n )}\n >\n <Trash2 className=\"size-4\" /> Dissolve\n </Button>\n ) : null}\n </CardTitle>\n </CardHeader>\n <CardContent>\n {!displayedTeam ? (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">No team selected</div>\n ) : teamRoster.length === 0 ? (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">No members</div>\n ) : (\n <div className=\"grid gap-2 md:grid-cols-2\">\n {teamRoster.map((agent) => (\n <div key={agent.id} className=\"flex min-w-0 items-center justify-between gap-3 rounded-md border border-border bg-background px-3 py-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className={cn('h-2 w-2 shrink-0 rounded-full', statusDotColor(agent, now))} />\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium\">{displayName(agent)}</div>\n <div className=\"truncate text-xs text-muted-foreground\">{agent.id}</div>\n </div>\n </div>\n <Badge variant=\"outline\" className={agentStatusClass(agent)}>{agent.status}</Badge>\n </div>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"min-w-0\">\n <span>Blackboard</span>\n {displayedTeam ? (\n <span className=\"ml-2 text-xs font-normal text-muted-foreground\">{displayedTeam.title || displayedTeam.id}</span>\n ) : null}\n </div>\n <Select value={kindFilter} onValueChange={(value) => setKindFilter(value as KindFilter)}>\n <SelectTrigger size=\"sm\" className=\"w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {KIND_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>{option.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </CardTitle>\n </CardHeader>\n <CardContent>\n {!displayedTeam ? (\n <div className=\"py-16 text-center text-sm text-muted-foreground\">No team selected</div>\n ) : entriesLoading && displayedEntries.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-muted-foreground\">Loading blackboard…</div>\n ) : displayedEntries.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-muted-foreground\">No blackboard entries</div>\n ) : (\n <ScrollArea className=\"h-[640px] pr-3\">\n <div className=\"space-y-5\">\n {entryKinds.map((kind) => {\n const kindEntries = groupedEntries[kind]\n if (kindEntries.length === 0) return null\n return (\n <section key={kind} className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\">{KIND_LABEL[kind]}</Badge>\n <span className=\"text-xs text-muted-foreground\">{kindEntries.length}</span>\n </div>\n <div className=\"space-y-2\">\n {kindEntries.map((entry) => (\n <div key={entry.id} className=\"space-y-3 rounded-md border border-border bg-background p-3\">\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium\">{entry.title}</div>\n <div className=\"text-xs text-muted-foreground\">\n by {shortId(entry.authorId)} · {timeAgo(now, entry.updatedAt || entry.createdAt)}\n </div>\n </div>\n <div className=\"flex flex-wrap justify-end gap-1\">\n <Badge variant=\"outline\" className={ENTRY_STATUS_CLASS[entry.status]}>{entry.status}</Badge>\n {entry.supersedes ? <Badge variant=\"outline\">supersedes {shortId(entry.supersedes)}</Badge> : null}\n </div>\n </div>\n {entry.body ? <p className=\"whitespace-pre-wrap text-sm text-muted-foreground\">{entry.body}</p> : null}\n </div>\n ))}\n </div>\n </section>\n )\n })}\n </div>\n </ScrollArea>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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"}