agent-relay-server 0.74.0 → 0.76.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.
- package/docs/openapi.json +39 -1
- package/package.json +2 -2
- package/public/assets/MessageBubble-DzhG1x8z.js +3 -0
- package/public/assets/{MessageBubble-CspI1qqW.js.map → MessageBubble-DzhG1x8z.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-WjXyWbzZ.js → PlanGraphPanel-brNJanGs.js} +2 -2
- package/public/assets/{PlanGraphPanel-WjXyWbzZ.js.map → PlanGraphPanel-brNJanGs.js.map} +1 -1
- package/public/assets/{activity-B9WoIWSe.js → activity-DIXJ-CVu.js} +2 -2
- package/public/assets/{activity-B9WoIWSe.js.map → activity-DIXJ-CVu.js.map} +1 -1
- package/public/assets/{agents-D7th_n2N.js → agents-BG9XZcVD.js} +2 -2
- package/public/assets/{agents-D7th_n2N.js.map → agents-BG9XZcVD.js.map} +1 -1
- package/public/assets/{automation-wOEiCheJ.js → automation-jVNnsYV7.js} +2 -2
- package/public/assets/{automation-wOEiCheJ.js.map → automation-jVNnsYV7.js.map} +1 -1
- package/public/assets/{chat-Bj6R0vno.js → chat-B7EMdwIg.js} +2 -2
- package/public/assets/{chat-Bj6R0vno.js.map → chat-B7EMdwIg.js.map} +1 -1
- package/public/assets/{formatted-body-impl-BXeoUw5r.js → formatted-body-impl-DBWLuF0p.js} +2 -2
- package/public/assets/{formatted-body-impl-BXeoUw5r.js.map → formatted-body-impl-DBWLuF0p.js.map} +1 -1
- package/public/assets/index-DB6XGZy9.js +23 -0
- package/public/assets/index-DB6XGZy9.js.map +1 -0
- package/public/assets/index-DepEqGST.css +2 -0
- package/public/assets/{insights-n-pd12FP.js → insights-CzGE5EyU.js} +2 -2
- package/public/assets/{insights-n-pd12FP.js.map → insights-CzGE5EyU.js.map} +1 -1
- package/public/assets/{maintenance-DZI5exUu.js → maintenance-CGWGOhDP.js} +2 -2
- package/public/assets/{maintenance-DZI5exUu.js.map → maintenance-CGWGOhDP.js.map} +1 -1
- package/public/assets/{managed-agents-Bh_vowgH.js → managed-agents-DyYn12ir.js} +2 -2
- package/public/assets/{managed-agents-Bh_vowgH.js.map → managed-agents-DyYn12ir.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-B-6jdhi0.js → markdown-preview-impl-BA37v8OC.js} +2 -2
- package/public/assets/{markdown-preview-impl-B-6jdhi0.js.map → markdown-preview-impl-BA37v8OC.js.map} +1 -1
- package/public/assets/{memory-B74VVg5K.js → memory-CoIM-761.js} +2 -2
- package/public/assets/{memory-B74VVg5K.js.map → memory-CoIM-761.js.map} +1 -1
- package/public/assets/{messages-D5n46hEh.js → messages-D-TOb6_t.js} +2 -2
- package/public/assets/{messages-D5n46hEh.js.map → messages-D-TOb6_t.js.map} +1 -1
- package/public/assets/{orchestrators-DBryzqq2.js → orchestrators-XLGUAnKs.js} +2 -2
- package/public/assets/{orchestrators-DBryzqq2.js.map → orchestrators-XLGUAnKs.js.map} +1 -1
- package/public/assets/{overview-0xb4ATEH.js → overview-C4Netp8S.js} +2 -2
- package/public/assets/{overview-0xb4ATEH.js.map → overview-C4Netp8S.js.map} +1 -1
- package/public/assets/{pairs-CnKDSHJu.js → pairs-1X219PK6.js} +2 -2
- package/public/assets/{pairs-CnKDSHJu.js.map → pairs-1X219PK6.js.map} +1 -1
- package/public/assets/{security-C2t99jNP.js → security-CCfKm-ow.js} +2 -2
- package/public/assets/{security-C2t99jNP.js.map → security-CCfKm-ow.js.map} +1 -1
- package/public/assets/{select-CCbQiEmS.js → select-BpPDknSO.js} +2 -2
- package/public/assets/{select-CCbQiEmS.js.map → select-BpPDknSO.js.map} +1 -1
- package/public/assets/{settings-Ucd8p6KS.js → settings-BUD05ZM3.js} +2 -2
- package/public/assets/{settings-Ucd8p6KS.js.map → settings-BUD05ZM3.js.map} +1 -1
- package/public/assets/{tasks-Bk8clApn.js → tasks-Dvqe1bbe.js} +2 -2
- package/public/assets/{tasks-Bk8clApn.js.map → tasks-Dvqe1bbe.js.map} +1 -1
- package/public/assets/{teams-BXkI8tvS.js → teams-I0UCuF9H.js} +2 -2
- package/public/assets/{teams-BXkI8tvS.js.map → teams-I0UCuF9H.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-BL-zWN1C.js → terminal-viewer-impl-CKM8zVGQ.js} +2 -2
- package/public/assets/{terminal-viewer-impl-BL-zWN1C.js.map → terminal-viewer-impl-CKM8zVGQ.js.map} +1 -1
- package/public/assets/{work-queue-DqMKcH5G.js → work-queue-xWRsNNPk.js} +2 -2
- package/public/assets/{work-queue-DqMKcH5G.js.map → work-queue-xWRsNNPk.js.map} +1 -1
- package/public/index.html +2 -2
- package/src/bus.ts +13 -5
- package/src/db/agents.ts +62 -7
- package/src/db/mappers.ts +18 -0
- package/src/db/migrations.ts +16 -0
- package/src/db/schema.ts +13 -0
- package/src/lifecycle-context-pressure.ts +98 -0
- package/src/lifecycle-manager.ts +21 -31
- package/src/quota.ts +40 -0
- package/src/routes/agents-spawn.ts +3 -0
- package/src/routes/agents.ts +14 -1
- package/src/routes/index.ts +2 -1
- package/src/upgrade-local-id.ts +23 -0
- package/src/upgrade.ts +19 -27
- package/public/assets/MessageBubble-CspI1qqW.js +0 -3
- package/public/assets/index-CQQTCZxg.css +0 -2
- package/public/assets/index-DNQadbv0.js +0 -22
- package/public/assets/index-DNQadbv0.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Bn as e,an as t,ct as n,gn as r,i}from"./lucide-react-DI8kTavp.js";import{l as a,t as o}from"./store-D1K4_xId.js";import{U as s,d as c,ut as l}from"./display-DHgdn4vN.js";import{t as u}from"./badge-YQhT30dy.js";import{t as d}from"./button-CaFI76Ja.js";import{B as f,K as p,s as m}from"./index-
|
|
2
|
-
//# sourceMappingURL=work-queue-
|
|
1
|
+
import{Bn as e,an as t,ct as n,gn as r,i}from"./lucide-react-DI8kTavp.js";import{l as a,t as o}from"./store-D1K4_xId.js";import{U as s,d as c,ut as l}from"./display-DHgdn4vN.js";import{t as u}from"./badge-YQhT30dy.js";import{t as d}from"./button-CaFI76Ja.js";import{B as f,K as p,s as m}from"./index-DB6XGZy9.js";import{r as h,t as g}from"./card-B_fQtKce.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)(t,{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)(r,{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)(t,{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)(i,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
|
|
2
|
+
//# sourceMappingURL=work-queue-xWRsNNPk.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-queue-DqMKcH5G.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-xWRsNNPk.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-DB6XGZy9.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-DI8kTavp.js">
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<link rel="modulepreload" crossorigin href="./assets/badge-YQhT30dy.js">
|
|
38
38
|
<link rel="modulepreload" crossorigin href="./assets/input-Cqqq0OFl.js">
|
|
39
39
|
<link rel="modulepreload" crossorigin href="./assets/themes-mW9zZP4O.js">
|
|
40
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
40
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DepEqGST.css">
|
|
41
41
|
</head>
|
|
42
42
|
<body class="bg-background text-foreground antialiased">
|
|
43
43
|
<div id="root"></div>
|
package/src/bus.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { enqueueContinuationInjectionAfterSelfResume } from "./self-resume";
|
|
|
17
17
|
import { messageMatchesAgent, targetMatchesAgent } from "./agent-ref";
|
|
18
18
|
import { probeWorkspaceOnTurnEnd } from "./workspace-probe";
|
|
19
19
|
import { busStaleGraceMs } from "./config";
|
|
20
|
+
import { quotaStateFromUnknown } from "./quota";
|
|
20
21
|
import {
|
|
21
22
|
BusProtocolError,
|
|
22
23
|
parseBusFrame,
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
import { errMessage, isRecord, stringValue } from "agent-relay-sdk";
|
|
28
29
|
import { agentProviderState } from "./provider-state";
|
|
29
30
|
import { getComponentAuth, isComponentAuthorizedFor, isAuthorized, isOriginAllowed, unauthorized } from "./security";
|
|
30
|
-
import type { AgentCard, Command, ComponentToken, ContextState, Message, ProviderCapabilities, RegisterAgentInput, Task } from "./types";
|
|
31
|
+
import type { AgentCard, Command, ComponentToken, ContextState, Message, ProviderCapabilities, QuotaState, RegisterAgentInput, Task } from "./types";
|
|
31
32
|
|
|
32
33
|
interface BusSocketData {
|
|
33
34
|
kind: "bus";
|
|
@@ -295,7 +296,7 @@ function handleRegister(ws: BusWebSocket, frame: RegisterFrame): void {
|
|
|
295
296
|
}
|
|
296
297
|
let epoch = 0;
|
|
297
298
|
if (payload.agentId) {
|
|
298
|
-
const label = stringMeta(payload.meta, "label"), lifecycle = stringMeta(payload.meta, "lifecycle"), context = contextFromMeta(payload.meta);
|
|
299
|
+
const label = stringMeta(payload.meta, "label"), lifecycle = stringMeta(payload.meta, "lifecycle"), context = contextFromMeta(payload.meta), quota = quotaFromMeta(payload.meta);
|
|
299
300
|
// Thin transport: build the unified RegisterAgentInput from the wire frame and hand it to
|
|
300
301
|
// the registerAgent service. ALL registration side effects — authoritative parent lineage
|
|
301
302
|
// (resolved from the token constraints inside the service, never the wire body), the managed
|
|
@@ -315,6 +316,7 @@ function handleRegister(ws: BusWebSocket, frame: RegisterFrame): void {
|
|
|
315
316
|
instanceId: payload.instanceId,
|
|
316
317
|
...(providerCapabilities ? { providerCapabilities } : {}),
|
|
317
318
|
...(context ? { context } : {}),
|
|
319
|
+
...(quota ? { quota } : {}),
|
|
318
320
|
...(lifecycle === "transient" || lifecycle === "persistent" ? { lifecycle } : {}),
|
|
319
321
|
meta: payload.meta,
|
|
320
322
|
};
|
|
@@ -348,12 +350,13 @@ function handleRegister(ws: BusWebSocket, frame: RegisterFrame): void {
|
|
|
348
350
|
});
|
|
349
351
|
}
|
|
350
352
|
|
|
351
|
-
function runtimeFromMeta(meta: Record<string, unknown> | undefined): { providerCapabilities?: ProviderCapabilities; context?: ContextState } | undefined {
|
|
353
|
+
function runtimeFromMeta(meta: Record<string, unknown> | undefined): { providerCapabilities?: ProviderCapabilities; context?: ContextState; quota?: QuotaState } | undefined {
|
|
352
354
|
if (!meta) return undefined;
|
|
353
355
|
const providerCapabilities = providerCapabilitiesFromMeta(meta, [], false);
|
|
354
356
|
const context = contextFromMeta(meta);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
+
const quota = quotaFromMeta(meta);
|
|
358
|
+
if (!providerCapabilities && !context && !quota) return undefined;
|
|
359
|
+
return { ...(providerCapabilities ? { providerCapabilities } : {}), ...(context ? { context } : {}), ...(quota ? { quota } : {}) };
|
|
357
360
|
}
|
|
358
361
|
|
|
359
362
|
function providerCapabilitiesFromMeta(
|
|
@@ -393,6 +396,7 @@ function providerCapabilitiesFromMeta(
|
|
|
393
396
|
lastUpdatedAt: now,
|
|
394
397
|
},
|
|
395
398
|
...(isRecord(explicit?.context) ? { context: explicit.context as ProviderCapabilities["context"] } : {}),
|
|
399
|
+
...(isRecord(explicit?.quota) ? { quota: explicit.quota as ProviderCapabilities["quota"] } : {}),
|
|
396
400
|
...(isRecord(explicit?.terminal) ? { terminal: explicit.terminal as ProviderCapabilities["terminal"] } : {}),
|
|
397
401
|
source: "runtime",
|
|
398
402
|
confidence: "reported",
|
|
@@ -423,6 +427,10 @@ function contextFromMeta(meta: Record<string, unknown>): ContextState | undefine
|
|
|
423
427
|
};
|
|
424
428
|
}
|
|
425
429
|
|
|
430
|
+
function quotaFromMeta(meta: Record<string, unknown>): QuotaState | undefined {
|
|
431
|
+
return quotaStateFromUnknown(meta.quota);
|
|
432
|
+
}
|
|
433
|
+
|
|
426
434
|
function hasRunnerLifecycleCapabilities(capabilities: string[], meta: Record<string, unknown>): boolean {
|
|
427
435
|
const lifecycle = isRecord(meta.lifecycleCapabilities) ? meta.lifecycleCapabilities : {};
|
|
428
436
|
return capabilities.includes("lifecycle.shutdown.hard") &&
|
package/src/db/agents.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { evaluatePoolBindings, upsertChannelForAgent } from "./channels.ts";
|
|
|
19
19
|
import { seedContinuationObjectiveFromSpawnPrompt } from "./continuations.ts";
|
|
20
20
|
import { messageTaskClosed } from "./claim-sql.ts";
|
|
21
21
|
import { ValidationError, getDb } from "./connection.ts";
|
|
22
|
-
import { rowToAgent, rowToContextSnapshot } from "./mappers.ts";
|
|
22
|
+
import { rowToAgent, rowToContextSnapshot, rowToQuotaSnapshot } from "./mappers.ts";
|
|
23
23
|
import { closeOpenPairsForAgent } from "./pair-maintenance.ts";
|
|
24
24
|
import { parseStringArray } from "./shared.ts";
|
|
25
25
|
import { settleSingleTargetOnDemandTasks } from "./task-maintenance.ts";
|
|
@@ -83,6 +83,8 @@ import type {
|
|
|
83
83
|
ContextSnapshot,
|
|
84
84
|
ContextState,
|
|
85
85
|
ProviderCapabilities,
|
|
86
|
+
QuotaSnapshot,
|
|
87
|
+
QuotaState,
|
|
86
88
|
TaskStatusInput,
|
|
87
89
|
WorkspaceMetadata,
|
|
88
90
|
WorkspaceRecord,
|
|
@@ -157,9 +159,10 @@ export function upsertAgent(input: RegisterAgentInput): AgentCard {
|
|
|
157
159
|
const lifecycleProvided = Object.prototype.hasOwnProperty.call(input, "lifecycle");
|
|
158
160
|
const instanceProvided = Boolean(input.instanceId);
|
|
159
161
|
const contextState = mergeRelayContextMarkers(input.id, input.context);
|
|
162
|
+
const quotaChanged = input.quota ? hasQuotaChanged(input.id, input.quota) : false;
|
|
160
163
|
const stmt = getDb().query(`
|
|
161
|
-
INSERT INTO agents (id, name, kind, label, tags, machine, rig, capabilities, ready, status, instance_id, epoch, provider_capabilities, context_state, meta, lifecycle, spawned_by, team_id, last_seen, created_at)
|
|
162
|
-
VALUES ($id, $name, $kind, $label, $tags, $machine, $rig, $capabilities, $ready, $status, $instanceId, $initialEpoch, $providerCapabilities, $contextState, $meta, $lifecycle, $spawnedBy, $teamId, $now, $now)
|
|
164
|
+
INSERT INTO agents (id, name, kind, label, tags, machine, rig, capabilities, ready, status, instance_id, epoch, provider_capabilities, context_state, quota_state, meta, lifecycle, spawned_by, team_id, last_seen, created_at)
|
|
165
|
+
VALUES ($id, $name, $kind, $label, $tags, $machine, $rig, $capabilities, $ready, $status, $instanceId, $initialEpoch, $providerCapabilities, $contextState, $quotaState, $meta, $lifecycle, $spawnedBy, $teamId, $now, $now)
|
|
163
166
|
ON CONFLICT(id) DO UPDATE SET
|
|
164
167
|
name = $name,
|
|
165
168
|
kind = $kind,
|
|
@@ -177,6 +180,7 @@ export function upsertAgent(input: RegisterAgentInput): AgentCard {
|
|
|
177
180
|
END,
|
|
178
181
|
provider_capabilities = coalesce($providerCapabilities, agents.provider_capabilities),
|
|
179
182
|
context_state = coalesce($contextState, agents.context_state),
|
|
183
|
+
quota_state = coalesce($quotaState, agents.quota_state),
|
|
180
184
|
meta = $meta,
|
|
181
185
|
lifecycle = CASE WHEN $lifecycleProvided = 1 THEN $lifecycle ELSE agents.lifecycle END,
|
|
182
186
|
spawned_by = coalesce($spawnedBy, agents.spawned_by),
|
|
@@ -202,6 +206,7 @@ export function upsertAgent(input: RegisterAgentInput): AgentCard {
|
|
|
202
206
|
$initialEpoch: instanceProvided ? 1 : 0,
|
|
203
207
|
$providerCapabilities: input.providerCapabilities ? JSON.stringify(input.providerCapabilities) : null,
|
|
204
208
|
$contextState: contextState ? JSON.stringify(contextState) : null,
|
|
209
|
+
$quotaState: input.quota ? JSON.stringify(input.quota) : null,
|
|
205
210
|
$meta: JSON.stringify(input.meta ?? {}),
|
|
206
211
|
$lifecycle: input.lifecycle ?? "persistent",
|
|
207
212
|
$lifecycleProvided: lifecycleProvided ? 1 : 0,
|
|
@@ -210,6 +215,7 @@ export function upsertAgent(input: RegisterAgentInput): AgentCard {
|
|
|
210
215
|
$now: now,
|
|
211
216
|
});
|
|
212
217
|
if (contextState) recordContextSnapshot(input.id, contextState, now);
|
|
218
|
+
if (input.quota && quotaChanged) recordQuotaSnapshot(input.id, input.quota, now);
|
|
213
219
|
|
|
214
220
|
const agent = getAgent(input.id)!;
|
|
215
221
|
seedContinuationObjectiveFromSpawnPrompt(agent.id, stringValue(agent.meta?.spawnRequestId), DEFAULT_SELF_RESUME_CONFIG, now);
|
|
@@ -347,28 +353,32 @@ export function recordAgentExitDiagnostics(id: string, diagnostics: ManagedSessi
|
|
|
347
353
|
export function heartbeat(
|
|
348
354
|
id: string,
|
|
349
355
|
guard?: AgentSessionGuard,
|
|
350
|
-
runtime?: { providerCapabilities?: ProviderCapabilities; context?: ContextState },
|
|
356
|
+
runtime?: { providerCapabilities?: ProviderCapabilities; context?: ContextState; quota?: QuotaState },
|
|
351
357
|
): boolean {
|
|
352
358
|
if (!validateAgentSession(id, guard).ok) return false;
|
|
353
359
|
const now = Date.now();
|
|
354
|
-
if (runtime?.providerCapabilities || runtime?.context) {
|
|
360
|
+
if (runtime?.providerCapabilities || runtime?.context || runtime?.quota) {
|
|
355
361
|
const contextState = mergeRelayContextMarkers(id, runtime.context);
|
|
362
|
+
const quotaChanged = runtime.quota ? hasQuotaChanged(id, runtime.quota) : false;
|
|
356
363
|
const result = getDb()
|
|
357
364
|
.query(`
|
|
358
365
|
UPDATE agents SET
|
|
359
366
|
last_seen = ?,
|
|
360
367
|
status = CASE WHEN status = 'offline' THEN 'idle' ELSE status END,
|
|
361
368
|
provider_capabilities = coalesce(?, provider_capabilities),
|
|
362
|
-
context_state = coalesce(?, context_state)
|
|
369
|
+
context_state = coalesce(?, context_state),
|
|
370
|
+
quota_state = coalesce(?, quota_state)
|
|
363
371
|
WHERE id = ?
|
|
364
372
|
`)
|
|
365
373
|
.run(
|
|
366
374
|
now,
|
|
367
375
|
runtime.providerCapabilities ? JSON.stringify(runtime.providerCapabilities) : null,
|
|
368
376
|
contextState ? JSON.stringify(contextState) : null,
|
|
377
|
+
runtime.quota ? JSON.stringify(runtime.quota) : null,
|
|
369
378
|
id,
|
|
370
379
|
);
|
|
371
380
|
if (result.changes > 0 && contextState) recordContextSnapshot(id, contextState, now);
|
|
381
|
+
if (result.changes > 0 && runtime.quota && quotaChanged) recordQuotaSnapshot(id, runtime.quota, now);
|
|
372
382
|
return result.changes > 0;
|
|
373
383
|
}
|
|
374
384
|
const result = getDb()
|
|
@@ -385,11 +395,56 @@ export function listContextSnapshots(agentId: string, options: { since?: number;
|
|
|
385
395
|
sql += " AND captured_at >= ?";
|
|
386
396
|
params.push(options.since);
|
|
387
397
|
}
|
|
388
|
-
sql += " ORDER BY captured_at ASC LIMIT ?";
|
|
398
|
+
sql += " ORDER BY captured_at ASC, id ASC LIMIT ?";
|
|
389
399
|
params.push(limit);
|
|
390
400
|
return (getDb().query(sql).all(...params) as any[]).map(rowToContextSnapshot);
|
|
391
401
|
}
|
|
392
402
|
|
|
403
|
+
export function listQuotaSnapshots(agentId: string, options: { since?: number; limit?: number } = {}): QuotaSnapshot[] {
|
|
404
|
+
const limit = Math.max(1, Math.min(options.limit ?? 288, 1000));
|
|
405
|
+
const params: any[] = [agentId];
|
|
406
|
+
let sql = "SELECT * FROM quota_snapshots WHERE agent_id = ?";
|
|
407
|
+
if (options.since !== undefined) {
|
|
408
|
+
sql += " AND captured_at >= ?";
|
|
409
|
+
params.push(options.since);
|
|
410
|
+
}
|
|
411
|
+
sql += " ORDER BY captured_at ASC, id ASC LIMIT ?";
|
|
412
|
+
params.push(limit);
|
|
413
|
+
return (getDb().query(sql).all(...params) as any[]).map(rowToQuotaSnapshot);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function hasQuotaChanged(agentId: string, quota: QuotaState): boolean {
|
|
417
|
+
const row = getDb().query("SELECT quota_state FROM agents WHERE id = ?").get(agentId) as { quota_state?: string | null } | undefined;
|
|
418
|
+
return row?.quota_state !== JSON.stringify(quota);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function pruneQuotaSnapshots(agentId?: string, olderThan = Date.now() - DAY_MS): number {
|
|
422
|
+
const result = agentId
|
|
423
|
+
? getDb().query("DELETE FROM quota_snapshots WHERE agent_id = ? AND captured_at < ?").run(agentId, olderThan)
|
|
424
|
+
: getDb().query("DELETE FROM quota_snapshots WHERE captured_at < ?").run(olderThan);
|
|
425
|
+
return result.changes;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function recordQuotaSnapshot(agentId: string, quota: QuotaState, now: number): void {
|
|
429
|
+
getDb().query(`
|
|
430
|
+
INSERT INTO quota_snapshots (
|
|
431
|
+
agent_id,
|
|
432
|
+
windows,
|
|
433
|
+
source,
|
|
434
|
+
quota_state,
|
|
435
|
+
captured_at
|
|
436
|
+
)
|
|
437
|
+
VALUES (?, ?, ?, ?, ?)
|
|
438
|
+
`).run(
|
|
439
|
+
agentId,
|
|
440
|
+
JSON.stringify(quota.windows),
|
|
441
|
+
quota.source,
|
|
442
|
+
JSON.stringify(quota),
|
|
443
|
+
now,
|
|
444
|
+
);
|
|
445
|
+
pruneQuotaSnapshots(agentId, now - DAY_MS);
|
|
446
|
+
}
|
|
447
|
+
|
|
393
448
|
function pruneContextSnapshots(agentId?: string, olderThan = Date.now() - DAY_MS): number {
|
|
394
449
|
const result = agentId
|
|
395
450
|
? getDb().query("DELETE FROM context_snapshots WHERE agent_id = ? AND captured_at < ?").run(agentId, olderThan)
|
package/src/db/mappers.ts
CHANGED
|
@@ -72,6 +72,8 @@ import type {
|
|
|
72
72
|
ContextSnapshot,
|
|
73
73
|
ContextState,
|
|
74
74
|
ProviderCapabilities,
|
|
75
|
+
QuotaSnapshot,
|
|
76
|
+
QuotaState,
|
|
75
77
|
TaskStatusInput,
|
|
76
78
|
WorkspaceMetadata,
|
|
77
79
|
WorkspaceRecord,
|
|
@@ -127,6 +129,7 @@ export function rowToAgent(row: any): AgentCard {
|
|
|
127
129
|
epoch: typeof row.epoch === "number" ? row.epoch : 0,
|
|
128
130
|
providerCapabilities: parseJson<ProviderCapabilities | undefined>(row.provider_capabilities, undefined),
|
|
129
131
|
context: parseJson<ContextState | undefined>(row.context_state, undefined),
|
|
132
|
+
quota: parseJson<QuotaState | undefined>(row.quota_state, undefined),
|
|
130
133
|
meta: parseJson(row.meta, {}),
|
|
131
134
|
lifecycle: row.lifecycle === "transient" ? "transient" : "persistent",
|
|
132
135
|
spawnedBy: row.spawned_by ?? undefined,
|
|
@@ -161,6 +164,21 @@ export function rowToContextSnapshot(row: any): ContextSnapshot {
|
|
|
161
164
|
};
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
export function rowToQuotaSnapshot(row: any): QuotaSnapshot {
|
|
168
|
+
const quota = parseJson<QuotaState>(row.quota_state, {
|
|
169
|
+
windows: parseJson<QuotaState["windows"]>(row.windows, []),
|
|
170
|
+
source: row.source,
|
|
171
|
+
updatedAt: row.captured_at,
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
agentId: row.agent_id,
|
|
175
|
+
capturedAt: row.captured_at,
|
|
176
|
+
windows: quota.windows,
|
|
177
|
+
source: quota.source,
|
|
178
|
+
updatedAt: quota.updatedAt,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
164
182
|
export function rowToMessage(row: any): Message {
|
|
165
183
|
return {
|
|
166
184
|
id: row.id,
|
package/src/db/migrations.ts
CHANGED
|
@@ -76,6 +76,8 @@ import type {
|
|
|
76
76
|
ContextSnapshot,
|
|
77
77
|
ContextState,
|
|
78
78
|
ProviderCapabilities,
|
|
79
|
+
QuotaSnapshot,
|
|
80
|
+
QuotaState,
|
|
79
81
|
TaskStatusInput,
|
|
80
82
|
WorkspaceMetadata,
|
|
81
83
|
WorkspaceRecord,
|
|
@@ -537,6 +539,9 @@ export function applyMigrations(): void {
|
|
|
537
539
|
if (!agentColNames.includes("context_state")) {
|
|
538
540
|
getDb().run("ALTER TABLE agents ADD COLUMN context_state TEXT");
|
|
539
541
|
}
|
|
542
|
+
if (!agentColNames.includes("quota_state")) {
|
|
543
|
+
getDb().run("ALTER TABLE agents ADD COLUMN quota_state TEXT");
|
|
544
|
+
}
|
|
540
545
|
if (!agentColNames.includes("lifecycle")) {
|
|
541
546
|
getDb().run("ALTER TABLE agents ADD COLUMN lifecycle TEXT NOT NULL DEFAULT 'persistent'");
|
|
542
547
|
}
|
|
@@ -594,6 +599,17 @@ export function applyMigrations(): void {
|
|
|
594
599
|
)
|
|
595
600
|
`);
|
|
596
601
|
getDb().run("CREATE INDEX IF NOT EXISTS idx_context_snapshots_agent_time ON context_snapshots(agent_id, captured_at DESC)");
|
|
602
|
+
getDb().run(`
|
|
603
|
+
CREATE TABLE IF NOT EXISTS quota_snapshots (
|
|
604
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
605
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
606
|
+
windows TEXT NOT NULL,
|
|
607
|
+
source TEXT NOT NULL,
|
|
608
|
+
quota_state TEXT NOT NULL,
|
|
609
|
+
captured_at INTEGER NOT NULL
|
|
610
|
+
)
|
|
611
|
+
`);
|
|
612
|
+
getDb().run("CREATE INDEX IF NOT EXISTS idx_quota_snapshots_agent_time ON quota_snapshots(agent_id, captured_at DESC)");
|
|
597
613
|
|
|
598
614
|
getDb().run(`
|
|
599
615
|
CREATE TABLE IF NOT EXISTS memories (
|
package/src/db/schema.ts
CHANGED
|
@@ -74,6 +74,8 @@ import type {
|
|
|
74
74
|
ContextSnapshot,
|
|
75
75
|
ContextState,
|
|
76
76
|
ProviderCapabilities,
|
|
77
|
+
QuotaSnapshot,
|
|
78
|
+
QuotaState,
|
|
77
79
|
TaskStatusInput,
|
|
78
80
|
WorkspaceMetadata,
|
|
79
81
|
WorkspaceRecord,
|
|
@@ -104,6 +106,7 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
104
106
|
epoch INTEGER NOT NULL DEFAULT 0,
|
|
105
107
|
provider_capabilities TEXT,
|
|
106
108
|
context_state TEXT,
|
|
109
|
+
quota_state TEXT,
|
|
107
110
|
meta TEXT NOT NULL DEFAULT '{}', lifecycle TEXT NOT NULL DEFAULT 'persistent',
|
|
108
111
|
spawned_by TEXT,
|
|
109
112
|
team_id TEXT,
|
|
@@ -287,6 +290,16 @@ export function initDb(path: string = "agent-relay.db"): Database {
|
|
|
287
290
|
);
|
|
288
291
|
CREATE INDEX IF NOT EXISTS idx_context_snapshots_agent_time ON context_snapshots(agent_id, captured_at DESC);
|
|
289
292
|
|
|
293
|
+
CREATE TABLE IF NOT EXISTS quota_snapshots (
|
|
294
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
295
|
+
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
296
|
+
windows TEXT NOT NULL,
|
|
297
|
+
source TEXT NOT NULL,
|
|
298
|
+
quota_state TEXT NOT NULL,
|
|
299
|
+
captured_at INTEGER NOT NULL
|
|
300
|
+
);
|
|
301
|
+
CREATE INDEX IF NOT EXISTS idx_quota_snapshots_agent_time ON quota_snapshots(agent_id, captured_at DESC);
|
|
302
|
+
|
|
290
303
|
CREATE TABLE IF NOT EXISTS agent_continuations (
|
|
291
304
|
agent_id TEXT PRIMARY KEY REFERENCES agents(id) ON DELETE CASCADE,
|
|
292
305
|
objective TEXT NOT NULL,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createCommand } from "./commands-db";
|
|
2
|
+
import { getAgent, listAgents } from "./db";
|
|
3
|
+
import { getManagedAgentState } from "./config-store";
|
|
4
|
+
import { emitCommandEvent } from "./command-events";
|
|
5
|
+
import { handleContextPressure } from "./context-advisory";
|
|
6
|
+
import type { AgentCard, Command, SpawnPolicy } from "./types";
|
|
7
|
+
|
|
8
|
+
interface LifecycleContextPressureInput {
|
|
9
|
+
policies: SpawnPolicy[];
|
|
10
|
+
now: () => number;
|
|
11
|
+
hasActiveCommand: (target: string, type: string) => boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface CompactAgentInput {
|
|
15
|
+
agent: AgentCard;
|
|
16
|
+
reason: string;
|
|
17
|
+
policyName?: string;
|
|
18
|
+
alwaysReload?: string[];
|
|
19
|
+
now: () => number;
|
|
20
|
+
hasActiveCommand: (target: string, type: string) => boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function reconcileLifecycleContextPressure(input: LifecycleContextPressureInput): Set<string> {
|
|
24
|
+
const byPolicy = new Map(input.policies.map((policy) => [policy.name, policy]));
|
|
25
|
+
const candidates = new Map<string, { agent: AgentCard; policy?: SpawnPolicy }>();
|
|
26
|
+
|
|
27
|
+
for (const policy of input.policies) {
|
|
28
|
+
const state = getManagedAgentState(policy.name);
|
|
29
|
+
if (state?.status !== "running" || !state.agentId) continue;
|
|
30
|
+
const agent = getAgent(state.agentId);
|
|
31
|
+
if (!agent || agent.status === "offline") continue;
|
|
32
|
+
candidates.set(agent.id, { agent, policy });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const agent of listAgents()) {
|
|
36
|
+
if (!isLiveRunnerManagedAgent(agent) || candidates.has(agent.id)) continue;
|
|
37
|
+
const policyName = typeof agent.meta?.policyName === "string" ? agent.meta.policyName : undefined;
|
|
38
|
+
candidates.set(agent.id, { agent, policy: policyName ? byPolicy.get(policyName) : undefined });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handled = new Set<string>();
|
|
42
|
+
for (const { agent, policy } of candidates.values()) {
|
|
43
|
+
if (handleContextPressure({
|
|
44
|
+
agent,
|
|
45
|
+
now: input.now,
|
|
46
|
+
hasActiveCommand: input.hasActiveCommand,
|
|
47
|
+
compact: (reason) => requestAgentCompact({
|
|
48
|
+
agent,
|
|
49
|
+
reason,
|
|
50
|
+
policyName: policy?.name,
|
|
51
|
+
alwaysReload: alwaysReloadTags(agent.tags),
|
|
52
|
+
now: input.now,
|
|
53
|
+
hasActiveCommand: input.hasActiveCommand,
|
|
54
|
+
}),
|
|
55
|
+
})) {
|
|
56
|
+
handled.add(agent.id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return handled;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function requestAgentCompact(input: CompactAgentInput): Command | null {
|
|
63
|
+
if (input.hasActiveCommand(input.agent.id, "agent.compact")) return null;
|
|
64
|
+
const spawnRequestId = typeof input.agent.meta?.spawnRequestId === "string" ? input.agent.meta.spawnRequestId : undefined;
|
|
65
|
+
const sessionName = typeof input.agent.meta?.sessionName === "string" ? input.agent.meta.sessionName : undefined;
|
|
66
|
+
const tmuxSession = typeof input.agent.meta?.tmuxSession === "string" ? input.agent.meta.tmuxSession : undefined;
|
|
67
|
+
const command = createCommand({
|
|
68
|
+
type: "agent.compact",
|
|
69
|
+
source: "system",
|
|
70
|
+
target: input.agent.id,
|
|
71
|
+
correlationId: spawnRequestId,
|
|
72
|
+
params: {
|
|
73
|
+
action: "compact",
|
|
74
|
+
agentId: input.agent.id,
|
|
75
|
+
...(input.policyName ? { policyName: input.policyName } : {}),
|
|
76
|
+
...(spawnRequestId ? { spawnRequestId } : {}),
|
|
77
|
+
...(sessionName ? { sessionName } : {}),
|
|
78
|
+
...(tmuxSession ? { tmuxSession } : {}),
|
|
79
|
+
reason: input.reason,
|
|
80
|
+
memory: input.alwaysReload?.length ? { alwaysReload: input.alwaysReload } : undefined,
|
|
81
|
+
requestedBy: "lifecycle-manager",
|
|
82
|
+
requestedAt: input.now(),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
emitCommandEvent(command, "command.requested");
|
|
86
|
+
return command;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function alwaysReloadTags(tags: string[]): string[] {
|
|
90
|
+
return tags
|
|
91
|
+
.filter((tag) => tag.startsWith("memory-reload:"))
|
|
92
|
+
.map((tag) => tag.slice("memory-reload:".length).trim())
|
|
93
|
+
.filter(Boolean);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isLiveRunnerManagedAgent(agent: AgentCard): boolean {
|
|
97
|
+
return agent.meta?.runnerManaged === true && agent.status !== "offline" && agent.status !== "stale";
|
|
98
|
+
}
|
package/src/lifecycle-manager.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
upsertManagedAgentState,
|
|
10
10
|
} from "./config-store";
|
|
11
11
|
import { emitRelayEvent } from "./events";
|
|
12
|
-
import {
|
|
12
|
+
import { alwaysReloadTags, reconcileLifecycleContextPressure, requestAgentCompact } from "./lifecycle-context-pressure";
|
|
13
13
|
import { markManagedAgentRunning, emitManagedState } from "./services/managed-running";
|
|
14
14
|
import { authContextFromSystem } from "./services/auth-context";
|
|
15
15
|
import { emitCommandEvent } from "./command-events";
|
|
@@ -66,8 +66,10 @@ export class LifecycleManager {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
async tick(): Promise<void> {
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const policies = this.loadPolicies();
|
|
70
|
+
const contextHandled = this.reconcileContextPressure(policies);
|
|
71
|
+
for (const policy of policies) {
|
|
72
|
+
this.reconcilePolicy(policy, contextHandled);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -175,7 +177,7 @@ export class LifecycleManager {
|
|
|
175
177
|
onConfigChanged(namespace: string, key: string): void {
|
|
176
178
|
if (namespace !== "spawn-policy") return;
|
|
177
179
|
const policy = this.loadPolicies().find((item) => item.name === key);
|
|
178
|
-
if (policy) this.reconcilePolicy(policy);
|
|
180
|
+
if (policy) this.reconcilePolicy(policy, this.reconcileContextPressure([policy]));
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
onRelayUpdated(_newVersion: string): void {
|
|
@@ -309,7 +311,7 @@ export class LifecycleManager {
|
|
|
309
311
|
return command;
|
|
310
312
|
}
|
|
311
313
|
|
|
312
|
-
private reconcilePolicy(policy: SpawnPolicy): void {
|
|
314
|
+
private reconcilePolicy(policy: SpawnPolicy, contextHandled = new Set<string>()): void {
|
|
313
315
|
const state = getManagedAgentState(policy.name);
|
|
314
316
|
const enabled = policy.enabled !== false;
|
|
315
317
|
if (!state) {
|
|
@@ -354,9 +356,16 @@ export class LifecycleManager {
|
|
|
354
356
|
this.stopAgent(policy, true, "idle-timeout");
|
|
355
357
|
return;
|
|
356
358
|
}
|
|
357
|
-
if (
|
|
359
|
+
if (contextHandled.has(agent.id)) return;
|
|
358
360
|
if (this.shouldCompact(agent)) {
|
|
359
|
-
|
|
361
|
+
requestAgentCompact({
|
|
362
|
+
agent,
|
|
363
|
+
reason: "context-threshold",
|
|
364
|
+
policyName: policy.name,
|
|
365
|
+
alwaysReload: alwaysReloadTags(agent.tags),
|
|
366
|
+
now: this.now,
|
|
367
|
+
hasActiveCommand: (target, type) => this.hasActiveCommand(target, type),
|
|
368
|
+
});
|
|
360
369
|
return;
|
|
361
370
|
}
|
|
362
371
|
if (policy.scheduledDailyRestart && this.dailyRestartDue(policy, state) && this.isIdle(policy, state, 3600)) {
|
|
@@ -384,24 +393,12 @@ export class LifecycleManager {
|
|
|
384
393
|
}
|
|
385
394
|
}
|
|
386
395
|
|
|
387
|
-
private
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
target: agentId,
|
|
393
|
-
params: {
|
|
394
|
-
action: "compact",
|
|
395
|
-
agentId,
|
|
396
|
-
policyName: policy.name,
|
|
397
|
-
reason,
|
|
398
|
-
memory: alwaysReload.length ? { alwaysReload } : undefined,
|
|
399
|
-
requestedBy: "lifecycle-manager",
|
|
400
|
-
requestedAt: this.now(),
|
|
401
|
-
},
|
|
396
|
+
private reconcileContextPressure(policies: SpawnPolicy[]): Set<string> {
|
|
397
|
+
return reconcileLifecycleContextPressure({
|
|
398
|
+
policies,
|
|
399
|
+
now: this.now,
|
|
400
|
+
hasActiveCommand: (target, type) => this.hasActiveCommand(target, type),
|
|
402
401
|
});
|
|
403
|
-
emitCommandEvent(command, "command.requested");
|
|
404
|
-
return command;
|
|
405
402
|
}
|
|
406
403
|
|
|
407
404
|
private ensureStoppedState(policy: SpawnPolicy): void {
|
|
@@ -654,10 +651,3 @@ function agentTerminalFailureMessage(agent: ReturnType<typeof getAgent> | null):
|
|
|
654
651
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
655
652
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
656
653
|
}
|
|
657
|
-
|
|
658
|
-
function alwaysReloadTags(tags: string[]): string[] {
|
|
659
|
-
return tags
|
|
660
|
-
.filter((tag) => tag.startsWith("memory-reload:"))
|
|
661
|
-
.map((tag) => tag.slice("memory-reload:".length).trim())
|
|
662
|
-
.filter(Boolean);
|
|
663
|
-
}
|