agent-relay-server 0.77.0 → 0.77.1
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.
- package/docs/openapi.json +1 -1
- package/package.json +1 -1
- package/public/assets/{MessageBubble-Ct4RjObq.js → MessageBubble-Bkzw574C.js} +2 -2
- package/public/assets/{MessageBubble-Ct4RjObq.js.map → MessageBubble-Bkzw574C.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-BQcLye6d.js → PlanGraphPanel-DaLJ5fKn.js} +2 -2
- package/public/assets/{PlanGraphPanel-BQcLye6d.js.map → PlanGraphPanel-DaLJ5fKn.js.map} +1 -1
- package/public/assets/{activity-DeQhQ17b.js → activity-BLFdfuxj.js} +2 -2
- package/public/assets/{activity-DeQhQ17b.js.map → activity-BLFdfuxj.js.map} +1 -1
- package/public/assets/{agents-coulRN38.js → agents-CEnKjxMD.js} +2 -2
- package/public/assets/{agents-coulRN38.js.map → agents-CEnKjxMD.js.map} +1 -1
- package/public/assets/{automation-V97mnoZp.js → automation-Crxi1JGO.js} +2 -2
- package/public/assets/{automation-V97mnoZp.js.map → automation-Crxi1JGO.js.map} +1 -1
- package/public/assets/{chat-B8fs8r1x.js → chat-CWmxVsVA.js} +2 -2
- package/public/assets/{chat-B8fs8r1x.js.map → chat-CWmxVsVA.js.map} +1 -1
- package/public/assets/{formatted-body-impl-CQ-8HQ7u.js → formatted-body-impl-CCA5xER4.js} +2 -2
- package/public/assets/{formatted-body-impl-CQ-8HQ7u.js.map → formatted-body-impl-CCA5xER4.js.map} +1 -1
- package/public/assets/{index-DJtda8l2.js → index-Du_Njun0.js} +5 -5
- package/public/assets/index-Du_Njun0.js.map +1 -0
- package/public/assets/{insights-CtT5cEif.js → insights-Dy4k6fpi.js} +2 -2
- package/public/assets/{insights-CtT5cEif.js.map → insights-Dy4k6fpi.js.map} +1 -1
- package/public/assets/{maintenance-BWLMjX8h.js → maintenance-Bgaxr-3J.js} +2 -2
- package/public/assets/{maintenance-BWLMjX8h.js.map → maintenance-Bgaxr-3J.js.map} +1 -1
- package/public/assets/{managed-agents-KxHV0VcQ.js → managed-agents-DKD0Ovct.js} +2 -2
- package/public/assets/{managed-agents-KxHV0VcQ.js.map → managed-agents-DKD0Ovct.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-C5b5cBM0.js → markdown-preview-impl-OM7F_OtZ.js} +2 -2
- package/public/assets/{markdown-preview-impl-C5b5cBM0.js.map → markdown-preview-impl-OM7F_OtZ.js.map} +1 -1
- package/public/assets/{memory-Bh3UgQCk.js → memory-BSvQoXx6.js} +2 -2
- package/public/assets/{memory-Bh3UgQCk.js.map → memory-BSvQoXx6.js.map} +1 -1
- package/public/assets/{messages-Dg7TSxpZ.js → messages-D-WqWrGU.js} +2 -2
- package/public/assets/{messages-Dg7TSxpZ.js.map → messages-D-WqWrGU.js.map} +1 -1
- package/public/assets/{orchestrators-BBKW7bO0.js → orchestrators-DZrmTmOK.js} +2 -2
- package/public/assets/{orchestrators-BBKW7bO0.js.map → orchestrators-DZrmTmOK.js.map} +1 -1
- package/public/assets/{overview-a8lG62AZ.js → overview-C8Q92ay4.js} +2 -2
- package/public/assets/{overview-a8lG62AZ.js.map → overview-C8Q92ay4.js.map} +1 -1
- package/public/assets/{pairs-ChSYXFwu.js → pairs-B_3Q2kaN.js} +2 -2
- package/public/assets/{pairs-ChSYXFwu.js.map → pairs-B_3Q2kaN.js.map} +1 -1
- package/public/assets/{security-D0obEjDT.js → security-CUiGx0a5.js} +2 -2
- package/public/assets/{security-D0obEjDT.js.map → security-CUiGx0a5.js.map} +1 -1
- package/public/assets/{select-DwCP2Jhh.js → select-Bh9XLp7J.js} +2 -2
- package/public/assets/{select-DwCP2Jhh.js.map → select-Bh9XLp7J.js.map} +1 -1
- package/public/assets/{settings-f1l2R0e8.js → settings-Dox7P4g0.js} +2 -2
- package/public/assets/{settings-f1l2R0e8.js.map → settings-Dox7P4g0.js.map} +1 -1
- package/public/assets/{tasks-DDVfmUst.js → tasks-BIP2A8PU.js} +2 -2
- package/public/assets/{tasks-DDVfmUst.js.map → tasks-BIP2A8PU.js.map} +1 -1
- package/public/assets/{teams-COZKriLe.js → teams-pbVU7Y_N.js} +2 -2
- package/public/assets/{teams-COZKriLe.js.map → teams-pbVU7Y_N.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-CjOZ__aw.js → terminal-viewer-impl-M_3TITNG.js} +2 -2
- package/public/assets/{terminal-viewer-impl-CjOZ__aw.js.map → terminal-viewer-impl-M_3TITNG.js.map} +1 -1
- package/public/assets/{work-queue-BC7heUfD.js → work-queue-D12yWpsi.js} +2 -2
- package/public/assets/{work-queue-BC7heUfD.js.map → work-queue-D12yWpsi.js.map} +1 -1
- package/public/index.html +1 -1
- package/src/db/migrations.ts +2 -0
- package/src/db/provider-quotas.ts +139 -1
- package/public/assets/index-DJtda8l2.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Vn as e,_n as t,i as n,lt as r,on as i}from"./lucide-react-BH2ROV8n.js";import{l as a,t as o}from"./store-m74m5eNF.js";import{U as s,d as c,ut as l}from"./display-DHgdn4vN.js";import{t as u}from"./badge-BxG6_yfp.js";import{t as d}from"./button-D5k3GbF6.js";import{B as f,K as p,s as m}from"./index-
|
|
2
|
-
//# sourceMappingURL=work-queue-
|
|
1
|
+
import{Vn as e,_n as t,i as n,lt as r,on as i}from"./lucide-react-BH2ROV8n.js";import{l as a,t as o}from"./store-m74m5eNF.js";import{U as s,d as c,ut as l}from"./display-DHgdn4vN.js";import{t as u}from"./badge-BxG6_yfp.js";import{t as d}from"./button-D5k3GbF6.js";import{B as f,K as p,s as m}from"./index-Du_Njun0.js";import{r as h,t as g}from"./card-DKyFtrez.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),n=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(!n.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)(r,{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:n.from,onChange:e=>i({compose:{...n,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:r,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)(i,{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)(t,{className:`w-3 h-3 inline mr-1`}),s(r,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)(i,{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)(n,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
|
|
2
|
+
//# sourceMappingURL=work-queue-D12yWpsi.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-BC7heUfD.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-D12yWpsi.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-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-Du_Njun0.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-BH2ROV8n.js">
|
package/src/db/migrations.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { matchAgents } from "../agent-ref";
|
|
|
17
17
|
import { getDb } from "./connection.ts";
|
|
18
18
|
import { ensureContinuationArchiveSearchSchema } from "./continuation-archives.ts";
|
|
19
19
|
import { normalizeReactionEmoji } from "./mappers.ts";
|
|
20
|
+
import { normalizeProviderQuotaAccountKeys } from "./provider-quotas.ts";
|
|
20
21
|
import { projectRootsMissingIssueRepo, setProjectIssueRepo } from "./projects.ts";
|
|
21
22
|
import { repoFromGitRemote } from "../services/git-remote.ts";
|
|
22
23
|
import type {
|
|
@@ -700,6 +701,7 @@ export function applyMigrations(): void {
|
|
|
700
701
|
`);
|
|
701
702
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_quota_snapshots_account_time ON quota_snapshots(provider, account_key, captured_at DESC)");
|
|
702
703
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_quota_snapshots_agent_time ON quota_snapshots(agent_id, captured_at DESC)");
|
|
704
|
+
normalizeProviderQuotaAccountKeys();
|
|
703
705
|
|
|
704
706
|
getDb().run(`
|
|
705
707
|
CREATE TABLE IF NOT EXISTS memories (
|
|
@@ -14,6 +14,8 @@ type ProviderQuotaRow = {
|
|
|
14
14
|
last_updated_at?: number | null;
|
|
15
15
|
last_attempt_at: number;
|
|
16
16
|
last_error?: string | null;
|
|
17
|
+
updated_at: number;
|
|
18
|
+
created_at: number;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
type ProviderQuotaSnapshotRow = {
|
|
@@ -32,10 +34,117 @@ function normalizeProvider(value: string): string {
|
|
|
32
34
|
return cleanKey(value).toLowerCase();
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
function canonicalProviderQuotaAccountKey(provider: string, value: string): string {
|
|
38
|
+
const accountKey = cleanKey(value);
|
|
39
|
+
const legacySuffix = `:${normalizeProvider(provider)}`;
|
|
40
|
+
return accountKey.startsWith("host:") && accountKey.endsWith(legacySuffix)
|
|
41
|
+
? accountKey.slice(0, -legacySuffix.length)
|
|
42
|
+
: accountKey;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sourceAgentHost(sourceAgentId: string | undefined): string | undefined {
|
|
46
|
+
if (!sourceAgentId) return undefined;
|
|
47
|
+
const row = getDb().query("SELECT machine, rig FROM agents WHERE id = ?").get(sourceAgentId) as { machine?: string | null; rig?: string | null } | undefined;
|
|
48
|
+
const host = row?.machine ?? row?.rig ?? undefined;
|
|
49
|
+
return host?.trim() || undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hostFromAccountKey(accountKey: string): string | undefined {
|
|
53
|
+
if (!accountKey.startsWith("host:")) return undefined;
|
|
54
|
+
const host = accountKey.slice("host:".length);
|
|
55
|
+
return host && !host.includes(":") ? host : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
function normalizeSourceAgentIds(value: unknown): string[] {
|
|
36
59
|
return [...new Set(parseStringArray(typeof value === "string" ? value : JSON.stringify(value ?? [])).filter(Boolean))];
|
|
37
60
|
}
|
|
38
61
|
|
|
62
|
+
function quotaRowsForProvider(provider: string): ProviderQuotaRow[] {
|
|
63
|
+
return getDb().query("SELECT * FROM provider_quotas WHERE provider = ?").all(provider) as ProviderQuotaRow[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function supersededAccountKeys(provider: string, accountKey: string, rawAccountKey: string, sourceAgentId: string | undefined): string[] {
|
|
67
|
+
const candidates = new Set<string>();
|
|
68
|
+
if (rawAccountKey && rawAccountKey !== accountKey) candidates.add(rawAccountKey);
|
|
69
|
+
const host = hostFromAccountKey(accountKey) ?? sourceAgentHost(sourceAgentId);
|
|
70
|
+
if (host) {
|
|
71
|
+
candidates.add(`host:${host}:${provider}`);
|
|
72
|
+
if (accountKey !== `host:${host}`) candidates.add(`host:${host}`);
|
|
73
|
+
}
|
|
74
|
+
if (sourceAgentId) {
|
|
75
|
+
for (const row of quotaRowsForProvider(provider)) {
|
|
76
|
+
if (row.account_key === accountKey) continue;
|
|
77
|
+
if (!normalizeSourceAgentIds(row.source_agent_ids).includes(sourceAgentId)) continue;
|
|
78
|
+
candidates.add(row.account_key);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
candidates.delete(accountKey);
|
|
82
|
+
return [...candidates];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function newestQuotaState(rows: ProviderQuotaRow[]): string | null {
|
|
86
|
+
const row = rows
|
|
87
|
+
.filter((candidate) => candidate.quota_state)
|
|
88
|
+
.sort((a, b) => (b.last_updated_at ?? 0) - (a.last_updated_at ?? 0))[0];
|
|
89
|
+
return row?.quota_state ?? null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function newestLastError(rows: ProviderQuotaRow[]): string | null {
|
|
93
|
+
const row = rows
|
|
94
|
+
.filter((candidate) => candidate.last_error)
|
|
95
|
+
.sort((a, b) => b.last_attempt_at - a.last_attempt_at)[0];
|
|
96
|
+
return row?.last_error ?? null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function mergeProviderQuotaRows(provider: string, accountKey: string, oldAccountKeys: string[], now: number): number {
|
|
100
|
+
const keys = [...new Set(oldAccountKeys.filter((key) => key && key !== accountKey))];
|
|
101
|
+
if (keys.length === 0) return 0;
|
|
102
|
+
const placeholders = [accountKey, ...keys].map(() => "?").join(", ");
|
|
103
|
+
const rows = getDb().query(`SELECT * FROM provider_quotas WHERE provider = ? AND account_key IN (${placeholders})`).all(provider, accountKey, ...keys) as ProviderQuotaRow[];
|
|
104
|
+
const oldRows = rows.filter((row) => row.account_key !== accountKey);
|
|
105
|
+
if (oldRows.length === 0) return 0;
|
|
106
|
+
|
|
107
|
+
const sourceAgentIds = [...new Set(rows.flatMap((row) => normalizeSourceAgentIds(row.source_agent_ids)))];
|
|
108
|
+
const quotaState = newestQuotaState(rows);
|
|
109
|
+
const lastUpdatedAt = rows.reduce<number | undefined>((latest, row) => {
|
|
110
|
+
if (row.last_updated_at === null || row.last_updated_at === undefined) return latest;
|
|
111
|
+
return latest === undefined ? row.last_updated_at : Math.max(latest, row.last_updated_at);
|
|
112
|
+
}, undefined);
|
|
113
|
+
const lastAttemptAt = rows.reduce((latest, row) => Math.max(latest, row.last_attempt_at), 0);
|
|
114
|
+
const updatedAt = Math.max(now, ...rows.map((row) => row.updated_at));
|
|
115
|
+
const createdAt = Math.min(...rows.map((row) => row.created_at));
|
|
116
|
+
|
|
117
|
+
getDb().query(`
|
|
118
|
+
INSERT INTO provider_quotas (
|
|
119
|
+
provider, account_key, quota_state, source_agent_ids, last_updated_at,
|
|
120
|
+
last_attempt_at, last_error, updated_at, created_at
|
|
121
|
+
)
|
|
122
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
123
|
+
ON CONFLICT(provider, account_key) DO UPDATE SET
|
|
124
|
+
quota_state = excluded.quota_state,
|
|
125
|
+
source_agent_ids = excluded.source_agent_ids,
|
|
126
|
+
last_updated_at = excluded.last_updated_at,
|
|
127
|
+
last_attempt_at = excluded.last_attempt_at,
|
|
128
|
+
last_error = excluded.last_error,
|
|
129
|
+
updated_at = excluded.updated_at
|
|
130
|
+
`).run(
|
|
131
|
+
provider,
|
|
132
|
+
accountKey,
|
|
133
|
+
quotaState,
|
|
134
|
+
JSON.stringify(sourceAgentIds),
|
|
135
|
+
lastUpdatedAt ?? null,
|
|
136
|
+
lastAttemptAt || now,
|
|
137
|
+
newestLastError(rows),
|
|
138
|
+
updatedAt,
|
|
139
|
+
createdAt,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const oldPlaceholders = keys.map(() => "?").join(", ");
|
|
143
|
+
getDb().query(`UPDATE quota_snapshots SET account_key = ? WHERE provider = ? AND account_key IN (${oldPlaceholders})`).run(accountKey, provider, ...keys);
|
|
144
|
+
const result = getDb().query(`DELETE FROM provider_quotas WHERE provider = ? AND account_key IN (${oldPlaceholders})`).run(provider, ...keys);
|
|
145
|
+
return result.changes;
|
|
146
|
+
}
|
|
147
|
+
|
|
39
148
|
function quotaFingerprint(quota: QuotaState): string {
|
|
40
149
|
return JSON.stringify(quota.windows.map((window) => ({
|
|
41
150
|
name: window.name,
|
|
@@ -99,9 +208,11 @@ function providerQuotaIdentityForAgent(agent: Pick<AgentCard, "id" | "name" | "t
|
|
|
99
208
|
|
|
100
209
|
export function upsertProviderQuota(input: ProviderQuotaUpdateInput, now = Date.now()): ProviderQuotaRecord {
|
|
101
210
|
const provider = normalizeProvider(input.provider);
|
|
102
|
-
const
|
|
211
|
+
const rawAccountKey = cleanKey(input.accountKey);
|
|
212
|
+
const accountKey = canonicalProviderQuotaAccountKey(provider, rawAccountKey);
|
|
103
213
|
if (!provider) throw new Error("provider required");
|
|
104
214
|
if (!accountKey) throw new Error("accountKey required");
|
|
215
|
+
mergeProviderQuotaRows(provider, accountKey, supersededAccountKeys(provider, accountKey, rawAccountKey, input.sourceAgentId), now);
|
|
105
216
|
const existing = getDb().query("SELECT * FROM provider_quotas WHERE provider = ? AND account_key = ?").get(provider, accountKey) as ProviderQuotaRow | undefined;
|
|
106
217
|
const sourceAgentIds = normalizeSourceAgentIds(existing?.source_agent_ids);
|
|
107
218
|
if (input.sourceAgentId && !sourceAgentIds.includes(input.sourceAgentId)) sourceAgentIds.push(input.sourceAgentId);
|
|
@@ -140,6 +251,7 @@ export function upsertProviderQuota(input: ProviderQuotaUpdateInput, now = Date.
|
|
|
140
251
|
if (input.quota && latestSnapshotFingerprint(provider, accountKey) !== quotaFingerprint(input.quota)) {
|
|
141
252
|
recordProviderQuotaSnapshot({ provider, accountKey, quota: input.quota, sourceAgentId: input.sourceAgentId }, now);
|
|
142
253
|
}
|
|
254
|
+
pruneProviderQuotaRecords(provider, undefined, now - DAY_MS);
|
|
143
255
|
|
|
144
256
|
return listProviderQuotas({ provider, accountKey, historyLimit: 24, now }).quotas[0]!;
|
|
145
257
|
}
|
|
@@ -170,6 +282,32 @@ function pruneProviderQuotaSnapshots(provider?: string, accountKey?: string, old
|
|
|
170
282
|
return result.changes;
|
|
171
283
|
}
|
|
172
284
|
|
|
285
|
+
function pruneProviderQuotaRecords(provider?: string, accountKey?: string, olderThan = Date.now() - DAY_MS): number {
|
|
286
|
+
const result = provider && accountKey
|
|
287
|
+
? getDb().query("DELETE FROM provider_quotas WHERE provider = ? AND account_key = ? AND updated_at < ?").run(provider, accountKey, olderThan)
|
|
288
|
+
: provider
|
|
289
|
+
? getDb().query("DELETE FROM provider_quotas WHERE provider = ? AND updated_at < ?").run(provider, olderThan)
|
|
290
|
+
: getDb().query("DELETE FROM provider_quotas WHERE updated_at < ?").run(olderThan);
|
|
291
|
+
return result.changes;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function normalizeProviderQuotaAccountKeys(now = Date.now()): number {
|
|
295
|
+
let changes = 0;
|
|
296
|
+
for (const row of getDb().query("SELECT * FROM provider_quotas").all() as ProviderQuotaRow[]) {
|
|
297
|
+
let accountKey = canonicalProviderQuotaAccountKey(row.provider, row.account_key);
|
|
298
|
+
if (accountKey.startsWith("credential:")) {
|
|
299
|
+
const sourceAgentId = normalizeSourceAgentIds(row.source_agent_ids)[0];
|
|
300
|
+
const host = sourceAgentHost(sourceAgentId);
|
|
301
|
+
if (host) accountKey = `host:${host}`;
|
|
302
|
+
}
|
|
303
|
+
if (accountKey !== row.account_key) {
|
|
304
|
+
changes += mergeProviderQuotaRows(row.provider, accountKey, [row.account_key], now);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
changes += pruneProviderQuotaRecords(undefined, undefined, now - DAY_MS);
|
|
308
|
+
return changes;
|
|
309
|
+
}
|
|
310
|
+
|
|
173
311
|
function listProviderQuotaSnapshots(provider: string, accountKey: string, options: { since?: number; limit?: number } = {}): ProviderQuotaSnapshot[] {
|
|
174
312
|
const limit = Math.max(1, Math.min(options.limit ?? 288, 1000));
|
|
175
313
|
const params: any[] = [normalizeProvider(provider), cleanKey(accountKey)];
|